Andrey Epifancev 98842208f5 OpenSpec: change wiki-history — история правок вики, лента и дифф (#140)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 11:31:32 +04:00

103 lines
10 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 хранить опубликованные версии страниц дерева проекта: код версии Eva (`DOCH-…`), код страницы, время правки
(UTC), автора (`login`, `name`), Markdown версии и число строк, добавленных и удалённых против предыдущей версии той же
страницы (порядок версий — по времени, затем по коду; у первой — против пустого текста). Время правки — время создания
версии в Eva (`cmf_created_at`). Версии страниц, которых нет в дереве проекта, хаб MUST NOT хранить; неопубликованные
версии (черновики) агент MUST NOT присылать.
При выдаче `wiki_sync` хаб SHALL дописывать в параметры команды `known_versions` — объект `{<код страницы>: [<код
версии>…]}` по версиям, уже хранящимся у страниц текущей копии проекта (без копии — `{}`); в `command.params` в базе
поле MUST NOT записываться. Агент `wiki` SHALL после страниц прислать опубликованные версии страниц дерева, которых нет
в `known_versions` их страницы, пачками `POST /agent/wiki/versions {command_id, versions}`: версия — `{page_code, code,
created_at, author: {login, name}, html, attachments, links}` (`created_at` — ISO-8601 со смещением; `login` и `name`
— строки или `null`, если Eva автора не отдала; `attachments` и `links` — как у страниц, по `html` версии), в пачке не
больше 50 версий, — и отчитаться `done` с `result.versions` —
числом присланных версий. Пачка по команде не в `claimed` или не `wiki_sync` — 409; неверная форма версии — 422
`bad_versions` с кодом версии и полем. Хаб SHALL переводить HTML версии в Markdown при приёме вне транзакции записи
(ссылки на вложения — текстом `📎 <имя>` без ссылки, ссылки на страницы — `page:<код>`) и складывать в стадию команды.
По отчёту `done` с `result.versions` хаб SHALL в той же операции, что и копию (см. «Синхронизация вики»): добавить
версии из стадии к страницам, чьи коды есть в `codes`, отбросив остальные; удалить версии страниц, которых нет в
`codes`; посчитать «+/−» у новых версий и у версии, следующей за вставленной раньше неё; записать время применения
истории `history_at`. Присланных версий меньше `result.versions` — команда `failed` «нет версий: пришло N из M», копия и
история прежние. Отчёт `done` без `result.versions` (агент без истории) SHALL обновлять копию как прежде и не менять
историю и `history_at`. Отчёт `failed` и отброшенный отчёт по сменённому корню историю не меняют. Сброс корня SHALL
удалять историю проекта вместе с копией. Стадия версий очищается так же, как стадия страниц.
#### Scenario: Первая загрузка истории
- **WHEN** у `AWPCA` дерево DOC-1, DOC-2; в хабе версий нет; выдан `wiki_sync`
- **THEN** в параметрах выдачи `known_versions = {}`; агент прислал 3 версии DOC-1 и 2 версии DOC-2, отчёт `done
{versions: 5}`; в хабе 5 версий, у первой версии каждой страницы «+N / −0», `history_at` заполнен
#### Scenario: Догрузка новых версий
- **WHEN** у DOC-1 в хабе версии DOCH-10, DOCH-11; в Eva опубликована DOCH-12
- **THEN** при выдаче `known_versions = {"DOC-1": ["DOCH-10", "DOCH-11"], …}`; агент прислал только DOCH-12; у неё
«+/−» против DOCH-11
#### Scenario: Агент без истории
- **WHEN** агент v1 отчитался `done {root_title, codes, eva_calls}` без `versions`
- **THEN** копия обновлена, версии и `history_at` прежние
#### Scenario: Страница ушла из дерева
- **WHEN** у DOC-3 в хабе 4 версии; новая синхронизация вернула `codes` без DOC-3
- **THEN** версий DOC-3 в хабе нет
#### Scenario: Версия чужой страницы
- **WHEN** в стадии версия с `page_code = DOC-99`, которой нет в `codes`
- **THEN** версия не записана, команда `done`
#### Scenario: Потерянная пачка версий
- **WHEN** отчёт `done {versions: 5}`, в стадии 3 версии
- **THEN** команда `failed` «нет версий: пришло 3 из 5», копия и история прежние
### Requirement: Маршруты истории вики
Хаб SHALL предоставлять под клиентским токеном (проект — `project=<метка>`, неизвестный — 404, без корня — 409
`no_root`), читая только базу хаба:
- `GET /wiki/changes?project=&since=&until=&author=&limit=` — правки страниц текущей копии проекта со временем в
`[since, until]`. `since`, `until` — ISO-8601 со смещением или `Z` (без смещения — 422); без `until` — текущее время;
без `since` — `until` минус 24 часа; `author` — подстрока имени или логина без учёта регистра; `limit` — по умолчанию
500, 1–2000. Ответ — `{since, until, synced_at, history_at, truncated, changes: [{at, page_code, title, path,
version, author: {login, name}, added, removed}]}` по `at` по убыванию, затем по `version` по убыванию; правок больше
`limit` — первые `limit` и `truncated = true`. Неверное время, `since` позже `until`, `limit` вне 1..2000 — 422
`bad_request` с именем параметра. `history_at = null` — история проекта ещё не загружалась.
- `GET /wiki/pages/{code}/versions?project=` — `{code, title, versions: [{version, at, author: {login, name}, added,
removed}]}`, новые первыми; страницы нет в копии — 404.
- `GET /wiki/pages/{code}/diff?project=&version=&offset=` — правка `version` (без параметра — последняя) против
предыдущей версии страницы: `{code, title, path, version, prev_version, at, author: {login, name}, added, removed,
index, total, offset, next_offset, diff}`, где `diff` — unified diff Markdown (контекст 3 строки; у первой версии —
против пустого, `prev_version = null`) с символа `offset` до 20 000 символов, `next_offset` — начало следующей части
или `null`, `index` — номер версии от старой (с 1), `total` — число версий. Страницы нет в копии — 404; версии нет у
страницы — 404 `no_version`; у страницы нет версий — 404 `no_versions`.
`GET /wiki/pages/{code}` SHALL дополнительно отдавать `modified_at`, `modified_by: {login, name}` (последняя версия;
нет версий — `null`) и `versions` — число версий. `GET /wiki/status` SHALL дополнительно отдавать `history_at`.
#### Scenario: Что сделали за сегодня
- **WHEN** у `AWPCA` 27.10 в 17:59 и 18:02 (MSK) Соснов опубликовал версии DOC-101252, 26.10 Молчанов — версию DOC-2;
`GET /wiki/changes?project=AWPCA&since=2026-10-27T00:00:00+03:00`
- **THEN** две правки DOC-101252, новая первой, у каждой автор `{login: "sosnov-dvi@rshbeco.ru", name: "Дмитрий
Соснов"}`, `added`/`removed`, путь страницы в дереве; правки DOC-2 нет
#### Scenario: Фильтр по автору
- **WHEN** `GET /wiki/changes?project=AWPCA&since=…&author=соснов`
- **THEN** только правки Соснова
#### Scenario: Дифф последней правки
- **WHEN** у DOC-101252 4 версии; `GET /wiki/pages/DOC-101252/diff?project=AWPCA`
- **THEN** `index = 4`, `total = 4`, `prev_version` — третья версия, `diff` начинается с `--- ` и `+++ `
#### Scenario: Длинный дифф
- **WHEN** дифф правки 45 000 символов, запрос без `offset`
- **THEN** первые 20 000 символов, `next_offset = 20000`; запрос с `offset=40000` — остаток, `next_offset = null`
#### Scenario: История ещё не загружена
- **WHEN** у `AWPCA` копия есть, агент v1, `GET /wiki/changes?project=AWPCA`
- **THEN** 200, `history_at = null`, `changes = []`
#### Scenario: Последняя правка в странице
- **WHEN** в хабе 4 версии DOC-101252, последняя — Соснов, `created_at = 2026-10-27T23:48:02+03:00`;
`GET /wiki/pages/DOC-101252?project=AWPCA`
- **THEN** `modified_at = "2026-10-27T20:48:02+00:00"`, `modified_by.name = "Дмитрий Соснов"`, `versions = 4`