2026-10-13 23:17:32 +04:00

140 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# hub-api Specification
## Purpose
TBD - created by archiving change bridge-core. Update Purpose after archive.
## Requirements
### Requirement: Процесс и доступ
`jira-hub serve` SHALL запускать один процесс: HTTP на `0.0.0.0:<port>` (по умолчанию 8790), база и настройки в
`~/.local/share/jira-hub/` (переопределяется `JIRA_HUB_HOME`). Все маршруты SHALL требовать `Authorization: Bearer <token>`:
агентский токен открывает только `/agent/*`, клиентский — всё остальное. Ответы — JSON, ошибки — `{error, detail}` с
кодами 400/401/404/409/422.
#### Scenario: Чужой токен
- **WHEN** агентским токеном вызван `GET /issues`
- **THEN** 401
### Requirement: Маршруты агента
Хаб SHALL предоставлять агенту следующие маршруты (все под агентским токеном):
- `POST /agent/claim` `{agent_version, settings_version, last_jira_call_at}` — долгий запрос до 30 с; ответ `{command:
{id, type, params, pause_seconds}, settings_version}` или `{command: null, settings_version}`; обновляет `agent_status`.
Если есть команда в `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`, заметка `new:N`
переносится на ключ). Отчёт по команде в `queued` SHALL отвергаться 409 `not_claimed`.
- `POST /agent/state` `{command_id, issues: [...], links: [...], neighbor_keys: [...], page, last_page}` — загрузка состояния
пачками; `neighbor_keys` — ключи вне базовой JQL, дочитанные по связям; хаб обновляет зеркало и `sync_run`.
- `GET /agent/known-keys` — ключи зеркала (для определения соседей).
#### 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`
### Requirement: Маршруты интерфейсов
Хаб SHALL предоставлять интерфейсам (MCP, позже просмотрщик) следующие маршруты под клиентским токеном:
- `GET /issues` с фильтрами `sprint`, `assignee`, `work_kind`, `fact_status`, `status`, `label`, `epic`, `neighbor`,
`q` (подстрока темы), `limit`/`offset` — компактные карточки (ключ, тема, статус, приоритет, срок, исполнитель, спринт,
эпик, заметки кратко).
- `GET /issues/{key}` — задача целиком: поля, связи в обе стороны, заметки, последние 10 команд по ней.
- `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`.
- `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`).
#### 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 с темой «Шахматка: комментарии», пакет не создан
### Requirement: Резервные копии
Перед выдачей первой команды пакета, содержащего записи в Jira, хаб SHALL скопировать файл базы в `backups/<дата-время>-<batch>.sqlite`
и хранить последние `backup_keep` копий.
#### Scenario: Ротация
- **WHEN** копий больше `backup_keep`
- **THEN** самые старые удаляются до `backup_keep`
### Requirement: Автообновление зеркала
Если `auto_refresh_minutes > 0`, агент на связи и очередь пуста дольше этого интервала, хаб SHALL поставить пакет
«Автообновление» из `refresh {jql: base_jql}`. Очередь не пуста, если есть команды в `queued` или `claimed`.
#### Scenario: Очередь занята
- **WHEN** интервал прошёл, но в очереди есть `queued` команды
- **THEN** автообновление не ставится до опустошения очереди
#### Scenario: Есть выданная команда без отчёта
- **WHEN** интервал прошёл, `queued` нет, одна команда в `claimed`
- **THEN** автообновление не ставится; команда довыдаётся при следующем `claim`
### Requirement: Реле через общую папку
`jira-hub serve` SHALL запускать поток реле вместе с HTTP-сервером. Пока настройка `relay_dir` пуста (`null`), реле
бездействует; настройку оно SHALL перечитывать не реже раза в 5 с, включение и выключение не требуют перезапуска. Если
каталог `relay_dir` существует, реле SHALL создать в нём подкаталоги `req/`, `resp/`, `work/`; если не существует —
записать одно предупреждение в лог и проверять снова.
Обмен:
- запрос агента — файл `req/<id>.json`: `{id, method, path, body, authorization, deadline}`, где `deadline` — момент UTC
(ISO 8601), после которого ответ не нужен; файлы, имя которых начинается с точки, реле MUST NOT читать;
- реле SHALL брать запрос переименованием в `work/<id>.json` и обрабатывать каждый взятый запрос в отдельном потоке;
- запрос с истёкшим `deadline` SHALL выбрасываться без ответа; `POST /agent/claim` SHALL пересылаться, только если до
`deadline` осталось не меньше 35 с, иначе выбрасываться без ответа;
- остальные запросы реле SHALL пересылать в свой HTTP на `127.0.0.1:<port>` тем же методом, путём и телом, с заголовком
`Authorization` из файла, и записывать ответ `resp/<id>.json` = `{id, status, body}` (тело — строка ответа HTTP) через
временный файл с именем на точку и переименование; затем удалять `work/<id>.json`;
- если соединение с `127.0.0.1:<port>` не удалось, ответ SHALL иметь `status = 502` и текст ошибки в `body`;
- нечитаемый JSON запроса SHALL перечитываться до 2 с, затем запрос выбрасывается с записью в лог.
При старте реле SHALL удалить файлы из `work/` с записью в лог. Раз в минуту реле SHALL удалять ответы в `resp/` старше
10 мин, запросы в `req/` с `deadline` старше 60 с и файлы с именем на точку старше 10 мин. На каждый обработанный запрос
реле SHALL писать в лог хаба строку `relay <METHOD> <path> <status> <секунды>`. HTTP API, обработчики и проверка токенов
при этом не меняются.
#### Scenario: Запрос через папку
- **WHEN** в `req/` появился `a1.json` с `GET /agent/settings`, агентским токеном и `deadline` через 60 с
- **THEN** в `resp/` появляется `a1.json` со `status = 200` и телом настроек; `work/a1.json` удалён
#### Scenario: Чужой токен через папку
- **WHEN** в файле запроса `GET /issues` с агентским токеном
- **THEN** ответ со `status = 401`, как по HTTP
#### Scenario: Просроченный claim
- **WHEN** реле взяло `POST /agent/claim`, до `deadline` которого осталось 20 с
- **THEN** запрос выброшен без ответа и без обращения к хабу; команда в очереди осталась `queued`
#### Scenario: Долгий claim не задерживает другие запросы
- **WHEN** реле пересылает `claim`, который ждёт команду 30 с, и в это время в `req/` появляется `GET /agent/settings`
- **THEN** ответ на `settings` пишется, не дожидаясь `claim`
#### Scenario: Папка не подключена
- **WHEN** `relay_dir` задан, но каталога нет; через минуту каталог появился
- **THEN** в логе одно предупреждение; после появления каталога реле создаёт подкаталоги и обрабатывает запросы