OpenSpec: design-only change wiki-versions — версии страниц в wiki_sync (#7)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-10-28 11:31:32 +04:00
parent 430de5b670
commit 5c487d0ae9
3 changed files with 82 additions and 0 deletions

View File

@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-10-28

View File

@ -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:<uuid>"]]` работает; `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).** Цена ошибки: нет — прежний хаб маршрута версий
не знает.

View File

@ -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 хаба.
- Частичная перекачка дерева страниц: тексты страниц по-прежнему читаются все.
- Новые настройки агента.