From 6b955aa143eb82a68105b654ef26f9d30a9350b8 Mon Sep 17 00:00:00 2001 From: kpa39l Date: Sun, 13 Sep 2026 20:27:41 +0000 Subject: [PATCH] =?UTF-8?q?openspec:=20=D0=B0=D1=80=D1=85=D0=B8=D0=B2?= =?UTF-8?q?=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=20change=20gotosocial-publ?= =?UTF-8?q?isher=20(2026-09-13),=20delta=20=D0=B2=D0=BB=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=B2=20specs/gotosocial-publisher?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../specs/gotosocial-publisher/spec.md | 0 .../2026-09-13-gotosocial-publisher}/tasks.md | 10 +-- openspec/specs/gotosocial-publisher/spec.md | 74 +++++++++++++++++++ 6 files changed, 79 insertions(+), 5 deletions(-) rename openspec/changes/{gotosocial-publisher => archive/2026-09-13-gotosocial-publisher}/.openspec.yaml (100%) rename openspec/changes/{gotosocial-publisher => archive/2026-09-13-gotosocial-publisher}/design.md (100%) rename openspec/changes/{gotosocial-publisher => archive/2026-09-13-gotosocial-publisher}/proposal.md (100%) rename openspec/changes/{gotosocial-publisher => archive/2026-09-13-gotosocial-publisher}/specs/gotosocial-publisher/spec.md (100%) rename openspec/changes/{gotosocial-publisher => archive/2026-09-13-gotosocial-publisher}/tasks.md (91%) create mode 100644 openspec/specs/gotosocial-publisher/spec.md diff --git a/openspec/changes/gotosocial-publisher/.openspec.yaml b/openspec/changes/archive/2026-09-13-gotosocial-publisher/.openspec.yaml similarity index 100% rename from openspec/changes/gotosocial-publisher/.openspec.yaml rename to openspec/changes/archive/2026-09-13-gotosocial-publisher/.openspec.yaml diff --git a/openspec/changes/gotosocial-publisher/design.md b/openspec/changes/archive/2026-09-13-gotosocial-publisher/design.md similarity index 100% rename from openspec/changes/gotosocial-publisher/design.md rename to openspec/changes/archive/2026-09-13-gotosocial-publisher/design.md diff --git a/openspec/changes/gotosocial-publisher/proposal.md b/openspec/changes/archive/2026-09-13-gotosocial-publisher/proposal.md similarity index 100% rename from openspec/changes/gotosocial-publisher/proposal.md rename to openspec/changes/archive/2026-09-13-gotosocial-publisher/proposal.md diff --git a/openspec/changes/gotosocial-publisher/specs/gotosocial-publisher/spec.md b/openspec/changes/archive/2026-09-13-gotosocial-publisher/specs/gotosocial-publisher/spec.md similarity index 100% rename from openspec/changes/gotosocial-publisher/specs/gotosocial-publisher/spec.md rename to openspec/changes/archive/2026-09-13-gotosocial-publisher/specs/gotosocial-publisher/spec.md diff --git a/openspec/changes/gotosocial-publisher/tasks.md b/openspec/changes/archive/2026-09-13-gotosocial-publisher/tasks.md similarity index 91% rename from openspec/changes/gotosocial-publisher/tasks.md rename to openspec/changes/archive/2026-09-13-gotosocial-publisher/tasks.md index 49e64fc..75a978e 100644 --- a/openspec/changes/gotosocial-publisher/tasks.md +++ b/openspec/changes/archive/2026-09-13-gotosocial-publisher/tasks.md @@ -38,8 +38,8 @@ ## 7. Документация и git -- [ ] 7.1 STATUS.md: раздел «Fediverse-публикация (GoToSocial)» -- [ ] 7.2 WALKTHROUGH.md/PRD.md: фича отмечена -- [ ] 7.3 git commit + push на gitverse -- [ ] 7.4 openspec archive (после подтверждения пользователя) -- [ ] 7.5 backup.sh (обязателен после изменений) \ No newline at end of file +- [x] 7.1 STATUS.md: раздел «Fediverse-публикация (GoToSocial)» +- [x] 7.2 WALKTHROUGH.md/PRD.md: фича отмечена +- [x] 7.3 git commit + push на gitverse +- [x] 7.4 openspec archive (после подтверждения пользователя) +- [x] 7.5 backup.sh (обязателен после изменений) \ No newline at end of file diff --git a/openspec/specs/gotosocial-publisher/spec.md b/openspec/specs/gotosocial-publisher/spec.md new file mode 100644 index 0000000..3ceb004 --- /dev/null +++ b/openspec/specs/gotosocial-publisher/spec.md @@ -0,0 +1,74 @@ +# gotosocial-publisher Specification + +## Purpose +TBD - created by archiving change gotosocial-publisher. Update Purpose after archive. + +## Requirements + +### Requirement: Fediverse-канал в publisher-service + +* publisher-service MUST уметь публиковать карточку в GoToSocial через Mastodon-совместимый REST API при указании fediverse-канала в запросе или конфиге. +* publisher-service MUST отправлять текст статуса через `POST /api/v1/statuses` с заголовком `Authorization: Bearer `. +* publisher-service MUST обрезать текст до 5000 символов (лимит GtS) перед публикацией, сохраняя ссылку на оригинал последней строкой. +* publisher-service MAY поддерживать visibility из конфига (`GT_SOCIAL_VISIBILITY`, default `public`). +* publisher-service MUST включать fediverse-канал в fan-out: ошибка одного канала не должна отменять публикацию в остальные (поведение как у Telegram-каналов). +* publisher-service MUST в режиме `dry_run` эмулировать публикацию в fediverse без реального HTTP-запроса наружу. + +#### Scenario: Публикация карточки в GoToSocial + +GIVEN конфиг publisher содержит `GT_SOCIAL_URL=https://social.dedinit.ru` и `GT_SOCIAL_ACCESS_TOKEN=<токен @vesti>`, +WHEN веб (или curl) отправляет `POST /api/v1/publish` с `card: {text, media?}` и каналом `@vesti@dedinit.ru`, +THEN publisher вызывает `POST https://social.dedinit.ru/api/v1/statuses` с Bearer-токеном и текстом, +AND ответ содержит `results["@vesti@dedinit.ru"].message_id` = id созданного статуса +AND `ok=true`, если GtS вернул 200. + +#### Scenario: Публикация с медиа + +GIVEN у карточки есть `media` и файл существует, +WHEN publisher публикует в fediverse-канал, +THEN publisher сначала загружает файл через `POST /api/v2/media` (multipart), получает `media_id`, +AND передаёт массив `media_ids` в `POST /api/v1/statuses` (до 6 вложений). + +#### Scenario: dry_run не уходит наружу + +GIVEN `dry_run=true` в запросе, +WHEN publisher обрабатывает fediverse-канал, +THEN в `results["@vesti@dedinit.ru"]` возвращается пустой `ChannelResult()` (message_id=0) +AND реальный HTTP-запрос к social.dedinit.ru НЕ выполняется. + +#### Scenario: GtS недоступен + +GIVEN GoToSocial не отвечает (сеть/HTTP 5xx), +WHEN publisher публикует в fediverse-канал, +THEN в `results["@vesti@dedinit.ru"].error` — понятное сообщение об ошибке +AND остальные каналы (Telegram) публикуются как обычно +AND `ok=false` (но без общего 502, если хотя бы один канал успешен). + +### Requirement: Конфигурация fediverse + +* publisher-service MUST читать настройки GtS из env: `GT_SOCIAL_URL`, `GT_SOCIAL_ACCESS_TOKEN`, `GT_SOCIAL_VISIBILITY` (SECRETS в .env). +* publisher-service MUST добавлять в `/healthz` блок `gotosocial`: url, аккаунт (из verify_credentials), `token_set`. +* publisher-service MUST определять fediverse-канал по признаку: содержит `@` + точка (например `@vesti@dedinit.ru`) или префикс `gt:` — и НЕ трактовать его как Telegram (chat_id). + +#### Scenario: healthz показывает состояние GtS + +GIVEN publisher запущен с настроенным GT_SOCIAL_ACCESS_TOKEN, +WHEN GET /healthz, +THEN ответ содержит `gotosocial: {url, token_set: true, account: }` (или `account_ok: false` при сбое verify_credentials). + +#### Scenario: token не задан + +GIVEN `GT_SOCIAL_ACCESS_TOKEN` пуст, +WHEN publisher получает запрос публикации в fediverse-канал, +THEN результат канала содержит `error` «GT_SOCIAL_ACCESS_TOKEN не задан» (HTTP-статус общий 502 при пустых остальных), +AND healthz показывает `token_set: false`. + +### Requirement: Удаление статуса (поддержка отмены тестовых) + +* delete_status MUST удалять статус по id через `DELETE /api/v1/statuses/{id}` (для отмены тестовых публикаций/очистки канала); это SHOULD-часть publisher-service. + +#### Scenario: Удаление статуса + +GIVEN существует статус с id=123 в GtS, +WHEN вызывается `gotosocial.delete_status(123)`, +THEN GtS возвращает 200 и статус исчезает из ленты (проверяется GET /api/v1/statuses/123 → 404).