docs(openspec): архив bridge-reliability, спеки довыдачи и миграции v2 в openspec/specs (#9)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
fa26faf5b4
commit
e744ef1212
@ -1,7 +1,7 @@
|
||||
# jira-hub
|
||||
|
||||
## Что это
|
||||
Хаб моста Jira для АРМ КА: единственный источник истины по задачам проекта AURORA (метка AWPCA) на Mac mini (`192.168.0.106`). Хранит зеркало задач, служебные заметки и очередь команд в SQLite, отдаёт HTTP API в локальной сети (агенту на виртуалке `jira-agent` и интерфейсам) и MCP-сервер для Claude. Главный сценарий: из чата поставить пакет команд, агент на виртуалке выполняет их в Jira по правилу «проверь, потом сделай» и возвращает состояние задач в зеркало. Спеки: `openspec/specs/`; текущий change — `openspec/changes/bridge-reliability/`.
|
||||
Хаб моста Jira для АРМ КА: единственный источник истины по задачам проекта AURORA (метка AWPCA) на Mac mini (`192.168.0.106`). Хранит зеркало задач, служебные заметки и очередь команд в SQLite, отдаёт HTTP API в локальной сети (агенту на виртуалке `jira-agent` и интерфейсам) и MCP-сервер для Claude. Главный сценарий: из чата поставить пакет команд, агент на виртуалке выполняет их в Jira по правилу «проверь, потом сделай» и возвращает состояние задач в зеркало. Спеки: `openspec/specs/`; текущего change нет (последний — `openspec/changes/archive/2026-10-13-bridge-reliability/`).
|
||||
|
||||
## Стек и ограничения
|
||||
- Python 3.13, uv, hatchling, pytest; HTTP на stdlib (`http.server`), БД — SQLite (stdlib), MCP — пакет `mcp` (закреплён `<2`, рулинг R-10).
|
||||
|
||||
@ -25,34 +25,53 @@ TBD - created by archiving change bridge-core. Update Purpose after archive.
|
||||
### Requirement: Цикл выполнения
|
||||
Агент SHALL работать одним процессом: `claim` к хабу (долгий запрос) → при получении команды: досыпать до
|
||||
`pause_seconds` от последнего вызова Jira → запись «started» в журнал → выполнение по контракту `commands` → запись
|
||||
«finished» с результатом → отчёт хабу, повторяемый до подтверждения. Одна команда в полёте. При изменении
|
||||
`settings_version` в ответе `claim` агент SHALL перечитать настройки до выполнения команды.
|
||||
«finished» с результатом → отчёт хабу, повторяемый до подтверждения. Одна команда в полёте: новый `claim` агент SHALL
|
||||
отправлять, только когда у него нет выданной, но не завершённой и не отчитанной команды (на этом хаб строит повторную
|
||||
выдачу, см. `commands`). Один процесс на журнал: `run` SHALL брать исключительную блокировку файла `<журнал>.lock` и,
|
||||
если она занята, завершаться с кодом 1 и сообщением «агент уже запущен». При изменении `settings_version` в ответе `claim` агент SHALL перечитать настройки до выполнения
|
||||
команды.
|
||||
|
||||
#### Scenario: Остановка между командами
|
||||
- **WHEN** получен Ctrl-C во время выполнения команды
|
||||
- **THEN** текущая команда доделывается и отчёт отправляется, затем процесс завершается
|
||||
|
||||
#### Scenario: Второй процесс с тем же журналом
|
||||
- **WHEN** агент работает, и запускается ещё один `agent.py run` с тем же `journal.jsonl`
|
||||
- **THEN** второй процесс пишет «агент уже запущен…» и завершается с кодом 1, не обращаясь к хабу и Jira
|
||||
|
||||
### Requirement: Журнал на диске и восстановление
|
||||
Агент SHALL вести `journal.jsonl` (только дозапись) рядом с собой: строки `started {command_id, at}`, `finished {command_id,
|
||||
status, result, error, jira_calls, rate_limited}`, `acked {command_id}`. При старте агент SHALL: отправить хабу все
|
||||
`finished` без `acked`; для `started` без `finished` — выполнить команду заново по правилу «проверь, потом сделай»
|
||||
(запросив её параметры у хаба по id). Команда `python3 agent.py prune` SHALL удалять из журнала записи с `acked` старше
|
||||
30 дней.
|
||||
Агент SHALL вести `journal.jsonl` (только дозапись) рядом с собой: строки `claimed {command_id, at}` (сразу после
|
||||
получения команды, до паузы), `started {command_id, at}`, `finished {command_id, status, result, error, jira_calls,
|
||||
rate_limited, at}`, `acked {command_id, at}`. При старте агент SHALL: отправить хабу все `finished` без `acked`; для
|
||||
`claimed`/`started` без `finished` — запросить команду у хаба по id и, если она в хабе всё ещё `claimed`, выполнить её
|
||||
заново по правилу «проверь, потом сделай» с паузой `pause_seconds` из ответа хаба, не записывая `claimed` повторно.
|
||||
Команда `python3 agent.py prune` SHALL удалять из журнала записи с `acked` старше 30 дней.
|
||||
|
||||
#### Scenario: Падение после ответа Jira
|
||||
- **WHEN** агент упал между `POST /issue` и записью `finished`, затем перезапущен
|
||||
- **THEN** агент находит в журнале `started` без `finished`, запрашивает команду у хаба, находит созданную задачу поиском
|
||||
дубля и отчитывается `done` с её ключом; второй задачи нет
|
||||
|
||||
#### Scenario: Восстановление без лишней записи
|
||||
- **WHEN** в журнале `claimed 40` без `finished`, хаб отдаёт команду 40 в `claimed` с `pause_seconds = 30`
|
||||
- **THEN** в журнал дописываются `started 40`, `finished 40`, `acked 40`; второй строки `claimed 40` нет; перед выполнением
|
||||
агент выдерживает 30 с от последнего вызова Jira
|
||||
|
||||
### Requirement: Ограничение темпа и 429
|
||||
Агент SHALL помнить время последнего вызова Jira (любого) и не начинать команду раньше `pause_seconds` после него.
|
||||
На HTTP 429 и 503 агент SHALL ждать `Retry-After` (иначе 60 с × номер попытки) и повторять тот же запрос до 5 раз,
|
||||
считая попытки в `rate_limited`; после 5 неудач — `failed`. Пауза между записями внутри одной команды — по зазору типа команды.
|
||||
Агент SHALL помнить время последнего вызова Jira (любого) и не начинать команду раньше `pause_seconds` после него. При
|
||||
старте агент SHALL принять за время последнего вызова Jira самое позднее `at` в журнале (если оно не в будущем); при пустом
|
||||
журнале паузы перед первой командой нет. На HTTP 429 и 503 агент SHALL ждать `Retry-After` (иначе 60 с × номер попытки)
|
||||
и повторять тот же запрос до 5 раз, считая попытки в `rate_limited`; после 5 неудач — `failed`. Пауза между записями
|
||||
внутри одной команды — по зазору типа команды.
|
||||
|
||||
#### Scenario: 429 с Retry-After
|
||||
- **WHEN** Jira ответила 429 с `Retry-After: 90`
|
||||
- **THEN** агент ждёт 90 с, повторяет тот же запрос; в отчёте `rate_limited = 1`
|
||||
|
||||
#### Scenario: Перезапуск не сбивает темп
|
||||
- **WHEN** последняя запись журнала `finished` сделана 10 с назад, агент перезапущен и получил команду с `pause_seconds = 30`
|
||||
- **THEN** агент ждёт около 20 с перед первым вызовом Jira по команде
|
||||
|
||||
### Requirement: Чтение состояния
|
||||
`refresh` SHALL выполняться запросом `search` (`jql`, `fields` = поля зеркала включая `issuelinks` и спринт,
|
||||
`maxResults = 100`, постранично) и отправляться хабу пачками по 100 задач со связями; ключи из `issuelinks`, которых нет
|
||||
@ -66,8 +85,8 @@ status, result, error, jira_calls, rate_limited}`, `acked {command_id}`. При
|
||||
### Requirement: Доставка и запуск
|
||||
README агента SHALL описывать: копирование `agent.py` и `agent.example.json` через общую папку, заполнение `agent.json`
|
||||
(включая выбор транспорта в `hub_url`), `check`, запуск в терминале и через `nohup` с логом, опциональный launchd-plist,
|
||||
`prune`. Версия агента SHALL быть в константе файла (2 — с транспортом через папку) и в каждом отчёте; хаб предупреждает
|
||||
в `agent_status`, если версия ниже минимальной из настроек.
|
||||
`prune`. Версия агента SHALL быть в константе файла (3 — с восстановлением темпа после перезапуска и блокировкой одного процесса) и в каждом отчёте;
|
||||
хаб предупреждает в `agent_status`, если версия ниже минимальной из настроек.
|
||||
|
||||
#### Scenario: Устаревший агент
|
||||
- **WHEN** агент версии 1 выходит на связь, а в настройках `min_agent_version = 2`
|
||||
|
||||
@ -56,14 +56,19 @@ SHALL отвергаться при создании пакета (422).
|
||||
|
||||
### Requirement: Пакет и порядок выполнения
|
||||
Пакет SHALL создаваться одним вызовом с автором, заголовком и списком команд; хаб SHALL назначить `seq` по порядку списка
|
||||
и добавить в хвост `refresh` по всем ключам пакета (кроме пакетов, состоящих только из `refresh`). Очередь SHALL
|
||||
выдавать команды в порядке `(batch.created_at, seq)`, одну в полёте. Пакет и его `queued` команды MAY быть отменены;
|
||||
`claimed` команда MUST NOT отменяться автоматически.
|
||||
и добавить в хвост `refresh` по всем ключам пакета (кроме пакетов, состоящих только из `refresh`). Пакет с двумя и более
|
||||
`ensure_issue` с одинаковой `summary` (после обрезки пробелов по краям) SHALL отвергаться (422). Очередь SHALL выдавать
|
||||
команды в порядке `(batch.created_at, seq)`, одну в полёте. Пакет и его `queued` команды MAY быть отменены; `claimed`
|
||||
команда MUST NOT отменяться автоматически.
|
||||
|
||||
#### Scenario: Оценка длительности
|
||||
- **WHEN** создаётся пакет из 3 `ensure_fields` и 2 `ensure_comment`
|
||||
- **THEN** `estimate_seconds` = сумма пауз по политике для последовательности типов + пауза `refresh`
|
||||
|
||||
#### Scenario: Две задачи с одной темой
|
||||
- **WHEN** пакет содержит `ensure_issue {temp_key: new:1, summary: "Отчёт"}` и `ensure_issue {temp_key: new:2, summary: " Отчёт "}`
|
||||
- **THEN** 422 с темой «Отчёт», пакет не создан
|
||||
|
||||
### Requirement: Политика темпа и пауза на команде
|
||||
Хаб SHALL хранить `pacing` как минимальные зазоры в секундах по типу команды (по умолчанию: `ensure_issue` и
|
||||
`ensure_fields` 120, `ensure_comment`, `ensure_status`, `ensure_link`, `ensure_sprint` 30, `refresh` 2). При выдаче команды
|
||||
@ -76,14 +81,35 @@ SHALL выдерживать зазор типа команды между за
|
||||
- **THEN** `claim` возвращает пустой ответ, агент не спит сверх долгого запроса
|
||||
|
||||
### Requirement: Ошибки и подтверждение по состоянию
|
||||
Ошибка команды SHALL записываться в `error` (HTTP-код, текст Jira до 1500 символов), пакет продолжается. Команда в
|
||||
`claimed` без ответа SHALL показываться как «нет ответа от агента с <claimed_at>». Если после любого `refresh` желаемый
|
||||
результат такой команды виден в зеркале (для `ensure_issue` — задача с той же темой от нас; `ensure_fields` — все поля
|
||||
совпадают; `ensure_status` — статус равен последнему в пути; `ensure_link` — связь есть и конфликтов нет; `ensure_sprint` —
|
||||
спринт совпадает), хаб SHALL перевести её в `confirmed_by_state`. Для `ensure_comment` и `refresh` подтверждения по
|
||||
состоянию нет: комментарии в зеркале не хранятся, такая команда ждёт отчёта агента или отмены человеком. Автоматических повторов хаб
|
||||
MUST NOT делать; повтор — новая команда с теми же параметрами по запросу интерфейса.
|
||||
Ошибка команды SHALL записываться в `error` (HTTP-код, текст Jira до 1500 символов), пакет продолжается.
|
||||
|
||||
**Повторная выдача.** Агент запрашивает новую команду, только когда у него нет незавершённой (см. `agent`), поэтому
|
||||
команда в `claimed` к моменту следующего `claim` считается не доставленной или потерянной агентом. При `claim` хаб SHALL
|
||||
сначала выдать снова самую старую (по `claimed_at`, затем `id`) команду в `claimed`: тот же `id` и параметры,
|
||||
`pause_seconds` по текущей политике темпа, новый `claimed_at`, `deliveries + 1`. Если у неё уже `deliveries >= 3`, хаб SHALL
|
||||
перевести её в `failed` с `error = "агент не отчитался после 3 выдач"` и перейти к следующей (довыдача другой `claimed`
|
||||
или следующая `queued`). Довыдача — доставка той же выдачи, а не повтор команды: новых команд хаб MUST NOT создавать
|
||||
сам, повтор после `failed` — новая команда с теми же параметрами по запросу интерфейса.
|
||||
|
||||
**Подтверждение по состоянию.** Если после любого `refresh` желаемый результат команды в `claimed` виден в зеркале, хаб
|
||||
SHALL перевести её в `confirmed_by_state`: `ensure_fields` — все поля совпадают; `ensure_status` — статус равен последнему
|
||||
в пути; `ensure_link` — связь есть и конфликтов нет; `ensure_sprint` — спринт совпадает. `ensure_issue`, `ensure_comment`
|
||||
и `refresh` по состоянию не подтверждаются: потерянный отчёт по ним закрывается повторной выдачей (агент находит
|
||||
созданную задачу поиском дубля) или отменой человеком.
|
||||
|
||||
#### Scenario: Отчёт потерян, факт на месте
|
||||
- **WHEN** `ensure_sprint {key: X, sprint: "Спринт 2"}` в `claimed` 20 минут, агент перезапущен, `refresh` показал X в «Спринт 2»
|
||||
- **THEN** команда `confirmed_by_state`, следующая команда очереди выдаётся
|
||||
- **WHEN** `ensure_sprint {key: X, sprint: "Спринт 2"}` в `claimed`, после выполненного `refresh` зеркало показывает X в «Спринт 2»
|
||||
- **THEN** команда `confirmed_by_state`
|
||||
|
||||
#### Scenario: Ответ на claim потерян
|
||||
- **WHEN** команда 40 выдана (`deliveries = 1`), ответ до агента не дошёл, агент снова прислал `claim`
|
||||
- **THEN** `claim` отдаёт команду 40, `deliveries = 2`; в очереди есть `queued` команды, но выдаётся 40
|
||||
|
||||
#### Scenario: Предел выдач
|
||||
- **WHEN** команда 41 в `claimed` с `deliveries = 3`, агент снова прислал `claim`, в очереди есть команда 42
|
||||
- **THEN** 41 — `failed` «агент не отчитался после 3 выдач»; `claim` отдаёт 42
|
||||
|
||||
#### Scenario: Потерянный отчёт о создании задачи
|
||||
- **WHEN** `ensure_issue {temp_key: new:5, summary: "Отчёт"}` выполнена агентом, отчёт не дошёл, агент прислал `claim`
|
||||
- **THEN** команда выдаётся снова; агент находит созданную задачу поиском дубля и отчитывается `done` с её ключом; второй
|
||||
задачи нет, заметка `new:5` перенесена на ключ
|
||||
|
||||
@ -8,12 +8,19 @@ TBD - created by archiving change bridge-core. Update Purpose after archive.
|
||||
### Requirement: Схема базы хаба
|
||||
База хаба SHALL быть одним файлом SQLite (WAL) с таблицами `issue`, `issue_link`, `issue_note`, `batch`, `command`,
|
||||
`sync_run`, `agent_status`, `setting`. Схема SHALL создаваться и мигрироваться самим хабом при старте по номеру версии
|
||||
в `setting`. Денежных и дробных полей нет; даты — ISO-8601 строки в UTC, кроме `duedate` и плановых дат (даты без времени).
|
||||
в `setting.schema_version` (текущая — 2). Миграция существующей базы SHALL выполняться в одной транзакции; перед ней
|
||||
хаб SHALL скопировать файл базы в `backups/<дата-время>-schema-v<старая>.sqlite` рядом с базой. Денежных и дробных
|
||||
полей нет; даты — ISO-8601 строки в UTC, кроме `duedate` и плановых дат (даты без времени).
|
||||
|
||||
#### Scenario: Первый запуск
|
||||
- **WHEN** хаб стартует с несуществующим файлом базы
|
||||
- **THEN** файл создаётся, таблицы и индексы на месте, `setting.schema_version` = текущая
|
||||
|
||||
#### Scenario: Миграция базы v1
|
||||
- **WHEN** хаб стартует с базой `schema_version = 1`, в которой есть задачи, заметки и команда в `claimed`
|
||||
- **THEN** в `backups/` копия базы v1; добавлена `command.deliveries` (у `claimed` — 1, у остальных — 0); данные на месте;
|
||||
`schema_version = 2`
|
||||
|
||||
### Requirement: Зеркало задачи
|
||||
Таблица `issue` SHALL хранить задачу Jira как есть: `key` (PK), `id`, `issue_type`, `summary`, `description` (wiki),
|
||||
`status`, `priority`, `assignee`, `reporter`, `duedate`, `planned_start`, `planned_end`, `epic_key`, `labels` (JSON-список),
|
||||
@ -35,7 +42,7 @@ SHALL хранить связи `(source_key, target_key, link_type)` с нап
|
||||
Таблица `issue_note` SHALL хранить поля, которых нет в Jira: `key` (PK; ключ Jira или временный `new:<n>`),
|
||||
`fact_status`, `work_kind`, `done_text`, `rest_text`, `owner_note`, `updated_at`, `updated_by`. Обновление зеркала
|
||||
MUST NOT менять `issue_note`. При завершении `ensure_issue` с временным ключом хаб SHALL переименовать строку заметок
|
||||
на настоящий ключ.
|
||||
на настоящий ключ — в том числе по позднему отчёту `done` с ключом о команде, отменённой до отчёта.
|
||||
|
||||
#### Scenario: Заметка переживает refresh
|
||||
- **WHEN** у задачи есть `issue_note.fact_status = "В работе"` и приходит полное обновление зеркала
|
||||
@ -44,9 +51,10 @@ MUST NOT менять `issue_note`. При завершении `ensure_issue`
|
||||
### Requirement: Пакеты и команды
|
||||
Таблица `batch` SHALL хранить `id`, `title`, `author`, `created_at`, `cancelled_at`, `estimate_seconds`.
|
||||
Таблица `command` SHALL хранить `id`, `batch_id`, `seq`, `type`, `params` (JSON), `status`
|
||||
(`queued | claimed | done | failed | skipped | cancelled | confirmed_by_state`), `claimed_at`, `finished_at`, `result` (JSON),
|
||||
`error`, `jira_calls`, `rate_limited` (число 429/503), `pause_seconds` (выданная пауза), `depends_on` (id команды,
|
||||
чей результат подставляется). Статус пакета SHALL вычисляться из команд, не храниться.
|
||||
(`queued | claimed | done | failed | skipped | cancelled | confirmed_by_state`), `claimed_at` (время последней выдачи),
|
||||
`deliveries` (сколько раз команда выдана агенту), `finished_at`, `result` (JSON), `error`, `jira_calls`, `rate_limited`
|
||||
(число 429/503), `pause_seconds` (выданная пауза), `depends_on` (id команды, чей результат подставляется). Статус пакета
|
||||
SHALL вычисляться из команд, не храниться.
|
||||
|
||||
#### Scenario: Статус пакета
|
||||
- **WHEN** в пакете 3 команды: `done`, `claimed`, `queued`
|
||||
@ -55,11 +63,13 @@ MUST NOT менять `issue_note`. При завершении `ensure_issue`
|
||||
### Requirement: Журнал синхронизаций и статус агента
|
||||
Таблица `sync_run` SHALL хранить каждую загрузку состояния: `started_at`, `finished_at`, `kind` (`keys | jql`), `query`,
|
||||
`issues_count`, `links_count`, `jira_calls`, `rate_limited`, `command_id`. Таблица `agent_status` (одна строка) SHALL
|
||||
хранить `last_seen_at`, `agent_version`, `settings_version`, `current_command_id`, `last_jira_call_at`.
|
||||
хранить `last_seen_at`, `agent_version`, `settings_version`, `current_command_id`, `last_jira_call_at`. Через API и MCP
|
||||
`agent_status` SHALL отдаваться с признаком `online` (истина, если `last_seen_at` моложе 5 минут) и списком
|
||||
`redelivered` — команд в `claimed`, выданных больше одного раза (`id`, `type`, `claimed_at`, `deliveries`).
|
||||
|
||||
#### Scenario: Агент давно не выходил на связь
|
||||
- **WHEN** `last_seen_at` старше 5 минут
|
||||
- **THEN** `agent_status` через API и MCP отдаётся с признаком `offline`
|
||||
- **THEN** `agent_status` через API и MCP отдаётся с `online = false`
|
||||
|
||||
### Requirement: Настройки
|
||||
Таблица `setting` SHALL хранить парами ключ–JSON: `base_jql`, `field_ids`, `issue_types`, `priority_map`, `link_types`,
|
||||
|
||||
@ -19,10 +19,14 @@ TBD - created by archiving change bridge-core. Update Purpose after archive.
|
||||
Хаб 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 (для восстановления из журнала).
|
||||
- `GET /agent/commands/{id}` — команда по id (для восстановления из журнала), включая `pause_seconds`.
|
||||
- `POST /agent/commands/{id}/result` `{status, result, error, jira_calls, rate_limited}` — идемпотентно: повторный
|
||||
отчёт по команде в терминальном статусе отвечает 200 без изменений.
|
||||
отчёт по команде в терминальном статусе отвечает 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` — ключи зеркала (для определения соседей).
|
||||
@ -35,6 +39,10 @@ TBD - created by archiving change bridge-core. Update Purpose after archive.
|
||||
- **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`,
|
||||
@ -43,7 +51,8 @@ TBD - created by archiving change bridge-core. Update Purpose after archive.
|
||||
- `GET /issues/{key}` — задача целиком: поля, связи в обе стороны, заметки, последние 10 команд по ней.
|
||||
- `PUT /issues/{key}/note` — заметки (частичное обновление, `updated_by` из тела).
|
||||
- `POST /batches` `{title, author, commands: [...]}` → `{id, estimate_seconds}`; 422 при неизвестном типе, неверных
|
||||
параметрах, временном ключе без `ensure_issue`.
|
||||
параметрах, временном ключе без `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`.
|
||||
@ -56,7 +65,15 @@ TBD - created by archiving change bridge-core. Update Purpose after archive.
|
||||
|
||||
#### Scenario: Отмена взятой команды
|
||||
- **WHEN** `POST /commands/15/cancel` без `force`, команда `claimed`
|
||||
- **THEN** 409 с `claimed_at`; с `force: true` — `cancelled`, следующий отчёт агента по ней принимается, но статус не меняется
|
||||
- **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`
|
||||
@ -68,12 +85,16 @@ TBD - created by archiving change bridge-core. Update Purpose after archive.
|
||||
|
||||
### Requirement: Автообновление зеркала
|
||||
Если `auto_refresh_minutes > 0`, агент на связи и очередь пуста дольше этого интервала, хаб SHALL поставить пакет
|
||||
«Автообновление» из `refresh {jql: base_jql}`.
|
||||
«Автообновление» из `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 с, включение и выключение не требуют перезапуска. Если
|
||||
|
||||
@ -26,7 +26,7 @@ TBD - created by archiving change bridge-core. Update Purpose after archive.
|
||||
| `batches` | `limit` | последние пакеты со статусом |
|
||||
| `command_retry`, `batch_cancel` | `id` | новый пакет / результат отмены |
|
||||
| `refresh` | `keys` или `full`, `wait` (bool) | `sync_run` или id пакета |
|
||||
| `agent_status` | — | онлайн/офлайн, версия, текущая команда, 429 за сутки, версия настроек |
|
||||
| `agent_status` | — | онлайн/офлайн, версия, текущая команда, довыданные команды (id, тип, число выдач), 429 за сутки, версия настроек |
|
||||
| `sync_log` | `limit` | последние `sync_run` |
|
||||
| `settings_get`, `settings_set` | ключ, значение | текущее значение / новая `settings_version` |
|
||||
|
||||
@ -39,3 +39,7 @@ TBD - created by archiving change bridge-core. Update Purpose after archive.
|
||||
#### Scenario: Ожидание результата
|
||||
- **WHEN** `batch_wait {id, timeout_minutes: 10}` и пакет завершился через 6 минут
|
||||
- **THEN** ответ через 6 минут со статусами всех команд
|
||||
|
||||
#### Scenario: Статус агента с довыдачей
|
||||
- **WHEN** агент на связи, команда 40 выдана дважды и ещё без отчёта
|
||||
- **THEN** ответ `agent_status` содержит «агент онлайн» и строку о команде 40 с числом выдач 2
|
||||
|
||||
@ -20,12 +20,19 @@ MUST NOT переноситься (они у агента).
|
||||
`jira-hub import <папка admin/jira-sync>` SHALL прочитать `tasks.csv`, `new_tasks.csv`, `epics.csv` и заполнить
|
||||
`issue_note` (ключ Jira; для строк без ключа — `new:<номер строки>`): статус по факту, вид работы, что сделано, что
|
||||
осталось, внутренние колонки. `links.csv` SHALL импортироваться как сверочный набор в отчёт импорта (ожидаемые связи
|
||||
против зеркала после первого `refresh`), не как команды. Импорт идемпотентен: повтор обновляет заметки, не дублирует.
|
||||
против зеркала после первого `refresh`), не как команды. Связь, которой в зеркале нет как `source→target` того же типа,
|
||||
но есть как `target→source` того же типа, SHALL попадать в `reversed_links`, а не в `missing_links`. Импорт идемпотентен:
|
||||
повтор обновляет заметки, не дублирует.
|
||||
|
||||
#### Scenario: Импорт и первая синхронизация
|
||||
- **WHEN** выполнены `init`, `import`, запущены хаб и агент, `refresh full` завершён
|
||||
- **THEN** число задач зеркала ≥ числа строк `tasks.csv` + созданных из `new_tasks.csv`; отчёт импорта перечисляет ключи
|
||||
из CSV, которых нет в зеркале, и связи из `links.csv`, которых нет в `issue_link`
|
||||
из CSV, которых нет в зеркале, связи из `links.csv`, которых нет в `issue_link`, и связи, которые есть в обратную сторону
|
||||
|
||||
#### Scenario: Связь в обратную сторону
|
||||
- **WHEN** в `links.csv` строка «AURORA-1445 → AURORA-1446, связанные», а в зеркале есть только «ASUPR_Связанные»
|
||||
1446→1445
|
||||
- **THEN** связь в `reversed_links`, в `missing_links` её нет
|
||||
|
||||
### Requirement: Что остаётся в admin/
|
||||
После ввода моста `admin/jira-sync/README.md` SHALL получить в начале пометку «заменено мостом jira-hub/jira-agent,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user