Andrey Epifancev 5a6cdecc8e OpenSpec change-history: журнал изменений задач (#70)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 10:04:52 +04:00

81 lines
7.0 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.

## 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`), вторым — смена статуса