81 lines
7.0 KiB
Markdown
81 lines
7.0 KiB
Markdown
## ADDED Requirements
|
||
|
||
### Requirement: Приём журнала изменений
|
||
Хаб SHALL принимать от агента журнал изменений задач маршрутом `POST /agent/history` (агентский токен) с телом
|
||
`{command_id, changes: [...]}`, элемент — `{history_id, seq, key, created, author, field_id, field_name, from,
|
||
from_string, to, to_string}`. До записи хаб SHALL проверить форму всей пачки: `changes` — список объектов;
|
||
`history_id`, `key`, `created`, `field_name` — непустые строки; `seq` — целое ≥ 0; `author`, `field_id`, `from`,
|
||
`from_string`, `to`, `to_string` — строка или `null`. Нарушение — 422 `bad_history` с номером элемента и именем поля,
|
||
пачка не применяется. Принятая пачка записывается в `issue_change` в одной транзакции (см. `data-model`, «Журнал
|
||
изменений задач»); ответ — `{accepted, duplicates}` (записано новых строк, отброшено повторов). Задача элемента может
|
||
отсутствовать в зеркале — элемент всё равно записывается.
|
||
|
||
#### Scenario: Пачка журнала
|
||
- **WHEN** `POST /agent/history` с тремя элементами, один из них уже есть в `issue_change`
|
||
- **THEN** 200 `{accepted: 2, duplicates: 1}`
|
||
|
||
#### Scenario: Неверный элемент
|
||
- **WHEN** `POST /agent/history` с двумя элементами, у второго `seq = "0"`
|
||
- **THEN** 422 `bad_history` с номером элемента 1 и полем `seq`; ни один элемент не записан
|
||
|
||
#### Scenario: Клиентский токен
|
||
- **WHEN** `POST /agent/history` клиентским токеном
|
||
- **THEN** 401
|
||
|
||
### Requirement: Лента изменений
|
||
Хаб SHALL отдавать интерфейсам (клиентский токен) ленту событий за период маршрутом `GET /changes` с параметрами:
|
||
- `since`, `until` — ISO-8601 со смещением или `Z`; без `until` — текущее время; без `since` — `until` минус 24 часа;
|
||
- `project` — только свои задачи проекта (метка проекта; соседи не входят); неизвестный проект — 404;
|
||
- `key` — события одной задачи (без ограничения проектом);
|
||
- `kinds` — через запятую из `status`, `description`, `dates`, `comment`, `other`, или `all`; по умолчанию —
|
||
`status,description,dates,comment`;
|
||
- `limit` — по умолчанию 500, не больше 2000.
|
||
|
||
События: строки `issue_change` с `at` в `[since, until]` — вид `status` (поле `status`), `description` (поле
|
||
`description`), `dates` (поля `duedate`, `planned_start`, `planned_end`), `other` (прочие поля); и комментарии
|
||
`issue_comment` с `created_at` в `[since, until]` — вид `comment`. Ответ — `{since, until, truncated, changes: [...]}`,
|
||
элемент — `{at, key, summary, status, kind, field, author, from, to, from_value, to_value}`: `summary` и `status` —
|
||
текущие из зеркала (`null`, если задачи в зеркале нет); `from`/`to` — `from_string`/`to_string` строки журнала, а если
|
||
строка `null` — `from_value`/`to_value`; `from_value`/`to_value` — сырые значения строки журнала; у комментария
|
||
`field = "comment"`, `from = null`, `to` — текст комментария, `from_value = to_value = null`. Порядок — по `at`
|
||
по убыванию, затем `key`, `history_id`, `seq` (комментарии — `comment_id`). Событий больше `limit` — отдаются первые
|
||
`limit` по этому порядку и `truncated = true`. Неверное время, `since` позже `until`, неизвестный вид, `limit` вне
|
||
1..2000 — 422 `bad_request` с именем параметра.
|
||
|
||
#### Scenario: Что закрылось за два часа
|
||
- **WHEN** в 12:00 UTC `GET /changes?project=AWPCA&since=2026-10-22T10:00:00Z&kinds=status`; в журнале — X (метка
|
||
`AWPCA`) «В работе» → «Готово» в 11:15 UTC и Y (метка `STOCKS_DRRK`) в 11:20 UTC
|
||
- **THEN** одно событие: X, `kind = status`, `from = "В работе"`, `to = "Готово"`, автор и `at` из журнала
|
||
|
||
#### Scenario: Комментарий в ленте
|
||
- **WHEN** у X комментарий с `created = "2026-10-22T13:30:00.000+0300"` (10:30 UTC), запрос с `since` 10:00 UTC
|
||
- **THEN** в ленте событие `kind = comment`, `at = "2026-10-22T10:30:00+00:00"`, `to` — текст комментария
|
||
|
||
#### Scenario: Прочие поля по запросу
|
||
- **WHEN** в журнале X сменился исполнитель; запрос без `kinds`, затем с `kinds=all`
|
||
- **THEN** в первом ответе события нет; во втором — `kind = other`, `field = "assignee"`
|
||
|
||
#### Scenario: Сосед проекта не в ленте
|
||
- **WHEN** Z без метки `AWPCA` связана с задачей `AWPCA`; в журнале Z сменился статус; `GET /changes?project=AWPCA`
|
||
- **THEN** события Z нет; `GET /changes?key=Z` его отдаёт
|
||
|
||
#### Scenario: Неверный период
|
||
- **WHEN** `GET /changes?since=вчера`
|
||
- **THEN** 422 `bad_request` с параметром `since`
|
||
|
||
### Requirement: Журнал в журнале синхронизаций
|
||
`GET /sync-runs` SHALL отдавать у каждого прогона также `history_at` (`null` — журнал по прогону не принят).
|
||
|
||
#### Scenario: Прогон с журналом
|
||
- **WHEN** полный прогон завершился `done` с принятым журналом; `GET /sync-runs`
|
||
- **THEN** у прогона `history_at` — время приёма; у хвостового `refresh {keys}` — `null`
|
||
|
||
### Requirement: История задачи в карточке
|
||
`GET /issues/{key}` SHALL отдавать также `changes` — все строки `issue_change` задачи (все виды, включая `other`) в
|
||
формате элемента ленты без `summary` и `status`, по `at` по убыванию (строки с `at = NULL` — в конце); журнала нет —
|
||
`changes = []`.
|
||
|
||
#### Scenario: Задача с историей
|
||
- **WHEN** у X в журнале смена статуса вчера и правка описания сегодня; `GET /issues/X`
|
||
- **THEN** `changes` из двух элементов: первым — правка описания (`kind = description`), вторым — смена статуса
|