## 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).** Цена ошибки: нет — прежний хаб маршрута версий не знает. - 28.10.2026 — **P-1. Автор — по полям.** `login`/`name` из `cmf_author` берутся по отдельности: строка или `null` (закрытое ACL поле Eva не отдаёт — `_acl_fields: deny`); `cmf_author` не объект — `{null, null}`. Уточняет решение 4. Цена ошибки: в ленте правка без имени или без логина. - 28.10.2026 — **P-2. Страница обхода без `id`** — версии не запрашиваются, строка в лог, команда продолжается. Цена ошибки: у такой страницы нет истории. - 28.10.2026 — **P-3. Битый `known_versions`** (не объект или значение не список строк) — `failed` «wiki_sync: known_versions — не объект {код страницы: [коды версий]}» до вызовов Eva. Обоснование: иначе хаб получил бы всю историю заново. Цена ошибки: синхронизация стоит до исправления хаба. - 28.10.2026 — **P-4. Версия исчезла между `list` и `get`** — `failed` «версия <код> страницы <код> не найдена или нет прав», как пропавшая страница. Цена ошибки: редкая гонка с правкой в Eva останавливает синхронизацию до повтора. - 28.10.2026 — **P-5. Порядок версий** — порядок обхода страниц, внутри страницы — порядок ответа Eva; хаб сортирует сам. Цена ошибки: нет. - 28.10.2026 — **P-6. e2e с историей — по признаку хаба** (`history_at` в `GET /wiki/status`); с хабом без истории блок пропускается с сообщением. Цена ошибки: до merge hub#140 шаг версий проверяется только юнит-тестами.