Merge pull request 'docs(openspec): архив hub-hygiene-d (#32)' (#38) from chore/32-archive-hub-hygiene-d into main
This commit is contained in:
commit
13e6c167a4
@ -55,13 +55,22 @@
|
||||
### Requirement: Временные ключи и ссылки внутри пакета
|
||||
Параметр-ключ любой команды MAY быть временным `new:<n>`. Хаб SHALL выдавать агенту такую команду только после `done`
|
||||
команды `ensure_issue` с тем же `temp_key` в том же пакете, подставив настоящий ключ. Если `ensure_issue` завершилась
|
||||
`failed` или `cancelled`, зависимые команды SHALL получить `skipped`. Временный ключ вне пакета с его `ensure_issue`
|
||||
SHALL отвергаться при создании пакета (422).
|
||||
`failed` или `cancelled`, зависимые команды SHALL получить `skipped` с ошибкой «создание задачи new:<n> отменено», если
|
||||
`ensure_issue` отменена, иначе «задача new:<n> не создана». Временный ключ вне пакета с его `ensure_issue` SHALL
|
||||
отвергаться при создании пакета (422).
|
||||
|
||||
#### Scenario: Создать и привязать
|
||||
- **WHEN** пакет: `ensure_issue {temp_key: new:60, …}`, `ensure_link {source: new:60, target: AURORA-1537, …}`
|
||||
- **THEN** вторая команда выдаётся агенту с `source = AURORA-1590` после завершения первой
|
||||
|
||||
#### Scenario: Создание отменено
|
||||
- **WHEN** пакет: `ensure_issue {temp_key: new:901, …}` (отменена с `force: true`), `ensure_comment {key: new:901, …}`
|
||||
- **THEN** `ensure_comment` — `skipped` с ошибкой «создание задачи new:901 отменено»
|
||||
|
||||
#### Scenario: Создание упало
|
||||
- **WHEN** `ensure_issue {temp_key: new:902, …}` завершилась `failed`, зависимая `ensure_link {source: new:902, …}`
|
||||
- **THEN** `ensure_link` — `skipped` с ошибкой «задача new:902 не создана»
|
||||
|
||||
### Requirement: Пакет и порядок выполнения
|
||||
Пакет SHALL создаваться одним вызовом с автором, заголовком и списком команд; хаб SHALL назначить `seq` по порядку списка
|
||||
и добавить в хвост `refresh` по всем ключам пакета (кроме пакетов, состоящих только из `refresh`). Хаб SHALL проверять
|
||||
@ -111,15 +120,20 @@ SHALL выдерживать зазор типа команды между за
|
||||
заметку, не меняя статус (как для отменённой, см. `hub-api`).
|
||||
|
||||
**Подтверждение по состоянию.** Если после любого `refresh` желаемый результат команды в `claimed` виден в зеркале, хаб
|
||||
SHALL перевести её в `confirmed_by_state`: `ensure_fields` — все поля совпадают; `ensure_status` — статус равен последнему
|
||||
в пути; `ensure_link` — связь есть и конфликтов нет; `ensure_sprint` — спринт совпадает. `ensure_issue`, `ensure_comment`
|
||||
и `refresh` по состоянию не подтверждаются: потерянный отчёт по ним закрывается повторной выдачей (агент находит
|
||||
созданную задачу поиском дубля) или отменой человеком.
|
||||
SHALL перевести её в `confirmed_by_state`: `ensure_fields` — все поля совпадают (строки сравниваются без различия
|
||||
`\r\n` и `\n`; `null` и пустая строка равны); `ensure_status` — статус равен последнему в пути; `ensure_link` — связь
|
||||
есть и конфликтов нет; `ensure_sprint` — спринт совпадает. `ensure_issue`, `ensure_comment` и `refresh` по состоянию не
|
||||
подтверждаются: потерянный отчёт по ним закрывается повторной выдачей (агент находит созданную задачу поиском дубля)
|
||||
или отменой человеком.
|
||||
|
||||
#### Scenario: Отчёт потерян, факт на месте
|
||||
- **WHEN** `ensure_sprint {key: X, sprint: "Спринт 2"}` в `claimed`, после выполненного `refresh` зеркало показывает X в «Спринт 2»
|
||||
- **THEN** команда `confirmed_by_state`
|
||||
|
||||
#### Scenario: Описание с CRLF в зеркале
|
||||
- **WHEN** `ensure_fields {key: X, description: "a\nb"}` в `claimed`, после `refresh` в зеркале у X `description = "a\r\nb"`
|
||||
- **THEN** команда `confirmed_by_state`
|
||||
|
||||
#### Scenario: Ответ на claim потерян
|
||||
- **WHEN** команда 40 выдана (`deliveries = 1`), ответ до агента не дошёл, агент снова прислал `claim`
|
||||
- **THEN** `claim` отдаёт команду 40, `deliveries = 2`; в очереди есть `queued` команды, но выдаётся 40
|
||||
|
||||
@ -100,7 +100,8 @@ HTTP API хаба `jira-hub serve`: маршруты агента (выдача
|
||||
- `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 /agent`, `GET /sync-runs?limit=`, `GET /settings`, `PUT /settings` (частично; поднимает `settings_version`;
|
||||
ответ — все настройки после записи, как у `GET /settings`, включая `settings_version`).
|
||||
|
||||
#### Scenario: Фильтр по спринту
|
||||
- **WHEN** `GET /issues?sprint=Спринт 2`
|
||||
@ -126,6 +127,11 @@ HTTP API хаба `jira-hub serve`: маршруты агента (выдача
|
||||
- **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, хаб SHALL скопировать файл базы в `backups/<дата-время>-<batch>.sqlite`
|
||||
и хранить последние `backup_keep` копий.
|
||||
@ -237,18 +243,26 @@ HTTP API хаба `jira-hub serve`: маршруты агента (выдача
|
||||
### 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}`; оба — через временный файл и
|
||||
переименование, манифест последним. Команда SHALL работать без запущенного `serve` и печатать «опубликована v<N>,
|
||||
sha256 <первые 12 символов>…, <size> байт»; при ошибке — сообщение и код 1, опубликованное не меняется. Хаб SHALL
|
||||
читать манифест при каждом `claim`, `GET /agent/release` и `GET /agent`; битый манифест означает «публикации нет»
|
||||
(предупреждение в лог). `GET /agent` SHALL дополняться полями `release` (`{version, sha256, published_at}` или `null`),
|
||||
`update_state`, `launcher_version` и `update_hint`: «агент запущен без run.py — обновится только вручную» (опубликована
|
||||
версия выше версии агента, агент онлайн, `launcher_version = null`), «откат с v<to> на v<from>: <error>»
|
||||
(`rolled_back`), «v<to> отвергнута: <error>» (`rejected`), иначе `null`.
|
||||
(каталог 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`; вывод «опубликована v6, sha256 …»; код 0
|
||||
- **THEN** в `release/` — новый `agent.py` и манифест с `version = 6`, `agent.py.prev` нет; вывод «опубликована v6,
|
||||
sha256 …»; код 0
|
||||
|
||||
#### Scenario: Версия не выше опубликованной
|
||||
- **WHEN** опубликована v6, публикуется файл с `AGENT_VERSION = 6`
|
||||
@ -258,9 +272,23 @@ sha256 <первые 12 символов>…, <size> байт»; при ошиб
|
||||
- **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` → `update_hint` = «агент запущен без run.py — обновится только вручную»
|
||||
- **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`
|
||||
|
||||
### Requirement: Раздача веб-приложения
|
||||
Хаб SHALL отдавать собранное веб-приложение (спека `web-app`) из каталога пакета `src/jira_hub/web/app/`, читая файлы
|
||||
|
||||
@ -27,7 +27,7 @@ MCP-сервер хаба (`jira-hub mcp`, stdio) для Claude: инструм
|
||||
| `command_retry` | `id` | новый пакет |
|
||||
| `batch_cancel` | `id` | вид пакета; для команд пакета, уже взятых агентом, — строка «команда #N уже у агента и будет выполнена; остановить — `POST /commands/N/cancel` с `force: true`» |
|
||||
| `refresh` | `keys` или `full`, `wait` (bool) | без `wait` — строка с id поставленного пакета; с `wait` — итог пакета, как у `batch_wait` |
|
||||
| `agent_status` | — | онлайн/офлайн, версия, текущая команда, довыданные команды (id, тип, число выдач), 429 за сутки, версия настроек; публикация агента («публикация: v<N> от <дата>» или «публикации нет»), состояние обновления («обновление: <state> v<from> → v<to>»), запускатель («запускатель: v<N>» или «без запускателя»), `update_hint`, если он есть |
|
||||
| `agent_status` | — | онлайн/офлайн, версия, текущая команда, довыданные команды (id, тип, число выдач), 429 за сутки, версия настроек; публикация агента («публикация: v<N> от <дата>» или «публикации нет»), состояние обновления («обновление: <state> v<from> → v<to>»), запускатель («запускатель: v<N>», «без запускателя» или, пока `agent_reported = false`, «запускатель: неизвестно (агент ещё не выходил на связь после перезапуска хаба)»), `update_hint`, если он есть |
|
||||
| `sync_log` | `limit` | последние `sync_run` |
|
||||
| `settings_get`, `settings_set` | ключ, значение | текущее значение / новая `settings_version` |
|
||||
|
||||
@ -56,3 +56,8 @@ MCP-сервер хаба (`jira-hub mcp`, stdio) для Claude: инструм
|
||||
- **WHEN** опубликована v7, агент v6 под запускателем v1 прислал `update_state = rolled_back` для v7
|
||||
- **THEN** ответ `agent_status` содержит «публикация: v7 от …», «обновление: rolled_back v6 → v7», «запускатель: v1» и
|
||||
«откат с v7 на v6: …»
|
||||
|
||||
#### Scenario: Статус агента сразу после перезапуска хаба
|
||||
- **WHEN** хаб перезапущен, агент ещё не прислал `claim`
|
||||
- **THEN** ответ `agent_status` содержит «запускатель: неизвестно (агент ещё не выходил на связь после перезапуска
|
||||
хаба)» и не содержит «без запускателя» и «агент запущен без run.py»
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user