Финальное ревью: версии без автора в лог, повтор кода версии, нет text — failed (#7, I-1, M-1, M-3)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
15ff652912
commit
060a2c6a1f
@ -74,7 +74,9 @@ launchctl unload ~/Library/LaunchAgents/ru.aepif.eva-agent.plist
|
||||
|
||||
Агент подтянет её сам (`auto_update`); настройку хаба `wiki_min_agent_version` (не `min_agent_version` — та у агента
|
||||
jira) поднимать после того, как `agent_status` покажет новую версию агента wiki. Для v2 (версии страниц): после выкатки
|
||||
хаба с change `wiki-history` и v2 поднять `wiki_min_agent_version` до 2 (Migration Plan хаба, п. 3).
|
||||
хаба с change `wiki-history` и v2 поднять `wiki_min_agent_version` до 2 (Migration Plan хаба, п. 3). До этого — проверить
|
||||
в логе агента первой синхронизации v2 строку «N из M версий без автора»: если она есть, `cmf_author` у версий Eva не
|
||||
раскрывает, и авторы в истории будут пустыми (их не исправит повторная синхронизация).
|
||||
|
||||
## Разработка
|
||||
|
||||
|
||||
@ -9,8 +9,8 @@
|
||||
- `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`).
|
||||
до ~56 тыс. символов), `has_published`, `cur_workflow`. У `CmfDocument` поле `cmf_author` по имени раскрывается в объект с `name` и
|
||||
`login`; у `CmfDocumentHistory` проба проверяла только `fields: ["**"]` — раскрытие по имени поля не проверено (I-1).
|
||||
- Черновик (`has_published = false`) Eva меняет на месте.
|
||||
|
||||
## Goals / Non-Goals
|
||||
@ -69,3 +69,14 @@
|
||||
сам. Цена ошибки: нет.
|
||||
- 28.10.2026 — **P-6. e2e с историей — по признаку хаба** (`history_at` в `GET /wiki/status`); с хабом без истории
|
||||
блок пропускается с сообщением. Цена ошибки: до merge hub#140 шаг версий проверяется только юнит-тестами.
|
||||
- 28.10.2026 — **I-1 (финальное ревью). Версии без автора — строка в лог** «N из M версий без автора (cmf_author не
|
||||
раскрыт или закрыт ACL)» на синхронизацию. Обоснование: автор известной версии не перечитывается, потеря необратима.
|
||||
Перед подъёмом `wiki_min_agent_version` — проба `CmfDocumentHistory.list` с `fields=["cmf_author"]` на виртуалке.
|
||||
Цена ошибки: нет.
|
||||
- 28.10.2026 — **M-1 (финальное ревью, поднято до Important). Код версии уходит один раз за прогон**, даже если Eva
|
||||
при сдвиге `slice` отдала запись повторно. Обоснование: хаб считает уникальные коды, повтор давал `failed`
|
||||
«нет версий: пришло N−1 из N». Цена ошибки: нет.
|
||||
- 28.10.2026 — **M-3 (финальное ревью, поднято до Important). Нет ключа `text` в ответе `CmfDocumentHistory.get` —
|
||||
`failed`** «версия X страницы Y: Eva не отдала text (нет прав?)». Уточняет решение 3: пустая версия в хабе не
|
||||
исправилась бы (код уже известен). `text: null` по-прежнему — пустая строка. Цена ошибки: при закрытом ACL `text`
|
||||
синхронизация стоит до выдачи прав.
|
||||
|
||||
13
src/agent.py
13
src/agent.py
@ -981,6 +981,7 @@ class Executor:
|
||||
base = self.eva.base
|
||||
out = _Batcher(lambda b: self._send_batch(self.hub.wiki_versions, command_id, b, "версия", "пачка версий"),
|
||||
MAX_VERSIONS)
|
||||
no_author = 0
|
||||
for page_code, doc_id in tree:
|
||||
if doc_id is None:
|
||||
log(" у страницы %s нет id — версии не запрошены" % page_code)
|
||||
@ -1002,12 +1003,18 @@ class Executor:
|
||||
full = self.eva.get_version(vcode)
|
||||
if not full:
|
||||
raise _VersionMissing("версия %s страницы %s не найдена или нет прав" % (vcode, page_code))
|
||||
html = full.get("text") if isinstance(full.get("text"), str) else ""
|
||||
if "text" not in full: # закрытое поле Eva не отдаёт; пустая версия в хабе не исправилась бы (M-3)
|
||||
raise _VersionMissing("версия %s страницы %s: Eva не отдала text (нет прав?)" % (vcode, page_code))
|
||||
html = full["text"] if isinstance(full["text"], str) else ""
|
||||
attachments, links = classify_links(html, base)
|
||||
out.add({"page_code": page_code, "code": vcode, "created_at": created,
|
||||
"author": version_author(v.get("cmf_author")), "html": html,
|
||||
author = version_author(v.get("cmf_author"))
|
||||
no_author += author == {"login": None, "name": None}
|
||||
out.add({"page_code": page_code, "code": vcode, "created_at": created, "author": author, "html": html,
|
||||
"attachments": attachments, "links": links})
|
||||
seen.add(vcode) # повтор кода в списке Eva (сдвиг slice) не уходит дважды (M-1)
|
||||
out.flush()
|
||||
if no_author: # автор известной версии не перечитывается — потерю видно сразу (I-1)
|
||||
log(" %d из %d версий без автора (cmf_author не раскрыт или закрыт ACL)" % (no_author, out.sent))
|
||||
return out.sent
|
||||
|
||||
def do_wiki_fetch(self, p: dict, cmd: dict) -> dict:
|
||||
|
||||
@ -382,6 +382,41 @@ class WikiVersionsTest(SyncBase):
|
||||
self.assertEqual(v1["attachments"], [{"ref": "/files/a/b.png", "url": self.eva.base_url + "/files/a/b.png", "name": "Схема"}])
|
||||
self.assertEqual((v2["html"], v2["attachments"], v2["links"]), ("", [], []))
|
||||
|
||||
def test_versions_without_author_logged(self): # I-1: потеря автора видна в логе
|
||||
self.tree2(); e = self.eva
|
||||
e.add_version("CmfDocument:1", "DOCH-1", author=None)
|
||||
e.add_version("CmfDocument:1", "DOCH-2", author="CmfPerson:6c2b")
|
||||
e.add_version("CmfDocument:1", "DOCH-3")
|
||||
buf = io.StringIO()
|
||||
with redirect_stdout(buf):
|
||||
out = self.sync(known_versions={})
|
||||
self.assertEqual(out["status"], "done", out)
|
||||
self.assertIn("2 из 3 версий без автора (cmf_author не раскрыт или закрыт ACL)", buf.getvalue())
|
||||
|
||||
def test_versions_all_with_author_not_logged(self):
|
||||
self.tree2(); self.eva.add_version("CmfDocument:1", "DOCH-1")
|
||||
buf = io.StringIO()
|
||||
with redirect_stdout(buf):
|
||||
self.sync(known_versions={})
|
||||
self.assertNotIn("без автора", buf.getvalue())
|
||||
|
||||
def test_versions_duplicate_code_sent_once(self): # M-1: повтор кода в списке Eva
|
||||
self.tree2()
|
||||
self.eva.add_version("CmfDocument:1", "DOCH-1", created_at="2026-10-27T10:00:00+03:00")
|
||||
self.eva.add_version("CmfDocument:1", "DOCH-1", created_at="2026-10-27T10:00:01+03:00")
|
||||
out = self.sync(known_versions={})
|
||||
self.assertEqual(out["status"], "done", out)
|
||||
self.assertEqual(out["result"]["versions"], 1)
|
||||
self.assertEqual([v["code"] for v in self.sent_versions()], ["DOCH-1"])
|
||||
|
||||
def test_version_text_missing_fails(self): # M-3: Eva не отдала text — не сохранять пустую версию
|
||||
self.tree2(); self.eva.add_version("CmfDocument:1", "DOCH-1")
|
||||
self.client.get_version = lambda code: {"code": code}
|
||||
out = self.sync(known_versions={})
|
||||
self.assertEqual(out["status"], "failed")
|
||||
self.assertEqual(out["error"], "версия DOCH-1 страницы DOC-1: Eva не отдала text (нет прав?)")
|
||||
self.assertEqual(self.hub.versions, [])
|
||||
|
||||
def test_versions_batches_50(self):
|
||||
self.tree2()
|
||||
for i in range(120):
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user