72 lines
7.7 KiB
Markdown
72 lines
7.7 KiB
Markdown
## 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).** Цена ошибки: нет — прежний хаб маршрута версий
|
||
не знает.
|
||
- 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 шаг версий проверяется только юнит-тестами.
|