100 lines
9.9 KiB
Markdown
100 lines
9.9 KiB
Markdown
## MODIFIED Requirements
|
||
|
||
### 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}` — задача целиком: поля, связи в обе стороны, заметки, последние 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` — отменяет `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` и остальные настройки;
|
||
токенов в ответе нет
|
||
|
||
### Requirement: Публикация версии агента
|
||
`jira-hub agent publish PATH` SHALL публиковать файл агента: текст в UTF-8 не больше 1 МБ, ровно одна строка
|
||
`AGENT_VERSION = <целое>`, версия больше опубликованной. Файл SHALL записываться в `<JIRA_HUB_HOME>/release/agent.py`
|
||
(каталог 0700), затем `manifest.json` `{version, sha256, size, published_at}`; оба — через временный файл с `fsync` и
|
||
переименование, манифест последним. Прежний `agent.py` на время публикации SHALL переименовываться в `agent.py.prev`
|
||
и удаляться после записи манифеста; если запись нового файла или манифеста не удалась (`OSError`), хаб SHALL вернуть
|
||
`agent.py.prev` на место `agent.py`, удалить временные файлы и завершить команду сообщением «не удалось записать
|
||
публикацию: <ошибка>» и кодом 1 — опубликованной остаётся прежняя версия. Команда SHALL работать без запущенного
|
||
`serve` и печатать «опубликована v<N>, sha256 <первые 12 символов>…, <size> байт»; при ошибке — сообщение и код 1,
|
||
опубликованное не меняется. Хаб SHALL читать манифест при каждом `claim`, `GET /agent/release` и `GET /agent`; манифест
|
||
годен, только если это объект, `version` — целое число (не логическое) не меньше 1, `sha256` — строка из 64 символов
|
||
`[0-9a-f]`; негодный или нечитаемый манифест означает «публикации нет» и предупреждение в лог — одно на каждый новый
|
||
текст ошибки. Файл `agent.py`, не декодируемый как UTF-8 при совпавшем sha256, означает «публикации нет»
|
||
(`GET /agent/release` — 404 `no_release`). `GET /agent` SHALL дополняться полями `release` (`{version, sha256,
|
||
published_at}` или `null`), `update_state`, `launcher_version`, `agent_reported` (был ли `claim` с момента запуска хаба)
|
||
и `update_hint`: «агент запущен без run.py — обновится только вручную» (опубликована версия выше версии агента, агент
|
||
онлайн, `agent_reported = true`, `launcher_version = null`), «откат с v<to> на v<from>: <error>» (`rolled_back`),
|
||
«v<to> отвергнута: <error>» (`rejected`), иначе `null`.
|
||
|
||
#### Scenario: Публикация новой версии
|
||
- **WHEN** опубликована v5, `jira-hub agent publish jira-agent/src/agent.py` с `AGENT_VERSION = 6`
|
||
- **THEN** в `release/` — новый `agent.py` и манифест с `version = 6`, `agent.py.prev` нет; вывод «опубликована v6,
|
||
sha256 …»; код 0
|
||
|
||
#### Scenario: Версия не выше опубликованной
|
||
- **WHEN** опубликована v6, публикуется файл с `AGENT_VERSION = 6`
|
||
- **THEN** «опубликована v6, новая должна быть больше», код 1; `release/` не изменён
|
||
|
||
#### Scenario: Нет строки версии
|
||
- **WHEN** публикуется файл без строки `AGENT_VERSION = <целое>` или с двумя такими строками
|
||
- **THEN** сообщение об ошибке, код 1; `release/` не изменён
|
||
|
||
#### Scenario: Диск полон при записи манифеста
|
||
- **WHEN** опубликована v8, публикуется v9, запись `manifest.json` завершилась `OSError`
|
||
- **THEN** «не удалось записать публикацию: …», код 1, без трейсбека; `agent.py` и манифест — прежние v8,
|
||
`GET /agent/release` отдаёт v8; временных файлов и `agent.py.prev` в `release/` нет
|
||
|
||
#### Scenario: Негодный манифест
|
||
- **WHEN** в `manifest.json` `version = 6.9` (или `true`, или `sha256` короче 64 символов), хаб получает 100 `claim`
|
||
- **THEN** в `claim` `agent_release = null`, `GET /agent/release` — 404; в логе одно предупреждение о манифесте
|
||
|
||
#### Scenario: Агент без запускателя
|
||
- **WHEN** опубликована v6, агент v5 онлайн, в последнем `claim` `launcher_version = null`
|
||
- **THEN** `GET /agent` → `agent_reported = true`, `update_hint` = «агент запущен без run.py — обновится только вручную»
|
||
|
||
#### Scenario: Хаб перезапущен, агент ещё не выходил на связь
|
||
- **WHEN** опубликована v10, агент v9 под запускателем, хаб перезапущен, `claim` после запуска ещё не было
|
||
- **THEN** `GET /agent` → `agent_reported = false`, `launcher_version = null`, `update_hint = null`; после первого
|
||
`claim` — `agent_reported = true`, `launcher_version` из `claim`
|