diff --git a/openspec/changes/wiki-versions/.openspec.yaml b/openspec/changes/wiki-versions/.openspec.yaml new file mode 100644 index 0000000..1bb6456 --- /dev/null +++ b/openspec/changes/wiki-versions/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-10-28 diff --git a/openspec/changes/wiki-versions/design.md b/openspec/changes/wiki-versions/design.md new file mode 100644 index 0000000..4a77fd1 --- /dev/null +++ b/openspec/changes/wiki-versions/design.md @@ -0,0 +1,57 @@ +## Context + +`do_wiki_sync` обходит дерево от корня (`build_children` + `walk`), читает текст каждой страницы +(`CmfDocument.get … ["code", "name", "text"]`) и шлёт пачки страниц `POST /agent/wiki/pages` (≤50 страниц, ≤4 МБ JSON), +затем отчитывается `done {root_title, codes, eva_calls}`. Хаб с change `wiki-history` дописывает в параметры выдачи +`known_versions` и принимает версии отдельным маршрутом. + +Проверено на живой Eva 28.10.2026: +- `CmfDocumentHistory.list` с `filter=[["parent_id", "==", "CmfDocument:"]]` работает; `parent.code` — ошибка + `-32001` (вложенный фильтр). +- Поля версии: `code` (`DOCH-…`), `cmf_created_at` (ISO со смещением `+03:00`), `cmf_author_id`, `text` (полный HTML, + до ~56 тыс. символов), `has_published`, `cur_workflow`. `cmf_author` по имени поля раскрывается в объект с `name` и + `login` (как у `CmfDocument`). +- Черновик (`has_published = false`) Eva меняет на месте. + +## Goals / Non-Goals + +**Goals:** опубликованные версии страниц дерева — хабу, без повторной перекачки известных; работа с хабом без истории. + +**Non-Goals:** см. proposal.md. + +## Decisions + +1. **Шаг версий — после страниц, в той же команде.** Страницы отправлены — затем версии; отчёт один. Порядок + страниц — порядок обхода. +2. **Выборка по странице дерева.** Для каждой страницы обхода — `CmfDocumentHistory.list` с фильтром `parent_id` = id + страницы из обхода (`doc["id"]`), полями `["code", "cmf_created_at", "has_published", "cmf_author"]`, страницами по + `PAGE_SIZE` с той же защитой от несоблюдения `slice`, что у `list_docs` (общий помощник для класса). Опубликованные + отбираются у себя (`has_published is True`): фильтр Eva по этому полю не проверялся. +3. **Текст — только новых.** Версия, чей код есть в `known_versions[<код страницы>]`, пропускается без запроса + текста. Для остальных — `CmfDocumentHistory.get` по коду с полями `["code", "text"]`; `html` = `text` (не строка — + пустая строка); `attachments`, `links` — `classify_links(html, base)`, как у страниц. +4. **Автор.** `{login, name}` из `cmf_author`; объекта нет или поля закрыты (`_acl_fields`) — `{login: null, name: null}`. +5. **Пачки.** Те же пределы, что у страниц: ≤50 версий и ≤4 МБ JSON на пачку; 413 от хаба — ошибка с кодом версии или + границами пачки (как `_send_pages`). +6. **Совместимость с прежним хабом.** Ключа `known_versions` в параметрах нет — шаг версий не выполняется, в отчёте + нет `versions`. Пустой объект `{}` — первый прогон, вся история. +7. **Ошибки.** Ошибка Eva на шаге версий (`EvaError`, сеть после повторов) — команда `failed` с текстом, как на шаге + страниц; хаб оставит копию и историю прежними (решение 10 change хаба). Версия без `code` или без + `cmf_created_at` пропускается с записью в лог. +8. **Счётчики.** `result.versions` — число отправленных версий; `eva_calls` включает вызовы шага версий. +9. **Тесты.** `tests/fake_eva.py` получает `CmfDocumentHistory.list` (фильтр `parent_id`) и `.get` (по `code`); сквозной + прогон с тестовым хабом — первая загрузка и догрузка одной новой версии. + +## Risks / Trade-offs + +- Один вызов Eva на страницу при каждой синхронизации; при пачке новых версий — ещё по вызову на версию. Первый прогон + AWPCA (28 страниц, десятки версий) — порядка сотни вызовов с паузой `wiki_pacing`. +- Если Eva однажды поменяет текст опубликованной версии, агент её не перечитает (код уже известен хабу). + +## Decisions made without discussion + +- 28.10.2026 — **отбор опубликованных у себя, не фильтром Eva (решение 2).** Обоснование: фильтр по `has_published` не + проверялся, а лишние метаданные дёшевы. Цена ошибки: чуть больше данных в ответе на страницу. +- 28.10.2026 — **пустой автор `{null, null}` вместо отказа (решение 4).** Цена ошибки: в ленте правка без имени. +- 28.10.2026 — **без `known_versions` шаг пропускается (решение 6).** Цена ошибки: нет — прежний хаб маршрута версий + не знает. diff --git a/openspec/changes/wiki-versions/proposal.md b/openspec/changes/wiki-versions/proposal.md new file mode 100644 index 0000000..86d6eca --- /dev/null +++ b/openspec/changes/wiki-versions/proposal.md @@ -0,0 +1,23 @@ +## Why + +Хаб получает историю правок страниц вики (aep/sdlc-hub#140, change `wiki-history`): ленту «кто, когда, что поменял» и +дифф каждой правки. Историю знает только Eva, а ходит в неё только агент. Проверка API 28.10.2026 (скрипты +`eva_probe.py`, `eva_probe2.py`) нашла версии страниц — `CmfDocumentHistory` — с полным текстом, автором и признаком +публикации (aep/eva-agent#7). + +## What Changes + +- Команда `wiki_sync` после отправки страниц забирает опубликованные версии страниц дерева, которых нет в + `known_versions` из параметров команды, и шлёт их хабу пачками `POST /agent/wiki/versions`; отчёт `done` получает + `result.versions`. +- Без `known_versions` в параметрах (хаб без истории) шаг версий пропускается: агент v2 работает и с прежним хабом. +- `AGENT_VERSION` 2. + +Контракт команды, маршрута и отчёта — в спеке хаба: `sdlc-hub/openspec/changes/wiki-history/specs/wiki/spec.md`, +требование «История правок страниц». Здесь — только решения агента (design.md). + +## Non-Goals + +- Черновики, версии вложений, журнал аудита — вне объёма change хаба. +- Частичная перекачка дерева страниц: тексты страниц по-прежнему читаются все. +- Новые настройки агента.