## MODIFIED Requirements ### Requirement: Маршруты агента Хаб SHALL предоставлять агенту следующие маршруты (все под агентским токеном): - `POST /agent/claim` `{agent_version, settings_version, last_jira_call_at, update_state, launcher_version}` — долгий запрос до 30 с; ответ `{command: {id, type, params, pause_seconds}, settings_version, agent_release}` или `{command: null, settings_version, agent_release}`, где `agent_release` — `{version, sha256}` опубликованной версии агента или `null`; обновляет `agent_status` (`last_jira_call_at = null` не затирает прежнее значение). `update_state` (объект или `null`, `error` хранится до 500 символов) и `launcher_version` (целое или `null`) необязательны; хаб SHALL хранить последние значения в памяти процесса, значения неверного типа игнорировать, не отвергая `claim`. Если есть команда в `claimed`, `claim` SHALL выдать её снова (повторная выдача, см. `commands`), а не следующую из очереди. - `GET /agent/settings` — настройки для агента (без токенов). - `GET /agent/commands/{id}` — команда по id (для восстановления из журнала), включая `pause_seconds`. - `POST /agent/commands/{id}/result` `{status, result, error, jira_calls, rate_limited}` — идемпотентно: повторный отчёт по команде в терминальном статусе отвечает 200 без изменений, кроме позднего отчёта `done` с `result.key` по `ensure_issue` в `cancelled` или `failed` без результата (результат записывается, статус не меняется, заметка `new:N` переносится на ключ). Отчёт по команде в `queued` SHALL отвергаться 409 `not_claimed`. `error` хаб SHALL обрезать до 1500 символов. - `POST /agent/state` `{command_id, issues: [...], links: [...], neighbor_keys: [...], page, last_page}` — загрузка состояния пачками; `neighbor_keys` — ключи вне базовой JQL, дочитанные по связям; хаб обновляет зеркало и `sync_run`. Каждая пачка применяется независимо; `page` и `last_page` необязательны и хабом не используются. Поле задачи `comments` необязательно; если оно есть, хаб SHALL до записи проверить его форму: список объектов, `id` — непустая строка, `body` — строка, `author`, `created`, `updated` — строка или `null`; нарушение — 422 `bad_state` с ключом задачи и именем поля, пачка не применяется. - `GET /agent/known-keys` — ключи зеркала (для определения соседей). - `GET /agent/release` — опубликованная версия агента `{version, sha256, content}` (`content` — текст `agent.py`); нет публикации или файл не сходится с манифестом по sha256 — 404 `no_release`. #### Scenario: Выдача с подстановкой ключа - **WHEN** следующая команда ссылается на `new:60`, а `ensure_issue` для `new:60` завершилась с `AURORA-1590` - **THEN** `claim` отдаёт команду с `AURORA-1590` в параметрах #### Scenario: Повторный отчёт - **WHEN** агент дважды прислал `result` для команды 15 - **THEN** второй ответ 200, статус и результат не изменились #### Scenario: Отчёт по невыданной команде - **WHEN** `POST /agent/commands/16/result` с `status = done`, команда 16 в `queued` - **THEN** 409 `not_claimed`, команда осталась `queued` #### Scenario: Длинная ошибка - **WHEN** агент прислал `failed` с `error` длиной 5000 символов - **THEN** в `command.error` первые 1500 символов #### Scenario: Поздний отчёт после предела выдач - **WHEN** `ensure_issue {temp_key: new:8, …}` переведена хабом в `failed` «агент не отчитался после 3 выдач», затем агент прислал `done` с `result.key = AURORA-1630` - **THEN** команда `failed`, в `result` — `AURORA-1630`; заметка `new:8` перенесена на `AURORA-1630` #### Scenario: Публикация в ответе claim - **WHEN** опубликована v6, агент v5 отправил `claim`, команд нет - **THEN** ответ `{command: null, settings_version, agent_release: {version: 6, sha256: …}}` #### Scenario: Состояние обновления от агента - **WHEN** агент прислал в `claim` `update_state = {state: rolled_back, from_version: 6, to_version: 7, error: "код 1 через 3 с"}` и `launcher_version = 1` - **THEN** `GET /agent` отдаёт это `update_state`, `launcher_version = 1` и `update_hint` «откат с v7 на v6: код 1 через 3 с» #### Scenario: Скачивание версии - **WHEN** агентским токеном `GET /agent/release`, опубликована v6 - **THEN** 200 `{version: 6, sha256, content}`, sha256 от `content` в UTF-8 равен `sha256`; клиентским токеном — 401; без публикации — 404 `no_release` #### Scenario: Неверный комментарий в пачке - **WHEN** `POST /agent/state` с задачами X и Y, у Y комментарий `{id: "7", body: 42}` - **THEN** 422 `bad_state` с ключом Y и полем `body`; ни X, ни Y в зеркале не изменились ### Requirement: Маршруты интерфейсов Хаб SHALL предоставлять интерфейсам (MCP, позже просмотрщик) следующие маршруты под клиентским токеном: - `GET /issues` с фильтрами `sprint`, `assignee`, `work_kind`, `fact_status`, `status`, `label`, `epic`, `neighbor`, `q` (подстрока темы), `limit`/`offset` — строки зеркала (все поля `issue`) с `fact_status` и `work_kind` из заметок; сокращение до карточки — дело интерфейса. - `GET /issues/{key}` — задача целиком: поля, `epic_summary` (тема эпика из зеркала или `null`), связи в обе стороны (`links_out: [{target_key, link_type, summary, status}]`, `links_in: [{source_key, link_type, summary, status}]`, где `summary` и `status` — связанной задачи из зеркала или `null`, если её там нет), заметки, последние 10 команд по ней, `comments: [{comment_id, author, created, updated, body}]` — все комментарии, новые сверху (по `created`, затем `comment_id`, по убыванию). - `PUT /issues/{key}/note` — заметки (частичное обновление, `updated_by` из тела). - `POST /batches` `{title, author, commands: [...]}` → `{id, estimate_seconds}`; 422 при неизвестном типе, неверных параметрах (в том числе неверного типа значения), временном ключе без `ensure_issue`, двух и более `ensure_issue` с одинаковой `summary` (сравнение после обрезки пробелов по краям; в ответе перечислены повторяющиеся темы). - `GET /batches?limit=`, `GET /batches/{id}` (с командами), `POST /batches/{id}/cancel` — отменяет `queued` команды пакета; ответ — вид пакета с полем `still_claimed`: список `{id, type}` команд пакета в `claimed` (они будут выполнены агентом; остановить — отмена команды с `force`). - `POST /commands/{id}/retry` — новая команда с теми же параметрами в новом пакете «Повтор команды N». - `POST /commands/{id}/cancel` — только для `queued`; для `claimed` — 409, если не передан `force: true`. - `POST /refresh` `{keys | jql | full: true}` → пакет из одного `refresh`. - `GET /agent`, `GET /sync-runs?limit=`, `GET /settings`, `PUT /settings` (частично; поднимает `settings_version`; ответ — все настройки после записи, как у `GET /settings`, включая `settings_version`). #### Scenario: Фильтр по спринту - **WHEN** `GET /issues?sprint=Спринт 2` - **THEN** только задачи с таким `sprint`, отсортированы по сроку, затем по ключу #### Scenario: Отмена взятой команды - **WHEN** `POST /commands/15/cancel` без `force`, команда `claimed` - **THEN** 409 с `claimed_at`; с `force: true` — `cancelled`, следующий отчёт агента по ней принимается, статус не меняется #### Scenario: Поздний отчёт по отменённому созданию - **WHEN** `ensure_issue {temp_key: new:7, …}` отменена с `force: true`, затем агент прислал `done` с `result.key = AURORA-1620` - **THEN** команда `cancelled`, в `result` — `AURORA-1620`; заметка `new:7` перенесена на `AURORA-1620` #### Scenario: Дубль темы в пакете - **WHEN** `POST /batches` с двумя `ensure_issue`, у обеих `summary = "Шахматка: комментарии"` - **THEN** 422 с темой «Шахматка: комментарии», пакет не создан #### Scenario: Тема не строка - **WHEN** `POST /batches` с `ensure_issue {temp_key: new:1, summary: 42, …}` - **THEN** 422 с указанием параметра `summary`, пакет не создан #### Scenario: Отмена пакета с командой у агента - **WHEN** в пакете 9 команда 30 `claimed`, команды 31 и 32 `queued`; `POST /batches/9/cancel` - **THEN** 31 и 32 `cancelled`; в ответе `still_claimed = [{id: 30, type: …}]`; 30 остаётся `claimed` #### Scenario: Ответ на запись настроек - **WHEN** `PUT /settings {relay_dir: "/Volumes/externalSSD/utm/rshb/jira-bridge"}` - **THEN** 200; ответ содержит `relay_dir` с записанным значением, новую `settings_version` и остальные настройки; токенов в ответе нет #### Scenario: Карточка с комментариями и связями - **WHEN** у AURORA-9001 два комментария (14.10 и 15.10), исходящая связь на AURORA-9002 («API свода», «Новая») и на AURORA-1, которой нет в зеркале; эпик AURORA-9100 «Тестовый эпик» - **THEN** `GET /issues/AURORA-9001` отдаёт `comments` с комментарием от 15.10 первым, у связи на AURORA-9002 — `summary = "API свода"`, `status = "Новая"`, у связи на AURORA-1 — `summary = null`, `status = null`; `epic_summary = "Тестовый эпик"`