diff --git a/docs/DOC_CLEANUP_PLAN.md b/docs/DOC_CLEANUP_PLAN.md new file mode 100644 index 00000000..470b538b --- /dev/null +++ b/docs/DOC_CLEANUP_PLAN.md @@ -0,0 +1,380 @@ +# ПЛАН РАЗБОРА ТЕХДОЛГА ДОКУМЕНТАЦИИ — заказ владельца 01.09.2026 + +> **⚠ ВРЕМЕННЫЙ РАБОЧИЙ ДОКУМЕНТ.** Живёт, пока идёт разбор; по закрытию последнего батча +> уезжает в `archive/` вместе с реестром находок. Держать его в живых доках после закрытия — +> значит сделать ровно то, против чего заказан сам разбор. +> +> **Заказ владельца дословно:** свести находки в план и идти по нему · привести документацию +> в хороший порядок · **с бэклогом ПООСТОРОЖНЕЕ** · спилить лишнее, актуализировать · +> **старое и откровенно лишнее выкинуть, актуальное оставить и НЕ ПОТЕРЯТЬ** · старые решения +> и мусор спиливать, чтобы не забивать контекст сессиям · **касается и фронтовой, и +> платформенной, и бэкенд-документации** (прямая санкция на чужие зоны, выданная 01.09). +> +> **Контроль исполнения — отдельный агент модели Fable 5** (решение владельца 01.09): после +> КАЖДОГО коммита я предъявляю ему коммит, он сверяется с реестром находок и выносит, верно ли +> закрыто. Пинг-понг идёт, пока обе стороны не скажут «техдолг закрыт». Мой самоотчёт +> закрытием НЕ считается. + +## Источники + +- **Реестр находок** — [`DOC_REVISION_FINDINGS.md`](DOC_REVISION_FINDINGS.md): 130 живых находок + с уликами (команда + вывод), по одной секции `N001`…`N130`. +- **Снятые опровергателем** — [`DOC_REVISION_REFUTED.md`](DOC_REVISION_REFUTED.md): 7 находок, + которые адверсариальный проход ОПРОВЕРГ. ⚠ Их чинить НЕЛЬЗЯ: «починка» вернёт дефект. + +## Как добыты находки (чтобы вес был понятен) + +Воркфлоу `docs-staleness-revision-A`, 15 срезов × (ревизор + опровергатель), 30 агентов, 0 отказов, +1689 вызовов инструментов. **1432 утверждения проверены ИСПОЛНЕНИЕМ**, 115 положительных контролей +(норма: отрицательный результат предъявляется вместе с доказательством, что инструмент в этой +области способен хоть что-то найти). Найдено 137 → адверсариальный пере-суд снял 7 → **осталось 130**. + +⚠ **Вес находки не одинаков.** 82 разрушающие и блокерные прошли пере-суд (75 ПОДТВЕРЖДЕНО). +Остальные проверены ОДНИМ агентом — они наряд на перепроверку, а не истина: исполнять их на +доверии запрещено, каждую верифицирую своей командой перед правкой. + +## Дисциплина разбора (жёсткая, из заказа владельца) + +1. **Не потерять.** Прежде чем удалить абзац/строку/док — грепнуть его отличительный токен по + всему дереву и назвать, куда уехало содержимое. Удаление без адреса = потеря. +2. **Бэклог — осторожно.** Строку закрываем ТОЛЬКО когда лекарство предъявлено в коде своей + командой. ID строк стабильны навсегда и на них ссылаются доки — закрытая строка уезжает в + D-ноту с номером решения, а не стирается молча. +3. **Чужие зоны.** Санкция владельца выдана на ДОКУМЕНТАЦИЮ. Код чужих зон не трогаю. + Каждая правка чужого дока объявляется в зонном журнале, чтобы зона увидела. +4. **Гейты перед каждым коммитом:** `counts.py --check` и `--lint`, `git status` на чужое, + коммит только явным списком путей. +5. **Соседний worktree `/home/ubuntu/projects/textmachine` — НЕ ТРОГАТЬ** (указание владельца + 01.09): ни чтением, ни записью, ни git-командой. Каталог `books/` — симлинк туда же. + +## Батчи + +Порядок — по убыванию цены ошибки. Каждый батч = один коммит = одна сверка контролёра. + +| Батч | Предмет | Находок | бл / maj / min | Статус | +|---|---|---|---|---| +| **Б1** | Блокеры бэклога: строки, закрытые кодом, но открытые в таблице | 3 | 3 / 0 / 0 | ⬜ не начат | +| **Б2** | Денежный путь `15-money-path.md` | 7 | 2 / 3 / 2 | ⬜ не начат | +| **Б3** | Шапка журнала и остальной PROGRESS | 17 | 0 / 9 / 8 | ⬜ не начат | +| **Б4** | Контракт API 14 (README + openapi) | 10 | 0 / 5 / 5 | ⬜ не начат | +| **Б5** | Реестр загейченных триггеров 13 | 8 | 0 / 5 / 3 | ⬜ не начат | +| **Б6** | Шов и эмиттер: 16, 17 | 11 | 0 / 8 / 3 | ⬜ не начат | +| **Б7** | Движковые арх-доки: 03, 09, 10, 12 | 17 | 0 / 5 / 12 | ⬜ не начат | +| **Б8** | Корневые и онбординг: CLAUDE.md, README, STACK, glossary, product-requirements | 17 | 0 / 6 / 11 | ⬜ не начат | +| **Б9** | Ранние арх-доки 01/04/06 — баннеры или архив | 6 | 0 / 1 / 5 | ⬜ не начат | +| **Б10** | ЧУЖАЯ ЗОНА (санкция владельца 01.09): бэкенд-доки | 10 | 0 / 6 / 4 | ⬜ не начат | +| **Б11** | ЧУЖАЯ ЗОНА (санкция владельца 01.09): фронт-доки | 16 | 0 / 9 / 7 | ⬜ не начат | +| **Б12** | ЧУЖАЯ ЗОНА (санкция владельца 01.09): платформенные доки | 8 | 1 / 4 / 3 | ⬜ не начат | + +### Состав батчей поимённо + + +**Б1 — Блокеры бэклога: строки, закрытые кодом, но открытые в таблице** + +- `N001` блокер · `docs/PROGRESS.md:176` · C-исполненное-как-открытое — Строка 194 ЗАКРЫТА лендингом 31.08 (`bb541a8`, экземпляр A10 закона раскрытия): `projectBookUSD(...) + r.bankRoleCommittedUSD()` уже стоит в `status.g + +- `N002` блокер · `docs/PROGRESS.md:200` · C-исполненное-как-открытое — Строка 238 ЗАКРЫТА тем же лендингом 31.08 (`bb541a8`, экземпляр A6 «проекция видит правку исходника»). Посадка `rebillsource_test.go` называет строку + +- `N003` блокер · `docs/PROGRESS.md:201` · C-исполненное-как-открытое — Строка 239 ЗАКРЫТА лендингом 31.08 (`bb541a8`, экземпляр A7+A12). Правило `exportConfigDrift` не «перенесено», а сведено в ОДНО общее определение `orp + + +**Б2 — Денежный путь `15-money-path.md`** + +- `N004` блокер · `docs/architecture/15-money-path.md:31` · D-противоречие-ратифицированному — Числа взяты из D39.165 §1 (28.08) и меряны на леджерах ДО ценового перелома. D39.179 п.1 (31.08) ратифицировал: DeepSeek пере-пинен под цены, вступивш + +- `N005` блокер · `docs/architecture/15-money-path.md:35` · A-дрейф — Абзац описывает ТОЛЬКО живую ветвь `run abandon`. С пака P11 (PD-385, коммит e548e5a от 29.08) есть вторая ветвь — PHASE=settling: к ней отказ «юнит/б + +- `N039` major · `docs/architecture/15-money-path.md:6` · B-мёртвый-указатель — Норму «чистый ФАЙЛ» ратифицировал D39.179 п.4, который сам говорит: «уточняет D39.177 §2в … Ось «зона» была НЕВЕРНА … Носитель нормы — шапка 15-money- + +- `N040` major · `docs/architecture/15-money-path.md:19` · A-дрейф — Перечень движковых суб-бюджетов читается как исчерпывающий (репэйр + эскалация) и не знает третьего семейства — книго-широких бюджетов банк-ролей `gat + +- `N041` major · `docs/architecture/15-money-path.md:21` · D-противоречие-ратифицированному — «Различение живёт в отчёте прогона и в логе» перестало быть верным 31.08. D39.181 п.2 ратифицировал закон раскрытия: «признак остановки по объёму обяз + +- `N113` minor · `docs/architecture/15-money-path.md:6` · C-исполненное-как-открытое — Пак контракта 0.9.0 ЗАЛЕНДЖЕН 31.08 коммитом 0a680a3 (D39.180), файл в дереве чист, версия в нём — 0.9.0. Настоящее время «двигается прямо сейчас» лож + +- `N114` minor · `docs/architecture/15-money-path.md:14` · A-дрейф — Из трёх опор канона, названных здесь, у двух в доке есть носители (телеметрия — леджер/status в §2, потолки — §2/§3), а у третьей — «явное согласие на + + +**Б3 — Шапка журнала и остальной PROGRESS** + +- `N013` major · `docs/PROGRESS.md:3` · A-дрейф — Канон уже НЕ 0.8.0: в `docs/architecture/14-api-contract/openapi.yaml` стоит 0.9.0 — минор уехал с лендингом P12 31.08, о котором сообщает ЭТА ЖЕ стро + +- `N014` major · `docs/PROGRESS.md:3` · A-дрейф — Числа «73%» в цитируемом отчёте холодного прогона НЕТ вовсе. Отчёт даёт долю редактора с ДВУМЯ знаменателями: 49.3% от всего прогона ($0.214865 из $0. + +- `N015` major · `docs/PROGRESS.md:3` · E-второй-носитель — Шапка объявляет себя ЕДИНСТВЕННЫМ носителем состояния очереди, но не несёт двух лендингов 31.08, которые несёт `docs/README.md`: бэкенд-пак «ДЕНЬГИ И + +- `N016` major · `docs/PROGRESS.md:80` · C-исполненное-как-открытое — Довод «⇒ строка жива» опирается на прогоны coldrun-a/b, но 31.08 отработал НОВЫЙ холодный прогон (coldrun-v16), в котором редакторская волна гонялась + +- `N017` major · `docs/PROGRESS.md:143` · A-дрейф — Гейт назначен на событие, которое УЖЕ произошло (первый холодный прогон, 31.08), и прогон показал, что назначенный замер этим путём недостижим: манифе + +- `N018` major · `docs/PROGRESS.md:157` · A-дрейф — Файла `.bank-stop.json` больше НЕ существует: он снесён вместе с писателем и четырьмя тестами при постройке входной двери шва (D39.158) — в Go-коде дв + +- `N019` major · `docs/PROGRESS.md:182` · D-противоречие-ратифицированному — Дважды протухла. (1) Цифра «≈$0.05» СНЯТА ратифицированной нотой D39.179 п.1 как протухшая: пере-пин DeepSeek 15.08 даёт множитель 4.47, а замер 31.08 + +- `N020` major · `docs/PROGRESS.md:196` · C-исполненное-как-открытое — Строка 232 ЗАКРЫТА лендингом 31.08 (`bb541a8`, экземпляр A11). Читающий метод `store.AnnouncedOnceKeys()` построен, его докстринг называет строку 232 + +- `N021` major · `docs/PROGRESS.md:202` · C-исполненное-как-открытое — Строка 240 исполнена лендингом P12 31.08 (`0a680a3`, D39.180) — обеими ветвями своей же диспозиции. Гард `LiftBankStop` СНЯТ (в `platform/` осталось д + +- `N075` minor · `docs/PROGRESS.md:3` · B-мёртвый-указатель — Промт НЕ в `docs/archive/prompts/` — он до сих пор лежит в корне `docs/`. В архиве промтов лежат два ДРУГИХ холодно-прогонных промта (`BACKEND_COLDRUN + +- `N076` minor · `docs/PROGRESS.md:3` · A-дрейф — Сегодня `main` опережает `origin/main` на 48 коммитов. Несущий вывод («развязки нет») выжил — `origin/main` остаётся ПРЕДКОМ `main`, расхождения по-пр + +- `N077` minor · `docs/PROGRESS.md:3` · A-дрейф — «Пять» было верно до 31.08 (P8-FIX · P8-REVIEW · P9 · P10 · P11). После P7, где долг выдачи был отложен в P8, по реестру нот легло ШЕСТЬ P-нумерованны + +- `N078` minor · `docs/PROGRESS.md:15` · C-исполненное-как-открытое — Условие, по которому число обещано пере-калибровать, ИСПОЛНИЛОСЬ: платный прогон отработал 31.08 и дал собственную оценку движка $1.1377277707 на деся + +- `N079` minor · `docs/PROGRESS.md:146` · C-исполненное-как-открытое — Гейт пал: первый холодный прогон отработал 31.08 и выдал ровно то число, на котором строка условила решение — `families_held=0` (строка требует ослабл + +- `N080` minor · `docs/PROGRESS.md:183` · B-мёртвый-указатель — Оба line-якоря строки 203 уехали после контрактного минора 0.9.0 (P12, `0a680a3`, 31.08). `openapi.yaml:1784-1787` сегодня показывает поля `id`/`units + +- `N081` minor · `docs/PROGRESS.md:193` · B-мёртвый-указатель — Якорь уехал на 28 строк после контрактного минора 0.9.0 (P12, 31.08): цитируемая фраза стоит на `:2079`, а `:2051` показывает заголовок совсем другой + +- `N082` minor · `docs/PROGRESS.md:207` · C-исполненное-как-открытое — Строка исполнена НАПОЛОВИНУ, и строка об этом не знает: 31.08 коммитом `94381bb` в `docs/scripts/githooks/pre-commit` добавлен warn-блок, который пере + + +**Б4 — Контракт API 14 (README + openapi)** + +- `N034` major · `docs/architecture/14-api-contract/README.md:8` · A-дрейф — Канон стоит на 0.9.0 с 31.08 (минор заленджен D39.180 вместе с паком P12), а ревью-шапка всё ещё называет базой 0.8.0. Верно: зеркало 0.2.3 против кан + +- `N035` major · `docs/architecture/14-api-contract/README.md:981` · C-исполненное-как-открытое — Расхождение ЗАКРЫТО: `Resume` больше не отвечает 202 на исчерпанный прогон — вердикт `ceilingSpent` возвращает `ErrCeilingReached`, и `httpapi/v0.go:8 + +- `N036` major · `docs/architecture/14-api-contract/README.md:1637` · A-дрейф — Собственная команда дока, исполненная сегодня, даёт 26 хитов, а не ноль: платформа сжимает САМА, в Go, в `platform/internal/httpapi/conditional.go` (` + +- `N037` major · `docs/architecture/14-api-contract/README.md:1718` · B-мёртвый-указатель — Вся колонка «Откуда взят (платформа)» Приложения А-2 съехала: реальные производители лежат на 240–270 строк ниже названных, а на названных номерах сто + +- `N038` major · `docs/architecture/14-api-contract/openapi.yaml:1414` · E-второй-носитель — Деплой отвечает ТРЕМЯ значениями: `runStage` (platform/internal/pgstore/readmodel.go:576) выдаёт `re_pass` для всего пере-прохода, помимо `drafting`/` + +- `N108` minor · `docs/architecture/14-api-contract/README.md:6` · B-мёртвый-указатель — D39.169 ратифицировала минор 0.8.0, а не 0.9.0; 0.9.0 принят и заленджен нотой D39.180 (31.08). Тело того же дока (строка 69) описывает 0.9.0 вовсе БЕ + +- `N109` minor · `docs/architecture/14-api-contract/README.md:60` · B-мёртвый-указатель — Файла по этому пути нет: отчёт вынесен в архив коммитом 31b96d1 (27.08) и лежит как `docs/archive/reports/CONTRACT_MINOR_REPORT_2026-08-27.md`. Ссылка + +- `N110` minor · `docs/architecture/14-api-contract/README.md:990` · C-исполненное-как-открытое — Носитель «вход P7» израсходован — пак P7 принят D39.153, и ⚠-абзац НАД самой таблицей это уже пишет («Носитель израсходован: пак P7 принят D39.153»). + +- `N111` minor · `docs/architecture/14-api-contract/README.md:998` · B-мёртвый-указатель — Строки 148 в едином бэклоге НЕТ — её удалил `a023e39` решением владельца 21.08. Хуже: ТОТ ЖЕ документ на строке 1670 уже это знает и пишет «⚠ Прежний + +- `N112` minor · `docs/architecture/14-api-contract/README.md:1002` · B-мёртвый-указатель — Промт вынесен в архив: живой путь — `docs/archive/prompts/BACKEND_SILENT_HARM_SESSION_PROMPT_2026-08-28.md`. Дополнительно: половина того, на что ссыл + + +**Б5 — Реестр загейченных триггеров 13** + +- `N029` major · `docs/architecture/13-tech-debt-anchors.md:52` · E-второй-носитель — Это ВТОРОЙ (и единственный не-баннированный) носитель статуса строки F4 реестра памяти, и он разошёлся с кодом: `tmctl backup` существует (cmd/tmctl/m + +- `N030` major · `docs/architecture/13-tech-debt-anchors.md:72` · D-противоречие-ратифицированному — Обе половины ложны и противоречат ратифицированной D39.69 §3 («строки 27/84 ЗАКРЫТЫ … neuter end-to-end (enum · gender_neuter в injection-данных · See + +- `N031` major · `docs/architecture/13-tech-debt-anchors.md:100` · C-исполненное-как-открытое — feed_cap ЗАКРЫТ ратифицированной нотой D39.69 §1 («value-порядок отреза бюджета (частота-desc — feed_cap ЗАКРЫТ этим, из строки 86 изъят)»), и код это + +- `N032` major · `docs/architecture/13-tech-debt-anchors.md:134` · B-мёртвый-указатель — Единственная объявленная функция файла — быть справочником file:line-якорей («Этот файл — справочные file:line-якоря … Актуализируется лендингами пако + +- `N033` major · `docs/architecture/13-tech-debt-anchors.md:180` · C-исполненное-как-открытое — Гейт по письму источника ПОСТРОЕН и стоит внутри самой lintNumberMagnitude; код называет заказ по имени в комментарии — «SOURCE-script gate (строка 79 + +- `N105` minor · `docs/architecture/13-tech-debt-anchors.md:12` · E-второй-носитель — Один и тот же якорь ja-препа живёт в двух живых носителях, и копии разошлись: строка 81 бэклога уже исправлена на «migrate.go:187/194», а справочник — + +- `N106` minor · `docs/architecture/13-tech-debt-anchors.md:24` · B-мёртвый-указатель — Якоря реестра техдолга в ДВА дока моего среза сдвинулись ровно на +6 строк и не были актуализированы: коммит d2cd30c (22.08) дописал ⚠-поправки в 02-m + +- `N107` minor · `docs/architecture/13-tech-debt-anchors.md:104` · E-второй-носитель — Факт про neuter лежит в этом файле ДВАЖДЫ, и копии разошлись ВНУТРИ одного документа: тело пункта (строки 70-72) несёт пере-сверку 23.08 «ЧЕТЫРЕ пункт + + +**Б6 — Шов и эмиттер: 16, 17** + +- `N042` major · `docs/architecture/16-events-emitter.md:11` · B-мёртвый-указатель — Якорь ведёт не туда И цель больше не существует: `docs/PROGRESS.md:149` — это строка бэклога 42, а строк 103 и 165 в таблице нет вовсе (закрыты D39.13 + +- `N043` major · `docs/architecture/16-events-emitter.md:17` · B-мёртвый-указатель — Три якоря §1 («что уже есть со стороны движка», раздел БЕЗ ⚠-баннера) уехали: `cmd/tmctl/main.go:67-70` — это константы полосы отказов (`refusalLast = + +- `N044` major · `docs/architecture/16-events-emitter.md:38` · A-дрейф — Дословная цитата, которую док обещает в шапке («дословные цитаты — в кавычках-ёлочках»), в дереве больше не существует: `events.go:5-6` теперь читаетс + +- `N045` major · `docs/architecture/16-events-emitter.md:66` · B-мёртвый-указатель — ВСЕ двенадцать якорей в `platform/docs/DEFECT_REGISTER.md` мертвы — ни один не попадает в свою строку. Фактические номера: PD-60→343, PD-61→344, PD-79 + +- `N046` major · `docs/architecture/17-seam-inbound-law.md:56` · B-мёртвый-указатель — Якорь родился верным и сгнил дрейфом — и уехал на строку, ВЫГЛЯДЯЩУЮ так же. На 23.08 (коммит `4f56160`, тот же, что написал якорь) `mining.go:221` бы + +- `N047` major · `docs/architecture/17-seam-inbound-law.md:71` · C-исполненное-как-открытое — Загрузчик сида СТРОГИЙ с пака входной двери: `backend/internal/seed/decode.go` называет строку 212 своим именем и ставит `dec.KnownFields(true)` — неи + +- `N048` major · `docs/architecture/17-seam-inbound-law.md:97` · C-исполненное-как-открытое — Первая строка таблицы «Двери, УЖЕ подразумеваемые живыми строками» описывает как ПРЕДСТОЯЩУЮ ту самую дверь, которую собственная шапка дока объявляет + +- `N049` major · `docs/architecture/17-seam-inbound-law.md:100` · C-исполненное-как-открытое — Строка 231 закрыта паком «деньги» (D39.170, 29.08) и снята с бэклога — сам PROGRESS.md говорит это в лоб. Док цитирует её ДВАЖДЫ как живую: на строке + +- `N115` minor · `docs/architecture/16-events-emitter.md:3` · E-второй-носитель — Движок с 31.08 (`bb541a8`) пишет `StreamVersion = "1.2"` — минор поднят под `Finished.Volume` (D39.181 п.2). 1.1 сегодня — это версия ЧИТАТЕЛЯ платфор + +- `N116` minor · `docs/architecture/16-events-emitter.md:90` · C-исполненное-как-открытое — PD-60 (строка 88, «открыт») и PD-61 (строка 90, «открыт») оба стоят в регистре как `fixed(P6, дерево сессии)`. Шапка дока баннирует только «PD-113 отк + +- `N117` minor · `docs/architecture/16-events-emitter.md:114` · B-мёртвый-указатель — Тела D39.119/122/123 уехали в слайс `docs/archive/architecture/05-decisions-D39-106-123.md`, где всего 210 строк — номера 394/416/428/430 там не сущес + + +**Б7 — Движковые арх-доки: 03, 09, 10, 12** + +- `N023` major · `docs/architecture/03-implementation-notes.md:64` · A-дрейф — Шапка строки 3 объявляет §3-контракты «живым референсом бэкенда», а формула §3.1 не совпадает с кодом ни одним полем-разделителем: реальный RequestHas + +- `N024` major · `docs/architecture/03-implementation-notes.md:262` · D-противоречие-ратифицированному — D30 п.1 ратифицировал ПРЯМО ОБРАТНОЕ и с явным supersede: «Флип D1: редактор — БИЛИНГВАЛЬНЫЙ (черновик + исходник), новый дефолт». Реестр решений поме + +- `N026` major · `docs/architecture/09-target-architecture.md:12` · A-дрейф — Канон стоит на 0.9.0, а не на 0.8.0: минор 0.9.0 залендён 31.08 паком платформы P12 (D39.180), и ОБА названных в этой же строке носителя показывают 0. + +- `N027` major · `docs/architecture/10-prompt-architecture.md:51` · C-исполненное-как-открытое — Вопрос закрыт и ПОСТРОЕН: WS2 пака-11 отрендерил редакторский констрейнт-блок именно как src→dst-мэппинг, и D39.13 ратифицировал это дословно как «зак + +- `N028` major · `docs/architecture/10-prompt-architecture.md:52` · C-исполненное-как-открытое — Q4a ИЗМЕРЕН 18.07 и ратифицирован D39.9 («верность НЕ покупается на translate-стадии»), а топологию ролей решил эксп-21 (D39.117: full-regen, починка + +- `N092` minor · `docs/architecture/03-implementation-notes.md:212` · B-мёртвый-указатель — Ни `dc6ea8e`, ни `a32ec2e` в репозитории не существуют — `git cat-file -t` отвечает «Not a valid object name» на оба, при том что тот же вызов на реал + +- `N093` minor · `docs/architecture/03-implementation-notes.md:267` · C-исполненное-как-открытое — Запрошенный «структурный запрет» построен и ратифицирован дважды: (1) на загрузке — `config/pipeline.go:731` отказывает конфигу, где модель провайдера + +- `N095` minor · `docs/architecture/09-target-architecture.md:12` · A-дрейф — Механизмов, которых таблица не знает, уже не два: 31.08 (после последней правки дока) залендён пак D39.181/182 «закон раскрытия», добавивший в шов ДВА + +- `N096` minor · `docs/architecture/09-target-architecture.md:178` · B-мёртвый-указатель — Токен `H14-inversion-guard` в ледджере, на который §3 явно ссылается заголовком раздела, не встречается ни разу: реальный идентификатор — `H14-D38.4-t + +- `N097` minor · `docs/architecture/10-prompt-architecture.md:8` · B-мёртвый-указатель — `08-sync-audit-ledger.md` вынесен в `docs/archive/architecture/` 25.07 как отработанный (санкция владельца, `docs/archive/README.md:5`), и рядом с `09 + +- `N098` minor · `docs/architecture/10-prompt-architecture.md:17` · A-дрейф — Механизм, названный здесь как построенный, СНЯТ паком-15: пар-ключевая карта `prompts:` в конфиге сегодня не просто не используется — она отвергается + +- `N099` minor · `docs/architecture/10-prompt-architecture.md:18` · B-мёртвый-указатель — Строка 366 живого D-лога сегодня пустая и лежит внутри эры D39.135 (приёмка фронта S4, 15.08) — то есть на 250 строк мимо цели и в чужой эре. Цитируем + +- `N100` minor · `docs/architecture/10-prompt-architecture.md:33` · A-дрейф — Инвентарь пар-специфики в телах промтов неполон с 24.07: паком-13 в ОБА боевых промпта вписан блок «Единицы и приёмы (общие для zh→ru)» с зашитыми кит + +- `N101` minor · `docs/architecture/10-prompt-architecture.md:41` · C-исполненное-как-открытое — exp15 как гейт ПАЛ: эксперимент исполнен и залендён 25.07 в редакции REV.2 (D39.7), потрачено $12.79. §4 п.2 того же дока это уже фиксирует («exp15 ЗА + +- `N102` minor · `docs/architecture/12-go-style-notes.md:8` · C-исполненное-как-открытое — Норма как норма жива, но её единственный пример — мёртвый указатель, и он указывает ровно в обратную сторону. `backend/internal/pipeline/checkers_zh_r + +- `N103` minor · `docs/architecture/12-go-style-notes.md:16` · B-мёртвый-указатель — Идентификатора `glossaryBlockHeader` в дереве нет: константа с русским заголовком жила в `internal/pipeline/memory.go:504` и удалена тем же паком-14 ( + +- `N104` minor · `docs/architecture/12-go-style-notes.md:17` · A-дрейф — Фактический хвост нормы («в код … не возвращаются») сегодня ложен: в НЕтестовом Go живут 8 упоминаний, все в комментариях — `cmd/tmctl/main.go:313`, ` + + +**Б8 — Корневые и онбординг: CLAUDE.md, README, STACK, glossary, product-requirements** + +- `N022` major · `docs/README.md:13` · A-дрейф — Канон контракта уехал до 0.9.0 (заленджен 31.08, D39.180), а зонная копия стоит на 0.2.3 от 15.08 — 2999 строк против 1444. «Байт-зеркало» сегодня ЛОЖ + +- `N050` major · `docs/glossary.md:11` · C-исполненное-как-открытое — P12 принят и залёнджен 31.08 нотой D39.180 (коммит 0a680a3), обязательство D39.158 закрыто целиком, регистр: major 2 против 7. Глоссарий лежит в онбор + +- `N051` major · `docs/glossary.md:22` · E-второй-носитель — В START_PROMT.MD есть секция V6 от 18.08.2026 (идея переделки архитектуры пайплайна), разобранная в реестр 23.08 строками ПТ-37..ПТ-43. Парный носител + +- `N052` major · `docs/product-requirements.md:32` · E-второй-носитель — Число — июльские деньги ДО пере-пина цен DeepSeek 13–15.08. Два других живых носителя того же числа несут ⚠-оговорку про множитель ×4.2–4.4 (STACK.md) + +- `N053` major · `docs/product-requirements.md:56` · A-дрейф — P12 залёнджен 31.08 (0a680a3) вместе с минором, канон в дереве стоит на 0.9.0. Верно: канон 0.9.0 с 31.08 (D39.180), зеркало фронта отстаёт уже на СЕМ + +- `N054` major · `docs/product-requirements.md:63` · D-противоречие-ратифицированному — D39.147 (слово владельца 17.08) АМЕНДИРОВАЛ условие разморозки: «лендинг P7 зону БОЛЬШЕ НЕ размораживает», S5-промт не выдаётся, разморозка — отдельны + +- `N068` minor · `CLAUDE.md:8` · B-мёртвый-указатель — Путь написан от `docs/`, а файл — корневой: от корня репозитория `architecture/09-target-architecture.md` не существует; реальный путь — `docs/archite + +- `N069` minor · `CLAUDE.md:21` · B-мёртвый-указатель — Второй уцелевший docs-относительный путь, битый ДВАЖДЫ: нет префикса `docs/` И нет расширения `.md`. Реальный носитель — `docs/architecture/12-go-styl + +- `N070` minor · `CLAUDE.md:93` · A-дрейф — Число родилось верным и сгнило дрейфом. На 24.08 (ff03dca — коммит, вписавший фразу) голова, как её определяет титул D-лога («голова D39.124+ (подрезк + +- `N083` minor · `docs/README.md:20` · A-дрейф — Половина фразы верна, половина протухла. `--check` действительно гейчен составом коммита (`docs/scripts/githooks/pre-commit:74`). `--lint` НЕ гейчен н + +- `N084` minor · `docs/README.md:31` · A-дрейф — Правило не исполнено ровно для одного файла — и это тот файл, который холодная бэкенд-сессия увидит первым в `ls docs/`. `BACKEND_COLDRUN_V16_SESSION_ + +- `N085` minor · `docs/STACK.md:7` · B-мёртвый-указатель — Блок стадии draft лежит на строках 60–84 (`stages:` — 59, `- name: draft` — 60, следующая стадия `- name: edit` — 85). На заявленных 36–60 сегодня бло + +- `N086` minor · `docs/STACK.md:15` · B-мёртвый-указатель — Блок `escalation` лежит на строках 142–159 (`escalation:` — 142, `chains:` — 143, `default:` — 153, `adult:` — 154, `budget_usd: 0` — 159). На заявлен + +- `N087` minor · `docs/STACK.md:16` · B-мёртвый-указатель — Указатель РОДИЛСЯ ВЕРНЫМ 09.08 (085dbb9): строка 6 `docs/PROGRESS.md` тогда читалась «**Открыто на владельце:** прогон Sol-пакетов руками (150) · кред + +- `N118` minor · `docs/glossary.md:7` · B-мёртвый-указатель — В файле docs/ORCHESTRATOR_SESSION_PROMPT.md (240 строк) символа «№» нет НИ РАЗУ: его шапка намеренно номер не держит — «Счётчик ролей ОДИН, номер выда + +- `N119` minor · `docs/product-requirements.md:50` · B-мёртвый-указатель — Сам факт ЖИВ (проверено кодом, см. positive_control), но указатель «очередь №20» умер дважды: роль передана №20→№21 (D39.177, 31.08) и №21→№22 (D39.18 + +- `N120` minor · `docs/product-requirements.md:57` · B-мёртвый-указатель — Якорь РОДИЛСЯ верным 02.08 (fbefe33: тогда 89-93 были ровно комментарием «the --pairs column aligned byte-for-byte to how the edit wave concatenated t + + +**Б9 — Ранние арх-доки 01/04/06 — баннеры или архив** + +- `N025` major · `docs/architecture/06-memory-risk-registry.md:81` · B-мёртвый-указатель — Колонка «Где в коде/схеме» реестра указывает шесть file:line-якорей, и пять из шести уехали: реальные адреса — MessagesWithInjection render.go:246 (а + +- `N088` minor · `docs/architecture/01-decisions.md:19` · A-дрейф — Каталога `pkg/` в бэкенде нет вовсе; план исполнен под `internal/`: internal/llm, internal/ledger, internal/obs, internal/store. Имена совпали, префик + +- `N089` minor · `docs/architecture/01-decisions.md:39` · A-дрейф — FTS5 в движке нет НИ на горячем, ни на холодном пути: во всём backend/ ноль виртуальных таблиц и единственное упоминание FTS5 — отрицание в комментари + +- `N090` minor · `docs/architecture/01-decisions.md:55` · B-мёртвый-указатель — Обоих коммитов в истории репозитория НЕ существует (`git cat-file -t c7f8a95` → «Not a valid object name»). Реальные коммиты того же дня и того же сод + +- `N091` minor · `docs/architecture/01-decisions.md:93` · A-дрейф — Отдельного файла БД под банк памяти НЕТ и не было построено: у книги ровно ОДНА база `.db` (config/book.go:189), и банк памяти живёт в ней же + +- `N094` minor · `docs/architecture/04-unhappy-paths.md:120` · C-исполненное-как-открытое — Паспорт качества главы ПОСТРОЕН и отдаётся `tmctl status`: тип `pipeline.ChapterPassport` (internal/pipeline/status.go:157-159, «the per-chapter quali + + +**Б10 — ЧУЖАЯ ЗОНА (санкция владельца 01.09): бэкенд-доки** + +- `N007` major · `backend/README.md:11` · A-дрейф — Перечень команд читается исчерпывающим и отстал на ТРИ верба из одиннадцати: `backup`, `seed-lint`, `bank-apply`. `bank-apply` — это ратифицированная + +- `N008` major · `backend/README.md:24` · A-дрейф — Машинной стоп-таблицы (`.bank-stop.json`) в движке НЕТ — её снёс пак «входная дверь шва» (d1eb8a9, ратифицирован D39.158); уцелевшая `.bank-stop.txt` + +- `N009` major · `backend/README.md:82` · D-противоречие-ратифицированному — От `$HOME` НЕ дефолтится НИЧЕГО. `internal/standdata` резолвит корпус ТОЛЬКО от корня репозитория (`/books`), и его собственная шапка говорит пр + +- `N010` major · `backend/README.md:99` · C-исполненное-как-открытое — Долг ПОГАШЕН и это записано в двух носителях. Код по обоим якорям сегодня говорит обратное README: repair.go:131 — «The boundary is the TARGET's decla + +- `N011` major · `backend/README.md:101` · B-мёртвый-указатель — Строка 53 в едином трекере не существует: она закрыта D39.82 (носитель этого сказан в самой таблице — строка 111: «D39.82 закрыл строку 53 через `putU + +- `N012` major · `backend/docs/D15.2-content-addressed-resume-spec.md:529` · A-дрейф — Голова схемы сегодня v16, а не v7, и номер v8 давно занят чужой миграцией. Спека при этом ЖИВА: строка бэклога 49 («Этапы Б+В спеки D15.2») открыта, и + +- `N071` minor · `backend/README.md:20` · B-мёртвый-указатель — Экспортированной `chunk.Ingest` не существует — в пакете живёт неэкспортированная `ingest(p string)`, обёртка над `IngestEncoded`. Родилась ошибка в Т + +- `N072` minor · `backend/README.md:94` · B-мёртвый-указатель — README объявляет, что все его «строка N» разрешаются в таблицу «Бэклог», и цитирует тринадцать номеров. Семь из них в таблице отсутствуют — закрыты и + +- `N073` minor · `backend/docs/MONEY_HONESTY_REPORT.md:787` · C-исполненное-как-открытое — Заленджено — коммитом bb541a8 (31.08) и принято нотой D39.182. Фраза стоит в секции, которая сама называет себя «§11. Последний абзац — СОСТОЯНИЕ, не + +- `N074` minor · `backend/docs/SEAM_FIX3_PLAN.md:1` · F-целиком-историческое — Четыре записки-плана паков шва — чистые списки намерений отработавших сессий. Паки приняты и заленджены (D39.158: «Четыре пака зоны бэкенда приняты и + + +**Б11 — ЧУЖАЯ ЗОНА (санкция владельца 01.09): фронт-доки** + +- `N055` major · `frontend/README.md:7` · A-дрейф — S4 исполнен 14.08 и ПРИНЯТ 15.08 (D39.135, `267aa35`): библиотека, интейк, форма запуска прогона, настройки, читалка — построенные экраны. Перечислени + +- `N056` major · `frontend/README.md:66` · D-противоречие-ратифицированному — Зеркало НЕ байт-равно: 0.2.3/16 операций против канона 0.9.0/20 операций, разные md5. И правило «зеркало = байт-копия» ратифицированно ПРИОСТАНОВЛЕНО + +- `N057` major · `frontend/docs/BACKLOG.md:50` · D-противоречие-ратифицированному — D39.144 (16.08) снял гейт полноты. Канон: `awaiting_bank` с ЛЮБЫМ состоянием решений → 202. Якорь `mining.go:201` тоже уехал: там сегодня стоит прямая + +- `N058` major · `frontend/docs/BACKLOG.md:51` · B-мёртвый-указатель — В каноне 0.9.0 нет ни схемы `BankDecision`, ни значения `promote`, ни пути `POST …/bank/decisions`, ни «частичного накопления»: канон 0.5.0 снёс их и + +- `N059` major · `frontend/docs/BACKLOG.md:62` · C-исполненное-как-открытое — Канон 0.9.0 перевесил поток на КНИГУ: `GET /books/{bookId}/events`, и его описание прямо называет причину — «a book is received and cut into chapters + +- `N060` major · `frontend/docs/BACKLOG.md:63` · C-исполненное-как-открытое — Ручка построена: канон 0.9.0 даёт `GET /capabilities` с обязательным `language_pairs` («Every pair the deployment knows about, unavailable ones includ + +- `N061` major · `frontend/docs/BACKLOG.md:68` · C-исполненное-как-открытое — Канон 0.9.0 несёт и `PATCH /books/{bookId}` (merge patch, только `title`, 409 при живом прогоне), и `DELETE /books/{bookId}` (204, 409 при живом прого + +- `N062` major · `frontend/docs/FRONTEND_PLAN.md:692` · D-противоречие-ратифицированному — Ратифицированный канон 0.9.0 кладёт на провод ОДНУ пару {done,total}, считаемую в ГЛАВАХ по всем проходам, плюс открытую строку `stage`. Клиент, напис + +- `N063` major · `frontend/docs/STACK_DECISIONS.md:245` · C-исполненное-как-открытое — Все ЧЕТЫРЕ пункта («пофазный прогресс», «персист манифеста глав и чанков», «JSON-выход таблицы подписи банка», «приём внешнего TRACEPARENT в cmd/tmctl + +- `N121` minor · `frontend/README.md:21` · A-дрейф — Маршрутов ВОСЕМЬ: S4 завела `/intake`, и он есть в обоих замкнутых тестом списках. Приёмка S4 в том же дереве печатает «8 маршрутов, 11 сценариев». + +- `N122` minor · `frontend/docs/BACKLOG.md:3` · B-мёртвый-указатель — Строк 99–102 в живом `docs/PROGRESS.md` больше нет — все четыре закрыты и по норме журнала («закрытые строки здесь НЕ хранятся») ушли в D-лог. Шапка у + +- `N123` minor · `frontend/docs/BACKLOG.md:23` · B-мёртвый-указатель — Токена `--color-hint` в зоне больше нет вовсе — ни в `tokens.css`, ни в тестах, ни в коде; строка держит хвост на несуществующем токене. Вторая полови + +- `N124` minor · `frontend/docs/FRONTEND_SESSION_PROMPT.md:2` · B-мёртвый-указатель — `S3_SESSION_PROMPT.md` в зоне не существует: промты S3/S3.5/S3.7 архивированы 14.08 в `docs/archive/prompts/FRONTEND_S3*`, зонные копии сняты. Довод б + +- `N125` minor · `frontend/docs/STACK_DECISIONS.md:111` · B-мёртвый-указатель — Зона зависит от пакета `playwright` 1.62.1, а не от `@playwright/test`: последнего нет ни в `package.json`, ни в дереве `node_modules`. Сессия, постав + +- `N126` minor · `frontend/docs/STACK_DECISIONS.md:116` · E-второй-носитель — Шагов у `check` шесть, и spectral — ЧЕТВЁРТЫЙ. Тот же файл на строке 148 уже утверждает «шесть шагов», то есть носитель противоречит сам себе; вторая + +- `N127` minor · `frontend/docs/frontend-PROGRESS.md:221` · C-исполненное-как-открытое — Приёмка S4 состоялась 15.08 и записана В ЭТОМ ЖЕ ФАЙЛЕ строкой 465 («ПРИНЯТО и залендено `267aa35`, D39.135»). Два места одного файла говорят разное. + + +**Б12 — ЧУЖАЯ ЗОНА (санкция владельца 01.09): платформенные доки** + +- `N006` блокер · `platform/docs/STACK_DECISIONS.md:414` · D-противоречие-ратифицированному — Строка (и парная ей 415 про `mined_rejects`) неверна в четырёх местах сразу. (1) `mined_delta` БОЛЬШЕ НЕ «путь из book.yaml»: ключ РЕТИРОВАН — `Retire + +- `N064` major · `platform/README.md:10` · A-дрейф — Перечень устарел с лендингом пака sqlc 29.08: `check: build vet fmt lint sqlc-check` — пятый шаг `sqlc-check` в списке отсутствует. Хуже перечня — умо + +- `N065` major · `platform/deploy/README.md:252` · C-исполненное-как-открытое — Глагол `tmctl migrate` существует и заленджен (`backend/cmd/tmctl/migrate.go`, в диспетчере `main.go:184/:245`, свой код выхода `exitSchemaMismatch = + +- `N066` major · `platform/docs/PLATFORM_DIRECTION.md:159` · C-исполненное-как-открытое — sqlc ВЗЯТ и заленджен 29.08 (`D39.172`: «Пак `sqlc` принят и заленджен: 40 запросов пяти файлов `pgstore` на типизированный слой, пин 1.31.1, `sqlc di + +- `N067` major · `platform/docs/STACK_DECISIONS.md:621` · B-мёртвый-указатель — Лечебная команда «самой частой грабли стенда» указывает на путь, которого не существует: `tm-runs.slice` по конвенции имён systemd вложен в `tm.slice` + +- `N128` minor · `platform/deploy/README.md:428` · C-исполненное-как-открытое — Воркер существует с пака P4: прогон спавнится в `internal/runs/spawn.go`, который задаёт `Workdir: l.Workdir` транзиентному юниту, а корень УЖЕ настро + +- `N129` minor · `platform/docs/PLATFORM_DIRECTION.md:101` · B-мёртвый-указатель — Указатели на строки единого бэклога, розданные по всему срезу, в шести случаях целят в строки, которых в живой таблице БОЛЬШЕ НЕТ: правило бэклога — « + +- `N130` minor · `platform/docs/STACK_DECISIONS.md:566` · E-второй-носитель — Перечень админ-команд отстал от CLI на семь глаголов. В диспетчере `cmd/tmplatformctl/main.go` сегодня: grant · adjust · balance · logins · revoke · b + + +## Сверх находок — работы заказа, которых в реестре нет + +Реестр отвечает на «что протухло». Заказ владельца шире, и эти пункты в него не попали: + +- **Б13. Архивация отработавшего (0в).** Промт `BACKEND_COLDRUN_V16_SESSION_PROMPT.md` лежит в + живом `docs/`, при этом ДВА носителя (таблица `README.md` и CURRENT-STATE) уже утверждают, что + он в `archive/prompts/`. Плюс исполненные план-доки зон: `backend/docs/SEAM_*`, + `MONEY_HONESTY_*`, `platform/docs/p8-review/`, `platform/docs/p9/`. +- **Б14. Планы доработок в бэкенд и платформу (0б).** По остатку единого бэклога (189 строк, + 59 «скоро») и регистра платформы (435 строк, 97 открытых, major 2). +- **Б15. Хвост лендинга ресёрча.** Очередь п.5 велит новой смене лендить файл, лендинг которого + владелец 01.09 откатил (`162ab9b`). Пока формулировка стоит, следующая смена повторит откат. + **Формулировку задаёт владелец — сам не переписываю.** diff --git a/docs/DOC_REVISION_FINDINGS.md b/docs/DOC_REVISION_FINDINGS.md new file mode 100644 index 00000000..a616e83d --- /dev/null +++ b/docs/DOC_REVISION_FINDINGS.md @@ -0,0 +1,2565 @@ +# РЕЕСТР НАХОДОК РЕВИЗИИ ДОКОВ — 01.09.2026, оркестратор №22 +Источник: воркфлоу `docs-staleness-revision-A`, 15 срезов, 30 агентов, 1432 клейма проверены +исполнением, 115 положительных контролей. Найдено 137, адверсариальный пере-суд снял 7. +**Живых находок: 130** — блокеров 6, major 61, minor 63. +Колонка ВЕРДИКТ: ПОДТВЕРЖДЕНО = прошло адверсариальный пере-суд; (пусто) = не выносилось на пере-суд +(не разрушающее и не блокер) — такая находка проверена ОДНИМ агентом и на доверии не исполняется. + +--- + +## N001 · БЛОКЕР · `docs/PROGRESS.md:176` · C-исполненное-как-открытое +**Срез:** ЕДИНЫЙ БЭКЛОГ docs/PROGRESS.md — таблица (строки 57–272 файла), проверка на класс C «исполненное как открытое» +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** их деньги не входят НИ в число согласия, НИ в `projected_book_usd`, при этом на сдвиге снапшота они реально пере-покупаются +**Почему протухло:** Строка 194 ЗАКРЫТА лендингом 31.08 (`bb541a8`, экземпляр A10 закона раскрытия): `projectBookUSD(...) + r.bankRoleCommittedUSD()` уже стоит в `status.go:801`, а посадочный тест `bankrolemoney_test.go` в первой же строке комментария называет себя «backlog row 194». Вдобавок ОСЬ строки опровергнута живыми данными: пере-покупка терминолога наступает не «на сдвиге снапшота», а от БАЙТОВ БАТЧА (пинг №2 той же сессии, `docs/PROGRESS.md:344`). +**Улика — команда:** `grep -rn 'bankRoleCommittedUSD' backend --include=*.go; sed -n '13,18p' backend/internal/pipeline/bankrolemoney_test.go` +**Улика — вывод:** backend/internal/pipeline/rebill.go:527:func (r *Runner) bankRoleCommittedUSD() float64 { +backend/internal/pipeline/status.go:801: rep.ProjectedBookUSD = projectBookUSD(units, byChunk, len(draftStages), len(editStages), rp) + r.bankRoleCommittedUSD() +backend/internal/pipeline/bankrolemoney_test.go:30:// Mutation this catches: drop the `+ r.bankRoleCommittedUSD()` addend and the projection stops covering +--- +// bankrolemoney_test.go: backlog row 194 — the money of the BANK ROLES was outside every projection an +// operator decides on. +**Другие носители того же факта:** docs/PROGRESS.md:306 (секция «Бэкенд» той же сессии: «A10 (контур банк-ролей в проекции, `committed_usd` не сдвинулся — предъявлено тестом)»), docs/PROGRESS.md:344 (пинг №2: ось строки 194 опровергнута), backend/docs/DISCLOSURE_LAW_DESIGN.md:447 («A10 — деньги банковых ролей вне контура согласия (строка 194)»), backend/docs/MONEY_HONESTY_REPORT.md +**Предложенное действие:** Закрыть строку 194 нотой лендинга пака «деньги и честность» (экземпляр A10) и убрать её из таблицы; в теле ноты сохранить опровержение оси — пере-покупка терминолога идёт от байтов батча, а не от сдвига снапшота. +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Закрыть строку 194 — верно, но в ноту закрытия обязаны войти ДВА опровержения её текста, а не одно. Кроме оси «сдвиг снапшота» (опровергнута пингом №2, `docs/PROGRESS.md:344` — пере-покупка идёт от БАЙТОВ БАТЧА) ЛОЖНА и первая половина клейма «не входят НИ в число согласия»: тест утверждает раздельно, что `committed_usd` = `SUM(checkpoints)` НЕ сдвинулся, потому что деньги банк-ролей были в нём ВСЕГДА (spend и checkpoints пишутся одной транзакцией) — слепа была только ПРОЕКЦИЯ. Плюс назвать честную границу из §9.9: в проекцию добавлено УЖЕ ПОТРАЧЕННОЕ, а не прогноз контура, то есть проекция стала НИЖНЕЙ границей. + +--- + +## N002 · БЛОКЕР · `docs/PROGRESS.md:200` · C-исполненное-как-открытое +**Срез:** ЕДИНЫЙ БЭКЛОГ docs/PROGRESS.md — таблица (строки 57–272 файла), проверка на класс C «исполненное как открытое» +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** при том же снапшоте строка идёт как «$0 resume» без сверки — `translate` на такой книге перекупает юниты БЕЗ предупреждения в смете +**Почему протухло:** Строка 238 ЗАКРЫТА тем же лендингом 31.08 (`bb541a8`, экземпляр A6 «проекция видит правку исходника»). Посадка `rebillsource_test.go` называет строку по номеру и описывает ровно её сценарий; мутация, которую тест ловит, — возврат голого `if cs.SnapshotID == cur { continue }`, то есть именно того кода, о слепоте которого писала строка. +**Улика — команда:** `sed -n '13,27p' backend/internal/pipeline/rebillsource_test.go` +**Улика — вывод:** // rebillsource_test.go: backlog row 238 — the re-payment projection was blind to an in-place SOURCE edit. +// The cold run of 31.08 did not run this probe on purpose ... so the defect stood as a code reading. This is that reading turned into a fixture. +// ... the projection's same-snapshot branch used to wave them through as «$0 resume» ... +// TestTheProjectionSeesAnInPlaceSourceEdit is the landing. +// Mutation this catches: restore the bare `if cs.SnapshotID == cur { continue }` and RebillUnits drops to zero while `translate` still charges +**Другие носители того же факта:** docs/PROGRESS.md:282 (шапка секции «Бэкенд»: «Свободная работа зоны — строками бэклога: 238 … · 239 …»), docs/PROGRESS.md:306 (та же секция: «A6 (проекция видит правку исходника)»), backend/docs/MONEY_HONESTY_REPORT.md (таблица касаний, `internal/pipeline/rebill.go` — A6) +**Предложенное действие:** Закрыть строку 238 нотой лендинга (A6) и убрать её из таблицы; одновременно снять её из списка «свободной работы зоны» в шапке секции «Бэкенд» (docs/PROGRESS.md:282), где она предлагается как незанятая. +**Разрушающее:** True + +--- + +## N003 · БЛОКЕР · `docs/PROGRESS.md:201` · C-исполненное-как-открытое +**Срез:** ЕДИНЫЙ БЭКЛОГ docs/PROGRESS.md — таблица (строки 57–272 файла), проверка на класс C «исполненное как открытое» +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** `Status.checkWave` не видит исчезнувшую из конфига стадию +**Почему протухло:** Строка 239 ЗАКРЫТА лендингом 31.08 (`bb541a8`, экземпляр A7+A12). Правило `exportConfigDrift` не «перенесено», а сведено в ОДНО общее определение `orphanStageRows`, и `status.go` зовёт его прямо рядом с `checkWave`, комментарием называя строку 239 по номеру. Тест-половина заказа тоже есть (пак сдал 33 посадки, `driftbasis_test.go`). +**Улика — команда:** `sed -n '735,741p' backend/internal/pipeline/status.go; grep -rn 'orphanStageRows' backend --include=*.go | head -4` +**Улика — вывод:** checkWave(draftSnaps, waveDraft) + checkWave(editSnaps, waveEdit) + // A stored row for a stage the current pipeline does not run — the rule `export` has had all + // along and this surface did not, reproduced verbatim on the cold run (backlog row 239). ONE + // definition, shared: orphanStageRows. + if stage, orphan := orphanStageRows(statuses, draftStageNames, editStageNames); orphan { + rep.ConfigDrift = true +**Другие носители того же факта:** docs/PROGRESS.md:282 (список свободной работы зоны), docs/PROGRESS.md:306 (той же секцией объявлено исполненным), backend/docs/MONEY_HONESTY_REPORT.md (строки таблицы `internal/pipeline/status.go` и `export.go` — A7+A12) +**Предложенное действие:** Закрыть строку 239 нотой лендинга (A7+A12) и убрать её из таблицы и из списка свободной работы зоны в docs/PROGRESS.md:282. +**Разрушающее:** True + +--- + +## N004 · БЛОКЕР · `docs/architecture/15-money-path.md:31` · D-противоречие-ратифицированному +**Срез:** Денежный путь — docs/architecture/15-money-path.md +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** реальная цена главы замерена по движковым леджерам — средняя **$0.0115–$0.0190**, максимум одной главы $0.0375, то есть константа завышена в **1.6–2.6×** … и «купить 10 глав» отдаёт движку $0.30, покупающий ~16–26 глав +**Почему протухло:** Числа взяты из D39.165 §1 (28.08) и меряны на леджерах ДО ценового перелома. D39.179 п.1 (31.08) ратифицировал: DeepSeek пере-пинен под цены, вступившие 16.08 16:00 UTC, множитель к июльским деньгам ×4.47. Мой замер по леджеру coldrun-v16 (прогон 31.08, сегодняшние цены, $0, только чтение): полная цепь главы 1 = $0.092934, главы 2 = $0.150865, а ТОЛЬКО черновик главы 8 = $0.076785 — то есть даже черновик вдвое дороже названного в доке АБСОЛЮТНОГО максимума $0.0375. Знак утверждения перевернулся: константа $0.03 сегодня ЗАНИЖЕНА примерно втрое, $0.30 покупает ~3 главы, а не 16–26, и ⛔ «константу по сегодняшним числам не калибровать» защищает ставку НИЖЕ себестоимости. ‘Исторический’ баннер над провенансом не спасает: вывод про ручку «главы» стоит в НАСТОЯЩЕМ времени как живое описание дефекта PD-410. +**Улика — команда:** `sqlite3 -header "file:/home/ubuntu/projects/textmachine-main/books/gu-zhenren/coldrun-v16/guzhenren-coldrun-v16.db?mode=ro" "select j.chapter, round(sum(c.cost_usd),6) usd, group_concat(distinct c.stage) stages from checkpoints c join jobs j on j.id=c.job_id where j.chapter in (1,2,8) group by j.chapter;"` +**Улика — вывод:** chapter|usd|stages +1|0.092934|draft,edit +2|0.150865|draft,edit +8|0.076785|draft + +Ратифицированная эррата (docs/architecture/05-decisions-log.md:1667, D39.179 п.1): «Прежняя цифра очереди «≈$0.05» СНЯТА как протухшая: DeepSeek пере-пинен коммитом 15.08 (76049bb, D39.137) под цены, вступившие в силу 16.08 16:00 UTC; заход bank-low ($0.045095 в июльских деньгах) по ним стоит $0.2017 — множитель 4.47» +**Другие носители того же факта:** docs/architecture/05-decisions-log.md:1018 и :1020 — тело D39.165 §1, первоисточник чисел (по диете D-лога тела не переписываются, эррата живёт в D39.179 — править там НЕ надо), platform/docs/DEFECT_REGISTER.md:28 — PD-410, пере-диспозиционирована паком P12 по тем же июльским числам (чужая зона — пингом) +**Предложенное действие:** Заменить баннер провенанса на: «⚠ Числа D39.165 §1 — ИЮЛЬСКИЕ ДЕНЬГИ и с 16.08 не действуют: D39.179 п.1 ратифицировал множитель ×4.47 к ценам, вступившим 16.08 16:00 UTC. Пере-замер по леджеру coldrun-v16 (31.08, $0): полная цепь главы $0.093–$0.151, один черновик главы до $0.077. Значит константа $0.03 сегодня ЗАНИЖЕНА ~втрое, а не завышена в 1.6–2.6×, и «купить 10 глав» = $0.30 покупает ~3 главы, а не 16–26. Направление лечения (цена от объёма исходника + объёмный потолок в движке) не меняется, но ЗНАК дефекта PD-410 обратен записанному: ручка не пере-продаёт, а НЕДО-продаёт.» Добавить D39.179 в карту §6 и отпинговать зоне платформы PD-410. +**Разрушающее:** False +**⚠ Опровергатель поправил действие:** Находка верна, предложенный текст — нет, он ставит под удар ровно то, чем прошлая смена уже обожглась. Три правки. (1) НЕ объявлять $0.093–$0.151 ценой главы: это n=2 главы из десяти, прогон остановлен снапшот-гардом (редакторская волна 3/14), и сам отчёт запрещает такое употребление дословно — «Чего замер НЕ должен делать: калибровать ставку платформы. Она гейчена строкой 202 и требует ЗАВЕРШЁННОЙ цепи» (COLDRUN_V16_REPORT_2026-08-31.md:642). Прогон вдобавок нёс объявленные девиации (черновик шёл БАНКНОТНЫМ промптом вместо конвенционного `translator.md`; `escalation.budget_usd` поднят 0.015 → 0.08), спред цены на юнит ×7.6, а причина, по которой редактор съел 73% захода, НЕ УСТАНОВЛЕНА. (2) Несущим доводом сделать РАТИФИЦИРОВАННУЮ арифметику, которая ни от чего этого не зависит: «Числа D39.165 §1 — ИЮЛЬСКИЕ (сама нота, ⛔г: прогоны 10–11.07, редактор другой моделью). D39.179 п.1 ратифицировал ×4.47 к ценам с 16.08 16:00 UTC ⇒ та же средняя сегодня $0.051–$0.085, тот же максимум $0.168. Константа $0.03 НЕ завышена — она ниже даже пере-считанной средней, и «купить 10 глав» = $0.30 покупает единицы глав, а не 16–26.» Замер холодного прогона добавить ОДНОЙ строкой как иллюстрацию с оговоркой n=2: движковая проекция полной цепи тех же десяти глав `projected_book_usd = 1.1377277707` (≈$0.114/глава, `docs/archive/reports/COLDRUN_V16_REPORT_2026-08-31.md:70`) и консервативный пол по ok-вызовам $0.0716/$0.0783. (3) Автор пропустил ВТОРОЙ ЖИВОЙ НОСИТЕЛЬ, и без него класс E разъедется снова: `docs/architecture/17-seam-inbound-law.md:101` несёт те же июльские числа («цена главы измерена ($0.0115–0.0190 против константы продажи $0.03)») — чинить в одной правке. Строка реестра `docs/architecture/05-decisions-index.md:229` их тоже повторяет, но она зеркало ноты: там достаточно указателя на D39.179. И уточнить формулировку про PD-410: строку регистра я прочёл — она несёт только (а)(б)(в)(г) и НЕ содержит ни «16–26 глав», ни «завышена в 1.6–2.6×», поэтому «знак дефекта PD-410 обратен записанному» — перебор; перевёрнуто СЛЕДСТВИЕ в D39.165 §1 и его копия в доке. Пинг зоне платформы послать стоит, но как новую информацию («бета сегодня НЕДО-берёт»), а не как претензию к ряду. ⛔(г) при этом остаётся в силе: правка описывает знак, калибровку по-прежнему держит строка бэклога 202. + +--- + +## N005 · БЛОКЕР · `docs/architecture/15-money-path.md:35` · A-дрейф +**Срез:** Денежный путь — docs/architecture/15-money-path.md +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** Команда ОТКАЗЫВАЕТ, если попытка ещё называет юнит или несёт базовую линию траты — над живым движком прогон не закрывают. Холд возвращается ЦЕЛИКОМ в обоих случаях (к abandon допускается только попытка, не дошедшая до движка, значит она ничего не потратила), а флаг решает лишь КОГДА +**Почему протухло:** Абзац описывает ТОЛЬКО живую ветвь `run abandon`. С пака P11 (PD-385, коммит e548e5a от 29.08) есть вторая ветвь — PHASE=settling: к ней отказ «юнит/базовая линия» НЕ применяется (попытка уже кончилась), она принимает попытку, которая ДОШЛА до движка и потратила, возвращает холд ЦЕЛИКОМ (единственная защита — порог `abandonAfter` неудач расчёта, отказ `ErrSettlementNotStuck`), а флаг `--release-hold` там ИГНОРИРУЕТСЯ: деньги всегда закрываются inline. Все три утверждения абзаца в settling-ветви ложны, и холд возвращается целиком за работу, которую движок реально оплатил. Абзац вошёл в док 22.08 (0e31a8b) и был верен; аудит указателей 31.08 его смысла не касался. +**Улика — команда:** `sed -n '292,300p;307p' /home/ubuntu/projects/textmachine-main/platform/cmd/tmplatformctl/runs.go` +**Улика — вывод:** // ⚠ THE PARAGRAPH ABOVE IS THE LIVE HALF ONLY, since PD-385. On a run whose PHASE is `settling` — +// finished, with a settlement that has failed and will not close — there is no sweep left to hand +// the money to, so the command closes it inline whatever the flag says, and the refusal above does +// not apply because the attempt has already ended. What DOES apply there is a different refusal: a +// settlement that has not failed even once is turned away, because this command returns the hold +// WHOLE and that one is about to be settled correctly for what it really spent. +... +release := fs.Bool("release-hold", false, "... ; ignored for a PHASE=settling run, whose money is always closed inline +**Другие носители того же факта:** platform/deploy/README.md:288-291 — тот же абзац дословно, НО там он починен отдельной секцией «**Строка PHASE = settling — та же команда, другой исход.**» (deploy/README.md:311-318), которой в 15-money-path нет, platform/docs/DEFECT_REGISTER.md:536 (PD-385) и :60 (PD-418) — регистровые носители ветвления +**Предложенное действие:** Дописать в §3 п.7 вторую ветвь: «⚠ С P11 (`PD-385`) ветвей ДВЕ, и абзац выше — только ЖИВАЯ. У прогона с `runs.finished_at is not null` (`PHASE=settling`) отказ по юниту/базовой линии не действует, к abandon допускается попытка, которая ДОШЛА до движка и потратила, холд всё равно возвращается ЦЕЛИКОМ (сдерживает только порог `abandonAfter` неудач расчёта — отказ `ErrSettlementNotStuck`), а `--release-hold` игнорируется: деньги закрываются в той же транзакции (`platform/internal/pgstore/runs.go`, греп `func abandonSettlement(`).» Живой образец формулировки — секция «Строка PHASE = settling» в platform/deploy/README.md. +**Разрушающее:** False +**⚠ Опровергатель поправил действие:** Действие в целом верно (дописать вторую ветвь в §3 п.7, образец — секция «Строка PHASE = settling» в platform/deploy/README.md), но добавить в него ДВЕ оговорки, без которых новый абзац сам станет протухшим: (а) ветвление идёт по `runs.finished_at`, а НЕ по наличию осиротевшей попытки — это ратифицированная оговорка `PD-418`, дословно стоящая в platform/deploy/README.md рядом с образцом; (б) ЖИВОЙ прогон с намертво заблокированной расплатой уходит в ЖИВУЮ ветвь и отказом по юниту закрыт до сих пор — `PD-424` в platform/docs/DEFECT_REGISTER.md стоит `open` именно на этой половине («терминальная РУЧКА»), и абзац, обещавший бы лечение settling-ветвью на живом прогоне, соврал бы. Плюс мелочь фактуры: холд возвращается по КАЖДОЙ осиротевшей попытке прогона, а не по одной (цикл `for _, o := range orphans`). Указатели — греп-формой, как и предложено (`platform/internal/pgstore/runs.go` сегодня чист, но зона под паками движется). + +--- + +## N006 · БЛОКЕР · `platform/docs/STACK_DECISIONS.md:414` · D-противоречие-ратифицированному +**Срез:** Доки зоны платформы: platform/README.md · platform/deploy/README.md · platform/docs/{ENGINEERING_STANDARDS,PLATFORM_DIRECTION,STACK_DECISIONS}.md · platform/docs/p8-review/* · platform/docs/p9/* +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** | `mined_delta` (путь из `book.yaml`) | **писателя в движке НЕТ** — только читатель `loadMinedDelta` +**Почему протухло:** Строка (и парная ей 415 про `mined_rejects`) неверна в четырёх местах сразу. (1) `mined_delta` БОЛЬШЕ НЕ «путь из book.yaml»: ключ РЕТИРОВАН — `RetiredMinedDelta string `yaml:"mined_delta"`` ловит его, чтобы конфиг эры объявляемых путей ПАДАЛ, а сам путь выводится из book_id (`b.MinedDelta = filepath.Join(dir, b.BookID+MinedDeltaSuffix)`). (2) «писателя в движке НЕТ» — ложь: писатель есть, это глагол `bank-apply` (`stageFileAtomic(book.MinedDelta, res.DeltaBytes)`), тот самый СЕДЬМОЙ канал, который дописан в эту же таблицу 31.08 четырьмя строками ниже. (3) Колонка «Атомарность: —» неверна: запись стейджится атомарно + `syncDir`. (4) «развилка ждёт ратификации» — развилка ЗАКРЫТА ратифицированной D39.156 п.3 (23.08, слово владельца): «Развилка 199(а) закрывается РАСТВОРЕНИЕМ… движок дефолтит `mined_delta`/`mined_rejects` по конвенции каталога… писателем становится движковый $0-глагол ⇒ ключей никто не объявляет». Строка 199 единого бэклога закрыта 28.08 (D39.166), её преемник 231 съеден паком «деньги». Цена ошибки не теоретическая: этот же документ на 424 предупреждает, что расширение набора ключей платформой — ратификация, а декодер движка строгий; сессия, прочитавшая «путь из book.yaml», может вписать `mined_delta:` в деплой-шаблон, и тогда НЕ ЗАГРУЗИТСЯ КАЖДАЯ новая книга. +**Улика — команда:** `grep -n 'RetiredMinedDelta string' backend/internal/config/book.go; grep -n 'stageFileAtomic(book.MinedDelta' backend/internal/pipeline/bankdecisions.go; grep -n 'b.MinedDelta = filepath.Join' backend/internal/config/book.go; sed -n '670p' docs/architecture/05-decisions-log.md | cut -c1-260; for n in 199 175; do printf 'row %s in live backlog: %s\n' $n $(grep -c "^| $n " docs/PROGRESS.md); done` +**Улика — вывод:** 115: RetiredMinedDelta string `yaml:"mined_delta"` +455: if deltaStage, err = stageFileAtomic(book.MinedDelta, res.DeltaBytes); err != nil { +199: b.MinedDelta = filepath.Join(dir, b.BookID+MinedDeltaSuffix) +**3. Развилка 199(а) закрывается РАСТВОРЕНИЕМ, а не выбором. Ни один из четырёх вариантов зоны: движок дефолтит `mined_delta`/`mined_rejects` по конвенции каталога … писателем становится движковый $0-глагол ⇒ ключей никто не объявляет** (это тело D39.156, ратифицировано 23.08) +row 199 in live backlog: 0 +row 175 in live backlog: 1 +**Положительный контроль:** grep -c "^| 175 " docs/PROGRESS.md → 1 (шаблон грепа строк бэклога РАБОТАЕТ и находит живые строки 175/202/224), при этом для 199 он даёт 0; правило удаления закрытых строк — docs/PROGRESS.md:59 «Закрытые строки здесь НЕ хранятся — они уходят в D-лог с номером решения» +**Другие носители того же факта:** platform/docs/STACK_DECISIONS.md:415 (парная строка `mined_rejects`, «❌ тот же разрыв»), platform/docs/STACK_DECISIONS.md:424 (хвост строки `book.yaml`: «Прямо относится к развилке 199(а)»), platform/docs/p9/door-live-probe.md:157 (тот же факт — но ВЕРНО: «правка термина через контрактную дверь доезжает до движковых файлов решений»), docs/architecture/17-seam-inbound-law.md:97 +**Предложенное действие:** Пере-написать обе строки таблицы: путь ВЫВОДИТСЯ из book_id (ключи `mined_delta`/`mined_rejects` в book.yaml ретированы и валят конфиг); писатель — движковый глагол `bank-apply`; атомарность — стейдж+rename+syncDir; колонка «Согласовано?» → ✅ с ссылкой на D39.156 п.3 вместо «❌ разрыв… ждёт ратификации». Хвост на 424 «Прямо относится к развилке 199(а)» снять. +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие принимается как есть. Единственное уточнение к формулировке замены: писателем является не «глагол bank-apply» вообще, а функция `writeDecisionFiles` (`backend/internal/pipeline/bankdecisions.go:450`), и порядок переименований в ней НЕ произвольный (rejects первым — аргумент в шапке функции); если таблица описывает атомарность, честнее написать «атомарно ПОФАЙЛОВО (stage+rename+syncDir), но пара файлов НЕ транзакционна — половинное состояние сходится на пере-посылке», иначе колонка «атомарно» пообещает больше, чем код даёт. + +--- + +## N007 · MAJOR · `backend/README.md:11` · A-дрейф +**Срез:** Доки зоны бэкенда: backend/README.md + backend/docs/*.md (9 файлов) +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** | `cmd/tmctl` | CLI: `translate` / `status` … / `redrive` … / `manifest` … / `migrate` … +**Почему протухло:** Перечень команд читается исчерпывающим и отстал на ТРИ верба из одиннадцати: `backup`, `seed-lint`, `bank-apply`. `bank-apply` — это ратифицированная ВХОДНАЯ ДВЕРЬ ШВА (D39.158), то есть единственный способ платформы вернуть движку решения владельца по банку; сессия, читающая карту пакетов, о её существовании не узнаёт. Тем же дефектом страдает «Карта пакетов»: из 18 пакетов `internal/` в ней нет `bookfile`, `runevents`, `standdata` — причём `internal/bookfile` завёл ТОТ ЖЕ коммит 8adcb86, которым строка 11 последний раз переписывалась. +**Улика — команда:** `cd backend && sed -n '183,186p' cmd/tmctl/main.go; ls internal/; for t in bookfile runevents standdata backup seed-lint bank-apply; do printf '%-12s ' $t; grep -q -- "$t" README.md && echo IN_README || echo 'NOT IN README'; done` +**Улика — вывод:** var dispatchCommands = []string{ + "translate", "report", "status", "export", "build", "redrive", "manifest", "backup", "migrate", "seed-lint", + "bank-apply", +} +--- +archguard bookfile checks chunk config lang ledger llm membank miner obs pipeline runevents seed standdata store terminology text +--- +bookfile NOT IN README +runevents NOT IN README +standdata NOT IN README +backup NOT IN README +seed-lint NOT IN README +bank-apply NOT IN README +**Предложенное действие:** Дописать в ячейку `cmd/tmctl`: `bank-apply` (входная дверь шва — приём документа решений владельца по банку, exit 14 «отклонено» / 15 «запись не завершилась», D39.156/D39.158) · `backup` (VACUUM INTO + integrity_check, он же pre-flight боевого прогона, строка 83) · `seed-lint`. В карту пакетов добавить строки `internal/bookfile` (EPUB 3/txt писатель), `internal/runevents` (кадры журнала прогона + словарь исходов), `internal/standdata` (резолв корпусных путей от корня репо). +**Разрушающее:** False + +--- + +## N008 · MAJOR · `backend/README.md:24` · A-дрейф +**Срез:** Доки зоны бэкенда: backend/README.md + backend/docs/*.md (9 файлов) +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** машинная стоп-таблица в `mining.go` (101); все три пишутся через `artifact.go` (write-then-rename — их читают ПОКА идёт прогон) +**Почему протухло:** Машинной стоп-таблицы (`.bank-stop.json`) в движке НЕТ — её снёс пак «входная дверь шва» (d1eb8a9, ратифицирован D39.158); уцелевшая `.bank-stop.txt` пишется ГОЛЫМ `os.WriteFile` (mining.go:198), то есть НЕ через artifact.go и БЕЗ write-then-rename. Реальные четыре клиента writeFileAtomic — bankexport, manifest, карта подписи и авто-банк. Строка родилась верной в 0e69bc1 (09.08, коммит, который машинную таблицу и завёл) и сгнила 27.08. +**Улика — команда:** `cd backend && grep -rn 'writeFileAtomic(' internal/pipeline/*.go | grep -v _test | grep -v 'func writeFileAtomic'; grep -n 'bankStopTablePath()' internal/pipeline/mining.go | head -3; git log --oneline --all -S'TableJSONPath' | cat` +**Улика — вывод:** internal/pipeline/bankexport.go:152: if err := writeFileAtomic(r.bankExportPath(), ... +internal/pipeline/manifest.go:401: if err := writeFileAtomic(r.manifestPath(), ... +internal/pipeline/mining.go:191: if err := writeFileAtomic(r.signatureMapPath(), mapBytes); err != nil { +internal/pipeline/mining.go:722: if err := writeFileAtomic(r.autoBankPath(), []byte(body)); err != nil { +198: if werr := os.WriteFile(r.bankStopTablePath(), []byte(renderBankStopTable(rows)), 0o644); werr != nil { +371: return r.Book.ProjectDB + ".bank-stop.txt" +--- git -S TableJSONPath --- +d1eb8a9 Land the seam inbound door: ... (removal) +0e69bc1 Land the contract-blockers pack: ... machine bank-stop table (introduction) +**Другие носители того же факта:** backend/internal/pipeline/artifact.go:11 — та же протухшая опись клиентов: «the machine bank-stop table (row 101)» перечислена, а карта подписи (mining.go:191) и авто-банк (mining.go:722) — нет +**Предложенное действие:** Заменить хвост ячейки на: «`manifest.go` (структура глав/чанков, строка 100), `bankexport.go` (весь банк тремя статусами, 125), карта подписи и авто-банк в `mining.go` — все четыре через `artifact.go` (write-then-rename); человеческая стоп-таблица `.bank-stop.txt` пишется обычным `os.WriteFile` (машинная `.bank-stop.json` снесена D39.158)». Заодно поправить шапку artifact.go. +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие верное по сути, но опись носителей в находке НЕТОЧНА и предложенная форма сама сгниёт. (1) `artifact.go:11-13` авто-банк УЖЕ перечисляет («and the engine's auto-bank (row 231 …)») — из четырёх реальных клиентов в его шапке не хватает ТОЛЬКО карты подписи (mining.go:191), а лишняя — машинная таблица (row 101). (2) Находка пропустила третий носитель: `backend/docs/pipeline.puml:72` («.bank-stop.json — машине (строка 101): ПОЛНАЯ таблица») — он вне .md-среза, но правится тем же ходом, и `docs/README.md:34` объявляет puml домом диаграмм рядом с кодом. (3) Номера строк бэклога 100/101/125/231 в этой ячейке ВСЕ закрыты (грепом по `^| N |` в docs/PROGRESS.md ноль при 189 живых строках) — это исторические ссылки-заказчики, не указатели в трекер; менять их не нужно, но и добавлять новых номеров в замену не надо. (4) В самом тексте вместо якорей-строк называй символы (`bankExportPath` · `manifestPath` · `signatureMapPath` · `autoBankPath`): именно line-якоря в этом README и протухают быстрее всего. + +--- + +## N009 · MAJOR · `backend/README.md:82` · D-противоречие-ратифицированному +**Срез:** Доки зоны бэкенда: backend/README.md + backend/docs/*.md (9 файлов) +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** Пути дефолтятся ОТ `$HOME` и от корня репозитория (не от `/home/ubuntu`), так что клон под любым пользователем и по любому пути находит то, что есть +**Почему протухло:** От `$HOME` НЕ дефолтится НИЧЕГО. `internal/standdata` резолвит корпус ТОЛЬКО от корня репозитория (`/books`), и его собственная шапка говорит прямо: «never taken from $HOME … the previous address $HOME/books is dead (D39.157 п.2, moved there on 24.08)». Переезд ратифицирован D39.157 п.2. Строки 47/79/80 того же README дают мёртвые адреса `/home/ubuntu/books/gu-zhenren/` и `~/books/...`; на этой машине они «работают» только потому, что /home/ubuntu/books — симлинк в соседний чекаут. +**Улика — команда:** `sed -n '1,14p;76,79p' backend/internal/standdata/standdata.go; awk '/^## D39\.157/,/^## D39\.158/' docs/architecture/05-decisions-log.md | grep -n 'Корень книг'; ls -la /home/ubuntu/books` +**Улика — вывод:** // - The STAND CORPUS — book derivatives that are versioned by their own git repository under +// /books (D39.157 п.2, moved there on 24.08; the previous address $HOME/books is dead). +// Both are derived from the repository root found by MARKER, never counted in ".." and never taken from +// $HOME: a default that reads a home directory is wrong for every machine but the one it was written on +const standRoot = "books" +// StandFile resolves parts against the stand corpus root (/books). +--- +5:**2. Корень книг — `<репозиторий>/books`, отдельный git.** Каталог перенесён владельцем внутрь дерева +--- +lrwxrwxrwx /home/ubuntu/books -> /home/ubuntu/projects/textmachine/books +**Другие носители того же факта:** backend/README.md:47 — «Книга-стенд: `/home/ubuntu/books/gu-zhenren/`», backend/README.md:79 — «`~/books/gu-zhenren/{rerun/records.json, guzhenren-seed-v2.yaml}`», backend/README.md:80 — «`~/books/gu-zhenren/labels/`», backend/cmd/tmctl/backup.go:28 — «the stand book lands under ~/books//…» +**Предложенное действие:** Строку 82 переписать: «Пути дефолтятся ОТ КОРНЯ РЕПОЗИТОРИЯ, найденного по маркеру `backend/go.mod` (`internal/standdata`); корпус — `<репозиторий>/books` (D39.157 п.2). $HOME не читается вовсе.» В строках 47/79/80 заменить `/home/ubuntu/books` и `~/books` на `<репозиторий>/books`. Отдельной строкой — backup.go:28. ⚠ Строка бэклога 217 покрывает только СКРИПТЫ полигона, носителя для этого README-дрейфа в трекере нет. +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие верное; уточняю две вещи. (1) Половина фразы («и от корня репозитория», «не от `/home/ubuntu`») ВЕРНА — переписывать надо не всю строку, а снять только «ОТ `$HOME` и»; предложенная редакция это делает, но не должна выглядеть как опровержение всей строки. (2) По строкам 47/79/80: замена на `<репозиторий>/books` правильна, но проверь, что имелось в виду — на ЭТОЙ машине оба адреса резолвятся (`/home/ubuntu/books` → `/home/ubuntu/projects/textmachine/books`, и `books/` внутри самого чекаута — тоже симлинк туда же), так что «сломано» тут не наблюдаемо и правка чисто анти-дрейфовая: пиши её как «адрес по D39.157 п.2», а не как «путь не работает» — иначе следующая сессия проверит `ls` и решит, что ты ошибся. (3) `backend/cmd/tmctl/backup.go:28` — код бэкенд-зоны, не док: это ПИНГ бэкенд-сессии, а не правка оркестратора. + +--- + +## N010 · MAJOR · `backend/README.md:99` · C-исполненное-как-открытое +**Срез:** Доки зоны бэкенда: backend/README.md + backend/docs/*.md (9 файлов) +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** Остаточные Cyrillic-хардкоды пар-гейтед DC-чекеров (`checks/repair.go:131`, `checks/checkers.go:457,474`) — инертны без dc-данных пары; кандидат на `c.wordScript` (строка 79). +**Почему протухло:** Долг ПОГАШЕН и это записано в двух носителях. Код по обоим якорям сегодня говорит обратное README: repair.go:131 — «The boundary is the TARGET's declared word script, not a hardcoded Cyrillic predicate (строка 79, D39.39)», repair.go:190 — «TARGET-SCRIPT DEBT REPAID (D39.24 §15.3 amended by D39.62/П1)», checkers.go:494 — «data-driven off c.wordScript instead of a hardcoded Cyrillic predicate». Сама строка бэклога 79 фиксирует: «Первые два компонента строки (isCyrLetter→`isTargetWordLetter` · magnitude `SourceScripts`-гейт) исполнены пакетом-чекеров, D39.78». Плюс мёртвые line-якоря: в checkers.go по 457/474 лежит `lintRegisterLexicon`, а нужный комментарий — на 494. +**Улика — команда:** `cd backend && sed -n '131p;190,191p' internal/checks/repair.go; sed -n '494p' internal/checks/checkers.go; grep -nE '^\| *\*{0,2}79\*{0,2} *\|' ../docs/PROGRESS.md | cut -c1-260` +**Улика — вывод:** // TARGET's declared word script, not a hardcoded Cyrillic predicate (строка 79, D39.39). +// TARGET-SCRIPT DEBT REPAID (D39.24 §15.3 amended by D39.62/П1): the word boundary was unicode.Cyrillic in +// BOTH the lint tokenizer and here; it is now the target's DECLARED word script (data), so a non-Cyrillic +// word boundary, data-driven off c.wordScript instead of a hardcoded Cyrillic predicate (строка 79, D39.39). +--- +| 79 | **Граница ПЛОТНОСТИ письма для серий**: `enabled` через Go-константу `cjkScriptNames` ... Первые два компонента строки (isCyrLetter→`isTargetWordLetter` · magnitude `SourceScripts`-гейт) исполнены пакетом-чекеров, D39.78 | +**Предложенное действие:** Снести строку 99 целиком и заменить одной: «Остаток строки 79 — граница ПЛОТНОСТИ письма (`cjkScriptNames` Go-константой); Cyrillic-хардкоды DC-чекеров ПОГАШЕНЫ пакетом-чекеров, D39.78 (`checks/repair.go:190`, `checks/checkers.go:494`).» +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие верное по существу, но предложенная замена воспроизводит ту же болезнь — снова номера строк (`repair.go:190`, `checkers.go:494`), которые уедут при следующей же правке пакета; ровно так и протухли 457/474. Пиши через СИМВОЛЫ: «Cyrillic-хардкоды словесной границы чекеров ПОГАШЕНЫ (`Checkers.isTargetWordLetter` · `brokenWordCandidates`); открытый остаток строки 79 — только граница ПЛОТНОСТИ письма (`cjkScriptNames` Go-константой)». Атрибуцию тоже уточни: код погашение приписывает D39.24 §15.3 с поправкой D39.62/П1, а строка 79 — пакету-чекеров D39.78; называй строку 79 и оба номера, а не один D39.78. + +--- + +## N011 · MAJOR · `backend/README.md:101` · B-мёртвый-указатель +**Срез:** Доки зоны бэкенда: backend/README.md + backend/docs/*.md (9 файлов) +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** Хвост LOW/NOTE-находок адверсариала D39.4 — в ledger-очереди (строка 53). +**Почему протухло:** Строка 53 в едином трекере не существует: она закрыта D39.82 (носитель этого сказан в самой таблице — строка 111: «D39.82 закрыл строку 53 через `putUniqueDC`»), а закрытые строки таблица по своему же правилу не хранит. Хвост D39.4 получил ЯВНУЮ диспозицию «не чинить — легитимно отложено, носитель = D39.4/D39.5 (пересекается с живыми строками 43/78)». README же подаёт это как открытый техдолг в секции, чья шапка гласит «ЕДИНЫЙ трекер — таблица «Бэклог» в `docs/PROGRESS.md`, номера строк = она» — читатель грепает 53 и не находит ничего. +**Улика — команда:** `grep -nE '^\| *\*{0,2}53\*{0,2} *\|' docs/PROGRESS.md; echo "rc=$? (пусто = строки нет)"; grep -n 'закрыл строку 53' docs/PROGRESS.md | cut -c1-160; grep -n 'D39.4 ledger-очередь LOW/NOTE' docs/archive/reports/SMALLPACK_TAILS_2026-08-02.md | cut -c1-300` +**Улика — вывод:** (пустой вывод по '^| 53 |') +rc=1 +251:| 111 | **Дубль ключа пар-файла: фикс накрыл половину.** D39.82 закрыл строку 53 через `putUniqueDC` в `parseDCCheckers`... +90: - *D39.4 ledger-очередь LOW/NOTE:* припаркованная 14 LOW + 14 NOTE хвост пака-1.5 адверсариала (Retries-снапшот · pair-fail-loud · trustGateEvent · golden-глосс-ячейка · пунктуационные огрызки); **не чинить** — легитимно отложено, носитель = D39.4/D39.5 (пересекается с живыми строками 43/78) +**Положительный контроль:** Инструмент в этой области находит: тем же паттерном по таблице извлекается 189 существующих ID — `grep -oE '^\| *\*{0,2}[0-9]+[а-я]?\*{0,2} *\|' docs/PROGRESS.md | wc -l` → 189, среди них 49, 49а, 50, 79, 146, 173, которые README цитирует рядом и которые ЖИВЫ. +**Предложенное действие:** Заменить на: «Хвост LOW/NOTE-находок адверсариала D39.4 — диспозиция «не чинить» (D39.82, строка 53 закрыта); живые пересечения — строки 43/78.» +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие верное, но одну деталь предложенной редакции надо снять: «строка 53 закрыта» D39.82 закрыл СВОЙ предмет (дубль ключа пар-файла через `putUniqueDC`), а хвост LOW/NOTE адверсариала — это ДРУГАЯ сущность, чья диспозиция «не чинить» стоит в docs/archive/reports/SMALLPACK_TAILS_2026-08-02.md:90 с носителем D39.4/D39.5. Склеивать их одной фразой значит завести новый неверный факт вместо старого. Пиши так: «Хвост LOW/NOTE-находок адверсариала D39.4 (14 LOW + 14 NOTE) — диспозиция «не чинить», носитель D39.4/D39.5; живые пересечения — строки 43/78 (прежний указатель на строку 53 мёртв: она закрыта D39.82)». И раз ссылка уходит в docs/archive/reports/, назови файл явно — иначе следующая сессия снова не найдёт, откуда взялась диспозиция. + +--- + +## N012 · MAJOR · `backend/docs/D15.2-content-addressed-resume-spec.md:529` · A-дрейф +**Срез:** Доки зоны бэкенда: backend/README.md + backend/docs/*.md (9 файлов) +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** **Схема**: миграция **v8** (текущий максимум — **v7** после cheap-gate миграции этой сессии: `migrate.go` v7 = `ALTER TABLE retrieval_state ADD COLUMN n_style_flags/style_detail`) +**Почему протухло:** Голова схемы сегодня v16, а не v7, и номер v8 давно занят чужой миграцией. Спека при этом ЖИВА: строка бэклога 49 («Этапы Б+В спеки D15.2») открыта, и README:96 отсылает к спеке как к дизайну-оф-рекорд. Сессия, реализующая §11/§12 по букве, заведёт коллизию номера. Рядом — мёртвый line-якорь `render.go:212`: литерал `tm-request-v2`, о котором спека говорит, лежит на render.go:306, а на 212 — тело подстановщика плейсхолдеров. +**Улика — команда:** `cd backend && grep -n '^\t// v1[0-6]' internal/store/migrate.go | tail -3; grep -n 'func SchemaHead' internal/store/migrate.go; grep -rn 'tm-request-v' --include=*.go internal/ | grep -v _test; sed -n '212p' internal/pipeline/render.go` +**Улика — вывод:** // v14 (pack-19): the per-chunk observability of the two new $0 flaggers, riding the same + // v15 (row 103): the run-event OUTBOX. A line of `events.jsonl` is a projection of a row committed + // v16 — the bank-stop memory (the D39.144 flag model): every normalized surface a --verify-bank stop +485:func SchemaHead() int { return len(migrations) } +internal/pipeline/render.go:306: w("tm-request-v2", req.BookID, strconv.Itoa(req.Chapter), ... +--- +(render.go:212) return "", fmt.Errorf("pipeline: unknown placeholder {{%s}} in template", name) +**Другие носители того же факта:** backend/docs/D15.2-content-addressed-resume-spec.md:560 — «**Миграция v8** (§11): колонки `guard_hash`, `verdict_snapshot`» +**Предложенное действие:** Дописать в ⚠-шапку спеки одну строку-эррату (тело §11/§12 по норме не переписывать): «⚠ Эррата: с момента письма голова схемы уехала v7→v16, номер v8 занят; при реализации взять СЛЕДУЮЩИЙ свободный (`store.SchemaHead()+1`). Якорь `render.go:212` съехал — литерал `tm-request-v2` на `render.go:306`.» +**Разрушающее:** False + +--- + +## N013 · MAJOR · `docs/PROGRESS.md:3` · A-дрейф +**Срез:** ГОЛОВА журнала: CURRENT-STATE и очередь — docs/PROGRESS.md, строки 1–18 (шапка ⟶ ТЕКУЩЕЕ СОСТОЯНИЕ, ~34 КБ) +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** зеркало отстало на ШЕСТЬ миноров (`frontend/docs/api-contract/openapi.yaml` — `0.2.3` против канона `0.8.0`, и первый из них — ЛОМАЮЩИЙ 0.3.0), 16 операций против 20 +**Почему протухло:** Канон уже НЕ 0.8.0: в `docs/architecture/14-api-contract/openapi.yaml` стоит 0.9.0 — минор уехал с лендингом P12 31.08, о котором сообщает ЭТА ЖЕ строка шапки. Значит и отставание зеркала теперь СЕМЬ миноров, а не шесть. Число операций (16 против 20) проверено и верно. +**Улика — команда:** `grep -rn '^ version:' docs/architecture/14-api-contract/*.yaml; grep -n '^ version:' frontend/docs/api-contract/openapi.yaml; echo -n 'canon ops: '; grep -c 'operationId:' docs/architecture/14-api-contract/openapi.yaml; echo -n 'mirror ops: '; grep -c 'operationId:' frontend/docs/api-contract/openapi.yaml` +**Улика — вывод:** docs/architecture/14-api-contract/openapi.yaml:5: version: 0.9.0 +5: version: 0.2.3 +canon ops: 20 +mirror ops: 16 + +(перепроверено на текущем HEAD 162ab9b: canon version: 0.9.0 / mirror version: 0.2.3) + +docs/README.md:26 знает верное: «пак **P12 ... ПРИНЯТ И ЗАЛЕНДЖЕН 31.08** (**D39.180**) вместе с контрактным минором **0.9.0**» +**Другие носители того же факта:** docs/PROGRESS.md:10 — «зеркало на 0.2.3, канон на 0.8.0 и уйдёт на 0.9.0 с лендингом P12» (лендинг уже был 31.08), frontend/docs/frontend-PROGRESS.md:9 — «Состояние зеркала контракта — 0.2.3 против канона 0.8.0 (и 0.9.0 придёт с лендингом P12)», docs/README.md:29 (строка «Контракт» таблицы активных промтов) — «минор **0.8.0** ПРИНЯТ и заленджен 29.08», про 0.9.0 в этой строке нет ни слова, хотя соседняя строка «Платформа» (README:26) его называет +**Предложенное действие:** В обеих строках шапки заменить «канона `0.8.0`» → «канона `0.9.0`» и «ШЕСТЬ миноров» → «СЕМЬ миноров»; в строке 10 снять будущее время «уйдёт на 0.9.0 с лендингом P12» на «ушёл на 0.9.0 лендингом P12 31.08, D39.180». Тем же заходом поправить frontend/docs/frontend-PROGRESS.md:9 и строку «Контракт» в docs/README.md:29 — иначе разморозка фронта будет спланирована на неверное число перегенераций. +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие верно, и лучше исполнить именно вторую, критериальную его половину: заменить ручной счёт на «ни один пак платформы после P7 этот долг не взял (проверка: `grep -rn 'createExport\|getExport' platform/internal/ --include=*.go` — пусто)», чтобы число не гнило при каждом следующем лендинге. Если счёт всё-таки оставлять — «шесть паков (P8-FIX · P8-REVIEW · P9 · P10 · P11 · P12), плюс непронумерованный `sqlc`». + +--- + +## N014 · MAJOR · `docs/PROGRESS.md:3` · A-дрейф +**Срез:** ГОЛОВА журнала: CURRENT-STATE и очередь — docs/PROGRESS.md, строки 1–18 (шапка ⟶ ТЕКУЩЕЕ СОСТОЯНИЕ, ~34 КБ) +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** ⚠ **Причина, по которой редактор съел 73% захода, НЕ УСТАНОВЛЕНА** — три каузальные истории построены и две сняты; следующий шаг — контролируемая проба по ЧЕТЫРЁМ осям (§14 отчёта) +**Почему протухло:** Числа «73%» в цитируемом отчёте холодного прогона НЕТ вовсе. Отчёт даёт долю редактора с ДВУМЯ знаменателями: 49.3% от всего прогона ($0.214865 из $0.436110) и 81.3% внутри порции П1. Более того, адверсариальный проход самого отчёта отдельной строкой ПРИНЯЛ находку «редактор — 81% без знаменателя». Шапка воспроизводит третье число, не принадлежащее ни одному знаменателю, и именно оно формулирует предмет следующей ПЛАТНОЙ пробы. +**Улика — команда:** `grep -n '73%' docs/archive/reports/COLDRUN_V16_REPORT_2026-08-31.md; sed -n '211,213p;572p' docs/archive/reports/COLDRUN_V16_REPORT_2026-08-31.md` +**Улика — вывод:** (grep '73%' — НОЛЬ строк) + +211:**Доля редактора — со знаменателем, потому что их два:** +212:**49.3%** от всего прогона ($0.214865 из $0.436110) и **81.3%** внутри П1, куда целиком уместилась +213:редакторская волна. Для сравнения: у minirun редактор — 80.6% его захода. +572:| «редактор — 81%» без знаменателя (49.3% от прогона) | **ПРИНЯТО** (§4.2) | +**Положительный контроль:** grep -c '%' docs/archive/reports/COLDRUN_V16_REPORT_2026-08-31.md → 14 (знак процента в файле есть и грепается); grep -rn '49\.3%\|81\.3%' → docs/archive/reports/COLDRUN_V16_REPORT_2026-08-31.md:212 и :572. Инструмент в этой области находит ровно то, что там лежит. +**Предложенное действие:** Заменить «73% захода» на числа отчёта со знаменателем: «49.3% всего прогона ($0.214865 из $0.436110) / 81.3% внутри П1». Проверить заодно, не считана ли «73%» из какого-то третьего места — в живых доках это число встречается только в несвязанных контекстах (research/20 точность майнинга, exp01 Batch-надбавка, research/18 DocRepair-предпочтение). +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие верно, и лучше исполнить именно вторую, критериальную его половину: заменить ручной счёт на «ни один пак платформы после P7 этот долг не взял (проверка: `grep -rn 'createExport\|getExport' platform/internal/ --include=*.go` — пусто)», чтобы число не гнило при каждом следующем лендинге. Если счёт всё-таки оставлять — «шесть паков (P8-FIX · P8-REVIEW · P9 · P10 · P11 · P12), плюс непронумерованный `sqlc`». + +--- + +## N015 · MAJOR · `docs/PROGRESS.md:3` · E-второй-носитель +**Срез:** ГОЛОВА журнала: CURRENT-STATE и очередь — docs/PROGRESS.md, строки 1–18 (шапка ⟶ ТЕКУЩЕЕ СОСТОЯНИЕ, ~34 КБ) +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** **⟶ ТЕКУЩЕЕ СОСТОЯНИЕ** (на 2026-09-01, голова D39.184 — КУРС: движок и платформа до «работает и отдаёт результат» ... **ОЧЕРЕДЬ №22** (единственный носитель — здесь; роль передана 01.09, №21 закрыт нотой передачи D39.184) +**Почему протухло:** Шапка объявляет себя ЕДИНСТВЕННЫМ носителем состояния очереди, но не несёт двух лендингов 31.08, которые несёт `docs/README.md`: бэкенд-пак «ДЕНЬГИ И ЧЕСТНОСТЬ ВЫДАЧИ» (D39.182) и ратифицированный владельцем ЗАКОН РАСКРЫТИЯ движка (D39.181, «честная цена: закон загейчен на 40%», живое — применение к десяти экземплярам фазой 2). Из номеров D39.18x шапка называет только D39.180 и D39.184. Следствие для заказа владельца (0б) «планы доработок в БЭКЕНД»: читатель очереди не узнаёт ни о свежем лендинге зоны, ни о заявленном остатке ратифицированного закона. +**Улика — команда:** `sed -n '1,18p' docs/PROGRESS.md | grep -o 'D39\.18[0-9]' | sort -u; echo '--- где D39.182 в PROGRESS.md ---'; grep -n 'D39\.182' docs/PROGRESS.md | cut -c1-90; echo '--- README ---'; grep -n 'ДЕНЬГИ И ЧЕСТНОСТЬ' docs/README.md | cut -c1-200` +**Улика — вывод:** D39.180 +D39.184 +--- где D39.182 в PROGRESS.md --- +209:| 247 | **§2.2 закона раскрытия опровергает себя следующей же строкой — эррата зонного +--- README --- +25: | Бэкенд | активного НЕТ | пак **«ДЕНЬГИ И ЧЕСТНОСТЬ ВЫДАЧИ» ПРИНЯТ И ЗАЛЕНДЖЕН 31.08** (**D39.182**): закон раскрытия (**D39.181**, ратифицирован владельцем + +(D39.182 во всём PROGRESS.md встречается РОВНО один раз — внутри строки бэклога 247, то есть только как ссылка долга, не как запись состояния) +**Положительный контроль:** sed -n '1,18p' docs/PROGRESS.md | grep -o 'D39\.1[0-9][0-9]' | sort -u → 43 РАЗЛИЧНЫХ номера, включая соседние D39.180 и D39.184. Шапка насыщена D-ссылками и грепается; пустой результат по D39.181/D39.182 — отсутствие факта, а не слепота инструмента. +**Другие носители того же факта:** docs/README.md:25 (строка «Бэкенд» таблицы активных промтов) — несёт и лендинг D39.182, и ратификацию D39.181, docs/architecture/05-decisions-index.md:245–246 — реестровые строки D39.181/D39.182 со столбцом «ЖИВОЕ» (применение закона к десяти экземплярам — фаза 2; строка бэклога 246), docs/PROGRESS.md:302 — упоминание D39.181 в зонной записи бэкенда, ниже шапки +**Предложенное действие:** Дописать в шапку одну фразу вида: «31.08 в зоне бэкенда лёг пак «деньги и честность выдачи» (D39.182) и РАТИФИЦИРОВАН закон раскрытия движка (D39.181); живой остаток — фаза 2 (применение к десяти экземплярам) и строка бэклога 246». Заодно продлить провенанс-перечень строки 16 до 248: строки 244–248 заведены 31.08 и в перечне обрываются на 243 (сами счётчики 189/88/125/59/0 при этом сходятся с `counts.py`). +**Разрушающее:** False +**⚠ Опровергатель поправил действие:** Действие верно, и лучше исполнить именно вторую, критериальную его половину: заменить ручной счёт на «ни один пак платформы после P7 этот долг не взял (проверка: `grep -rn 'createExport\|getExport' platform/internal/ --include=*.go` — пусто)», чтобы число не гнило при каждом следующем лендинге. Если счёт всё-таки оставлять — «шесть паков (P8-FIX · P8-REVIEW · P9 · P10 · P11 · P12), плюс непронумерованный `sqlc`». + +--- + +## N016 · MAJOR · `docs/PROGRESS.md:80` · C-исполненное-как-открытое +**Срез:** ЕДИНЫЙ БЭКЛОГ docs/PROGRESS.md — таблица (строки 57–272 файла), проверка на класс C «исполненное как открытое» +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** Редакторская волна на ЭТОМ прогоне не гонялась (coldrun-a/b: ноль редакторских вызовов) ⇒ строка жива +**Почему протухло:** Довод «⇒ строка жива» опирается на прогоны coldrun-a/b, но 31.08 отработал НОВЫЙ холодный прогон (coldrun-v16), в котором редакторская волна гонялась и оплачена: 4 оплаченных чекпойнта `edit/editor/deepseek-v4-pro` на $0.214865 — 49.3% всего захода $0.436110. То есть «полная цена холодного старта с редактурой» больше не «не измерена»: она измерена частично (доставлено 3 юнита из 14) и имеет собственную проекцию $1.1377. Числа самой строки ($0.031639 драфт-стадия, $0.045095 весь заход) остаются ИЮЛЬСКИМИ и по D39.179 п.1 множатся на 4.47 в сегодняшних ценах — в строке об этом ни слова. +**Улика — команда:** `sqlite3 'file:/home/ubuntu/projects/textmachine/books/gu-zhenren/coldrun-v16/guzhenren-coldrun-v16.db?mode=ro' "select stage,role,model_actual,count(*),round(sum(cost_usd),6) from checkpoints where cost_usd>0 group by 1,2,3 order by 5 desc;"` +**Улика — вывод:** edit|editor|deepseek-v4-pro|4|0.214865 +draft|translator|deepseek-v4-pro|5|0.103305 +draft|translator|deepseek-v4-flash|20|0.068135 +terminology|terminologist|deepseek-v4-flash|6|0.040617 +terminology|classifier|deepseek-v4-flash|3|0.009188 +(доля edit от всей суммы — 49.27%) +**Другие носители того же факта:** docs/PROGRESS.md:3 (CURRENT-STATE п.2 — числа холодного прогона), docs/archive/reports/COLDRUN_V16_REPORT_2026-08-31.md §4.2/§4.4 +**Предложенное действие:** Переписать хвост строки 16: назвать coldrun-v16 (31.08) как первый холодный прогон С редакторской волной, вписать $0.214865 edit / $0.436110 заход / проекция $1.1377 при 3 доставленных юнитах из 14, и явно пометить прежние июльские числа ценовой эрой (множитель 4.47, D39.179 п.1). Живым остатком оставить только то, что действительно не измерено, — цена ДОВЕДЁННОЙ до конца книги. +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие верно и строку НЕ закрывать — только переписать. Живым остатком оставить ровно то, что находка и называет: цена ДОВЕДЁННОЙ до конца книги (прогон отдал 3 юнита из 14). При переписывании привести в согласие и колонку гейта: сегодня она говорит «первый прогон добора идеала», а CURRENT-STATE числит строку 16 среди снятых холодным прогоном — оставить одну версию, иначе класс E воспроизведётся внутри одного файла. + +--- + +## N017 · MAJOR · `docs/PROGRESS.md:143` · A-дрейф +**Срез:** ЕДИНЫЙ БЭКЛОГ docs/PROGRESS.md — таблица (строки 57–272 файла), проверка на класс C «исполненное как открытое» +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** скоро (гейт: замер размеров юнитов на первом холодном прогоне, вместе со 157) +**Почему протухло:** Гейт назначен на событие, которое УЖЕ произошло (первый холодный прогон, 31.08), и прогон показал, что назначенный замер этим путём недостижим: манифест не публикует размеров юнитов В ЗНАКАХ вовсе — юнит несёт только `id`, `first_chunk_idx`, `chunk_count`, `edit_unit_id`. Отчёт прогона говорит прямо: «Заказ в этой части выполнить нечем: носителя нет». ⚠ Дополнительная ловушка для принимающего: отчёт подписал числа семейств под «строкой 154», а размеры юнитов — под «строкой 157», то есть КРЕСТ-НАКРЕСТ относительно таблицы. +**Улика — команда:** `python3 -c "import json;d=json.load(open('/home/ubuntu/projects/textmachine/books/gu-zhenren/coldrun-v16/guzhenren-coldrun-v16.db.manifest.json'));ch=d['chapters'][0];print(list(ch.keys()));print(json.dumps(ch['units'][0]))"` +**Улика — вывод:** ['id', 'number', 'heading', 'units_total', 'chunks_total', 'units'] +{"id": "c3aa4cbbb53de134:cb440937:0", "first_chunk_idx": 0, "chunk_count": 1, "edit_unit_id": 0} +(отчёт §9: «Распределения размеров юнитов В ЗНАКАХ манифест не публикует вовсе — ни `--json`, ни персист-сайдкар символьных размеров не несут. Заказ в этой части выполнить нечем: носителя нет.») +**Положительный контроль:** тот же python3-вызов печатает НЕПУСТОЙ список ключей главы и юнита — инструмент манифест читает и поля видит, значит отсутствие размеров в знаках — факт, а не пустой хит. +**Другие носители того же факта:** docs/archive/reports/COLDRUN_V16_REPORT_2026-08-31.md §9 (подписано «Строка 157 — ЧАСТИЧНО», хотя предмет — гейт 154) +**Предложенное действие:** Пере-написать гейт строки 154: событие «первый холодный прогон» наступило 31.08 и замер НЕ дало — назначить настоящее предусловие (публикация размеров юнитов в знаках в манифесте/сайдкаре) отдельным носителем, либо признать замер невозможным и решать по косвенной мере (входные токены черновых вызовов, 1557–1863 на чанк). Тем же касанием снять крест-накрест подпись отчёта §9 (154↔157). +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие верно, но одна деталь его формулировки опасна: «назначить настоящее предусловие … отдельным носителем» надо делать НЕ молча — публикация размеров юнитов в знаках это новое поле контракта манифеста (`manifest_version`), то есть решение уровня ноты, а не строки бэклога. Плюс уточнение по косвенной мере: 1557–1863 — это входные токены на ЧАНК, а юнит по этому же прогону несёт от 1 до 2 чанков (медиана 1), поэтому пере-считывать в цену юнита надо через `chunk_count`, а не приравнивать юнит чанку. + +--- + +## N018 · MAJOR · `docs/PROGRESS.md:157` · A-дрейф +**Срез:** ЕДИНЫЙ БЭКЛОГ docs/PROGRESS.md — таблица (строки 57–272 файла), проверка на класс C «исполненное как открытое» +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** рядом `bank-stop.json` — полная таблица подписи для S5 +**Почему протухло:** Файла `.bank-stop.json` больше НЕ существует: он снесён вместе с писателем и четырьмя тестами при постройке входной двери шва (D39.158) — в Go-коде движка нет ни одного вхождения, а холодный прогон 31.08 подтвердил это на диске (в каталоге прогона лежит `bank-stop.txt`, `bank-stop.json` нет). Строка обещает платформе/фронту машинную таблицу подписи для экрана S5, которой нет ни на диске, ни в коде. +**Улика — команда:** `grep -rn 'bank-stop\.json' backend --include=*.go; ls -L books/gu-zhenren/coldrun-v16/ | grep bank-stop` +**Улика — вывод:** (греп по *.go — НИ ОДНОГО вхождения) +--- +guzhenren-coldrun-v16.db.bank-stop.txt +**Положительный контроль:** grep -rn 'bank-stop\.txt|bank\.json' backend/internal backend/cmd → backend/internal/pipeline/mining.go:371: return r.Book.ProjectDB + ".bank-stop.txt"; backend/internal/pipeline/bankexport.go:85: func (r *Runner) bankExportPath() string { return r.Book.ProjectDB + ".bank.json" } — инструмент имена сайдкаров в этом коде находит, значит пустота именно у `bank-stop.json`. +**Другие носители того же факта:** platform/docs/STACK_DECISIONS.md:410 (`.bank-stop.json` в таблице каналов: «⚠ канал существует и…»), backend/docs/pipeline.puml:72 («.bank-stop.json — машине (строка 101): ПОЛНАЯ таблица»), frontend/docs/frontend-PROGRESS.md:499 («сайдкар `.bank.json` + `bank-stop.json`»), platform/docs/platform-PROGRESS.md:2695 и :2771 (перечень сайдкаров и их атомарности), docs/BACKEND_COLDRUN_V16_SESSION_PROMPT.md:363 — ЕДИНСТВЕННЫЙ носитель с верным текстом («Файла `bank-stop.json` не существует») +**Предложенное действие:** Снять из строки 169 упоминание `bank-stop.json` (заменить на: «машинная таблица `.bank-stop.json` СНЕСЕНА D39.158; оператору остался `.bank-stop.txt`, машине — `.bank.json`»), и тем же касанием пройти прочих живых носителей, где файл всё ещё числится существующим каналом. +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие верно; список носителей уточняю по исполнению. `docs/architecture/18-bank-ontology.md:45` ПРАВИТЬ НЕ НАДО — там файл назван в ПРОШЕДШЕМ времени как урок («Машинная таблица стопа `.bank-stop.json` была…»), это легитимная история, а не дрейф; так же и `backend/docs/SEAM_FIX3_PLAN.md:14` / `SEAM_PACK_FINDINGS.md:1415-1424`, которые описывают САМ снос. Протухли ровно три живых носителя, где файл числится СУЩЕСТВУЮЩИМ каналом: `platform/docs/STACK_DECISIONS.md:410` (строка таблицы каналов «⚠ канал существует и не потребляется» — прочитал, там же ложное «атомарно»), `backend/docs/pipeline.puml:72` и `frontend/docs/frontend-PROGRESS.md:499`. Два из них — чужие зоны: правка пингом, не касанием. + +--- + +## N019 · MAJOR · `docs/PROGRESS.md:182` · D-противоречие-ратифицированному +**Срез:** ЕДИНЫЙ БЭКЛОГ docs/PROGRESS.md — таблица (строки 57–272 файла), проверка на класс C «исполненное как открытое» +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** Цена ≈ $0.05 на десятиглавую книгу. +**Почему протухло:** Дважды протухла. (1) Цифра «≈$0.05» СНЯТА ратифицированной нотой D39.179 п.1 как протухшая: пере-пин DeepSeek 15.08 даёт множитель 4.47, а замер 31.08 на ровно такой десятиглавой книге дал $0.436110 при собственной проекции движка $1.1377 — то есть строка обещает платформе смету, ошибочную примерно в 9–23 раза. (2) Гейт в колонке веса — «скоро (гейт: холодный прогон движка)» — УЖЕ ПАЛ: холодный прогон отработал 31.08; настоящий блокер строки сместился на ключи провайдера (журнал платформы). Шапка CURRENT-STATE это знает, таблица — нет. +**Улика — команда:** `awk '/^## D39\.179/,/^## D39\.180/' docs/architecture/05-decisions-log.md | sed -n '3p'; sqlite3 'file:/home/ubuntu/projects/textmachine/books/gu-zhenren/coldrun-v16/guzhenren-coldrun-v16.db?mode=ro' 'select round(sum(cost_usd),6) from checkpoints;'` +**Улика — вывод:** 1. **Санкция владельца 31.08: ≈$0.60, потолок $0.80, ДЕСЯТЬ глав ПОЛНОЙ ЦЕПЬЮ включая редакторскую волну.** … **Прежняя цифра очереди «≈$0.05» СНЯТА как протухшая:** DeepSeek пере-пинен коммитом 15.08 (`76049bb`, D39.137) под цены, вступившие в силу 16.08 16:00 UTC; заход `bank-low` ($0.045095 в июльских деньгах) по ним стоит $0.2017 — множитель 4.47, пере-считан из его же токенов. +--- +0.43611 +**Другие носители того же факта:** docs/PROGRESS.md:3 (CURRENT-STATE п.3: «гейт СНЯТ холодным прогоном» — та же строка 202 в том же файле описана иначе), platform/docs/platform-PROGRESS.md:2166 («Строка 202 «книга насквозь по-настоящему» упирается теперь ровно в ключи») +**Предложенное действие:** Заменить «Цена ≈ $0.05 на десятиглавую книгу» на замеренное: холодный прогон 31.08 купил $0.436110 на 10 главах при доставленных 3 юнитах из 14 и проекции движка $1.1377; ссылка — D39.179 п.1. Колонку веса переписать: гейт «холодный прогон движка» ПАЛ 31.08, живой блокер — заполненный файл ключей для `TM_PLATFORM_ENGINE_KEYS_PATH` (obstacle P12). +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие верно; добавить два уточнения, чтобы новая цифра не протухла так же. Первое: $0.436110 — цена НЕПОЛНОГО захода (доставлено 3 юнита из 14), поэтому в строку надо писать не её одну, а связку «куплено $0.436110 при собственной проекции движка `projected_book_usd = 1.1377277707` и полном оттоке кассы ≈$1.44 по §4 отчёта» — иначе платформа снова возьмёт в смету число меньше правды. Второе: указатель на ключи ставить греп-указателем (`TM_PLATFORM_ENGINE_KEYS_PATH` в `platform/internal/config/config.go`), без номера строки — файл в живой зоне платформы. + +--- + +## N020 · MAJOR · `docs/PROGRESS.md:196` · C-исполненное-как-открытое +**Срез:** ЕДИНЫЙ БЭКЛОГ docs/PROGRESS.md — таблица (строки 57–272 файла), проверка на класс C «исполненное как открытое» +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** Не хватает ЧИТАЮЩЕГО метода поверх готовой константы +**Почему протухло:** Строка 232 ЗАКРЫТА лендингом 31.08 (`bb541a8`, экземпляр A11). Читающий метод `store.AnnouncedOnceKeys()` построен, его докстринг называет строку 232 по номеру и воспроизводит её же довод («invites its buyer to purchase it a second time»), а ось доставки в `volume.go:490,539` теперь выводится из анонс-леджера, а не из полноты строк — то есть исполнена и развилка, которую строка требовала назвать. +**Улика — команда:** `grep -rn 'AnnouncedOnceKeys' backend --include=*.go; sed -n '39,42p' backend/internal/store/outbox.go` +**Улика — вывод:** backend/internal/pipeline/volume.go:490:// announce-once ledger (store.AnnouncedOnceKeys) rather than re-derived from rows. +backend/internal/pipeline/volume.go:539: announced, err := r.Store.AnnouncedOnceKeys() +backend/internal/store/outbox.go:51:func (s *Store) AnnouncedOnceKeys() (map[string]bool, error) { +--- +// A projection that asks the first question calls an already-delivered book "never delivered" the day a +// stage is added to the config, and invites its buyer to purchase it a second time (unified backlog row +// 232). +**Другие носители того же факта:** docs/PROGRESS.md:305 («A11 (строка 232) — `store.AnnouncedOnceKeys()` + ось доставки по анонс-леджеру. Две посадки.»), backend/docs/MONEY_HONESTY_REPORT.md:769 («A11: `AnnouncedOnceKeys()` — читающий метод строки 232») +**Предложенное действие:** Закрыть строку 232 нотой лендинга (A11) и убрать из таблицы. ⚠ Якорь `backend/internal/store/outbox.go:31` внутри строки ПРОВЕРЕН и остался верным (`const onceKeyLookup`) — при закрытии не «чинить». +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** НЕ закрывать и НЕ убирать строку 232. СУЗИТЬ её: снять первую половину (ложное «NEVER delivered» после добавления стадии) как исполненную A11 — `store.AnnouncedOnceKeys()` + предикат `delivered[key] && unitShipped(rows)`; оставить живой ВТОРУЮ половину — юнит, прерванный между волнами, повторно тратит слот гранта — со ссылкой на `backend/docs/MONEY_HONESTY_REPORT.md` §4.5, где эта граница названа автором лекарства явно. Якорь `backend/internal/store/outbox.go:31` (`const onceKeyLookup`) не трогать. + +--- + +## N021 · MAJOR · `docs/PROGRESS.md:202` · C-исполненное-как-открытое +**Срез:** ЕДИНЫЙ БЭКЛОГ docs/PROGRESS.md — таблица (строки 57–272 файла), проверка на класс C «исполненное как открытое» +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** скоро (с §3.7 пака P12) | снять гард вместе с обходом либо доказать, что память v16 делает его недостижимым — с живой пробой на стенде +**Почему протухло:** Строка 240 исполнена лендингом P12 31.08 (`0a680a3`, D39.180) — обеими ветвями своей же диспозиции. Гард `LiftBankStop` СНЯТ (в `platform/` осталось два комментария, оба явно говорят «removed with the workaround»), колонка `bank_released` снесена миграцией 00029, а защита переехала на уровень выше: ветка `awaiting_bank` в `reconcile.go` теперь закрывает прогон, а не рестартует сквозь оплаченный стоп, и комментарий там ЯВНО называет строку 240. Движковая половина улики — отчёт холодного прогона §7 (`DELETE`/`DROP`/`TRUNCATE` у `bank_stop_presented` нет нигде; память пережила границу процесса). +**Улика — команда:** `grep -rn 'LiftBankStop' platform --include=*.go; sed -n '605,619p' platform/internal/runs/reconcile.go; ls platform/internal/pgstore/migrations/ | grep bank_released` +**Улика — вывод:** platform/internal/runs/control_test.go:652:// (`LiftBankStop`), and that guard was written against an engine that re-halted on everything still +platform/internal/runs/reconcile.go:615:// (`LiftBankStop`, removed with the workaround) only decided which BIT the restart wrote, and +--- + if l.Status == "awaiting_bank" { + // ... The stop the user paid for would simply cease to exist, which is unified-backlog row 240. + // ⚠ This is what protects the paid stop now; the platform's old guard did not. +--- +00029_drop_bank_released.sql +**Положительный контроль:** grep -rn 'BankStop' platform --include=*.go | head -4 → platform/internal/runs/reconcile.go:645 (TypeBankStop), :927 (ingest.OutcomeBankStop), control_test.go:436 TestResumeLiftsABankStop… — инструмент в этой области символы находит, значит пустота именно у `LiftBankStop`. +**Другие носители того же факта:** platform/docs/platform-PROGRESS.md:283 («Пинившийся гард (`LiftBankStop`) с памятью v16 не защищал НИЧЕГО; защита переехала на уровень выше»), platform/docs/platform-PROGRESS.md:2009 (строка Д3 регистра с якорем `reconcile.go:1122-1126`), platform/internal/runs/reconcile.go:615 (код называет строку 240 по номеру) +**Предложенное действие:** Закрыть строку 240 при лендинге/приёмке P12 нотой, назвав ОБЕ половины улики (снятый гард + перенос защиты на решение о рестарте, и §7 отчёта холодного прогона по движковой половине); ⚠ честно записать, что «живая проба на стенде» из диспозиции сделана на `local`-заглушке, а не на облачном провайдере (obstacle P12). +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие в целом верно, но его ФОРМУЛИРОВКА протухла на сутки: закрывать «при лендинге/приёмке P12» нечего — P12 ПРИНЯТ И ЗАЛЕНДЖЕН 31.08 (`0a680a3`, ратификация D39.180), так что закрытие делается СЕЙЧАС нотой со ссылкой на D39.180, а не в будущем лендинге. Тем же касанием обязательно чинить второй носитель, который находка назвала, но не пометила протухшим: в `platform/docs/platform-PROGRESS.md:2009` ряд Д3 регистра держит якорь `reconcile.go:1122-1126` на снятый гард — я прочёл эти строки, там `ReleaseUnspawned`/`MarkSettled` (файл 1621 строка), то есть указатель МЁРТВ и по норме D39.179 п.4 в живом файле зоны обязан быть греп-указателем без номера. Оговорку находки про пробу принимаю и подтверждаю: obstacle пака говорит «Живой пробой на ОБЛАЧНОМ провайдере не удался … Цепь пробита на `local`-заглушке». + +--- + +## N022 · MAJOR · `docs/README.md:13` · A-дрейф +**Срез:** Корневые онбординг-доки: CLAUDE.md · docs/README.md · docs/STACK.md +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** зонная копия `frontend/docs/api-contract/` — байт-зеркало, сверять при лендинге +**Почему протухло:** Канон контракта уехал до 0.9.0 (заленджен 31.08, D39.180), а зонная копия стоит на 0.2.3 от 15.08 — 2999 строк против 1444. «Байт-зеркало» сегодня ЛОЖНО: копии разошлись шестью минорами (0.3.0…0.9.0). Верно: копия ОТСТАЛА и зеркалом не является; дисциплина «сверять при лендинге» не исполнялась ни разу с заморозки фронта. +**Улика — команда:** `cmp docs/architecture/14-api-contract/openapi.yaml frontend/docs/api-contract/openapi.yaml; grep -n 'version:' <оба> | head -1; wc -l <оба>; md5sum <оба>` +**Улика — вывод:** docs/architecture/14-api-contract/openapi.yaml frontend/docs/api-contract/openapi.yaml differ: byte 61, line 5 +cmp exit=1 +канон 5: version: 0.9.0 +зеркало 5: version: 0.2.3 + 2999 docs/architecture/14-api-contract/openapi.yaml + 1444 frontend/docs/api-contract/openapi.yaml +34bb4b6d0496f4377633597cd12bc73a docs/architecture/14-api-contract/openapi.yaml +4e4ab5e6d313bd56d395032a718ab18a frontend/docs/api-contract/openapi.yaml +**Другие носители того же факта:** docs/architecture/14-api-contract/openapi.yaml:9, docs/architecture/14-api-contract/README.md:73, docs/glossary.md:31, docs/ORCHESTRATOR_SESSION_PROMPT.md:173, docs/research/28-contract-review.md:1589, frontend/README.md:66, frontend/docs/API_CONTRACT_DRAFT.md:16, frontend/docs/frontend-PROGRESS.md:48 +**Предложенное действие:** Заменить «— байт-зеркало, сверять при лендинге» на факт: «зонная копия `frontend/docs/api-contract/` ОТСТАЛА на 0.2.3 (канон 0.9.0) — зона заморожена, синхронизация при разморозке; читать ТОЛЬКО канон». Завести строку единого бэклога «зонное зеркало контракта отстало на шесть миноров». Правку самого зеркала не делать — это чужая (замороженная) зона. +**Разрушающее:** False + +--- + +## N023 · MAJOR · `docs/architecture/03-implementation-notes.md:64` · A-дрейф +**Срез:** Заметки реализации и Go-нормы — docs/architecture/03-implementation-notes.md · docs/architecture/12-go-style-notes.md +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** `request_hash = sha256(book_id | chapter | chunk_index | stage | role | model+sampling | snapshot_id | sha256(rendered_messages))`. +**Почему протухло:** Шапка строки 3 объявляет §3-контракты «живым референсом бэкенда», а формула §3.1 не совпадает с кодом ни одним полем-разделителем: реальный RequestHash фолдит версионный тег `tm-request-v2`, ДОБАВЛЯЕТ `attempt`, разворачивает «model+sampling» в пять отдельных полей (model · temperature · reasoning · json_only · max_tokens) и фолдит сообщения НАПРЯМУЮ с length-prefix и явным счётчиком, а НЕ как вложенный `sha256(rendered_messages)`. Сессия, реализовавшая ключ по букве §3.1, получила бы чужие чекпоинты (attempt отсутствует ⇒ ре-генерация садится на только что провалившийся чекпоинт — ровно тот дефект, который код в комментарии называет). +**Улика — команда:** `sed -n '292,316p' /home/ubuntu/projects/textmachine-main/backend/internal/pipeline/render.go` +**Улика — вывод:** func RequestHash(req Request) string { + h := sha256.New() + // Each field is length-prefixed (8 bytes LE) instead of a NUL separator ... Version v2 pins the hash-format change. + var lb [8]byte + w := func(parts ...string) { ... } + w("tm-request-v2", req.BookID, strconv.Itoa(req.Chapter), strconv.Itoa(req.ChunkIdx), strconv.Itoa(req.Attempt), + req.Stage, req.Role, req.Model, + strconv.FormatFloat(req.Temperature, 'f', -1, 64), req.Reasoning, strconv.FormatBool(req.JSONOnly), + strconv.Itoa(req.MaxTokens), req.SnapshotID) + // Explicit message count: the list length is also part of the key. + binary.LittleEndian.PutUint64(lb[:], uint64(len(req.Messages))) + h.Write(lb[:]) + for _, m := range req.Me +**Предложенное действие:** Сноской по образцу уже стоящей в §3.4 «ПОПРАВКА»: «⚠ Формула устарела — действующий ключ см. `backend/internal/pipeline/render.go:292` (`RequestHash`): версионный тег `tm-request-v2`, поля length-prefixed, `attempt` В КЛЮЧЕ, sampling развёрнут (temperature·reasoning·json_only·max_tokens), сообщения фолдятся напрямую со счётчиком, а не вложенным sha256. Изменение зафиксировано волнами §6 (`attempt` в ключе и схеме, length-prefix + счётчик сообщений), но §3.1 не был обновлён». +**Разрушающее:** False + +--- + +## N024 · MAJOR · `docs/architecture/03-implementation-notes.md:262` · D-противоречие-ратифицированному +**Срез:** Заметки реализации и Go-нормы — docs/architecture/03-implementation-notes.md · docs/architecture/12-go-style-notes.md +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** **D1** — редактор C1 видит исходник (`{{text}}` в editor.md); для Фазы 1+ сделать редактора монолингвальным (только `{{draft}}`), верность → coverage-гейт + билингвальный судья ... но `{{text}}` убрать, когда они приедут. +**Почему протухло:** D30 п.1 ратифицировал ПРЯМО ОБРАТНОЕ и с явным supersede: «Флип D1: редактор — БИЛИНГВАЛЬНЫЙ (черновик + исходник), новый дефолт». Реестр решений помечает саму формулировку доку («{{text}} убрать из editor.md») как SUPERSEDED → D17 → D30.1. Док не знает об этом и продолжает предписывать имплементеру Фазы 1+ снять `{{text}}` — исполнение этой строки развернуло бы ратифицированный контракт и двинуло prompt_version → resnapshot. Боевой конфиг сегодня явно ссылается на D30.1 как на основание билингва. +**Улика — команда:** `grep -n '| D1 |' docs/architecture/05-decisions-index.md; sed -n '115,115p' docs/architecture/05-decisions-log.md | cut -c1-260; grep -n '{{text}}' backend/prompts/zh-ru/editor.md; grep -n 'D30.1' backend/configs/pipeline-c1.yaml` +**Улика — вывод:** docs/architecture/05-decisions-index.md:17:| D1 | | Редактор C1 — монолингвальный: видит только черновик + approved-глоссарий, без исходника; {{text}} убрать из editor.md, поднять edit prompt_version | SUPERSEDED → D17 → D30.1 (редактор БИЛИНГВ) | слайс D1-D38 | модели промпт | +05-decisions-log.md:115: 1. **Флип D1: редактор — БИЛИНГВАЛЬНЫЙ (черновик + исходник), новый дефолт. Ратифицирован с явным supersede D17.в** +backend/prompts/zh-ru/editor.md:37:{{text}} +backend/configs/pipeline-c1.yaml:96: # против D30.1 — редактор БИЛИНГВ и видит плотный CJK-исходник (prompts/zh-ru/editor.md, {{text}}). +**Предложенное действие:** Сноской сразу за пунктом D1: «⚠ ОПРОВЕРГНУТО D30 п.1 (12.07): редактор БИЛИНГВАЛЬНЫЙ — новый дефолт, с явным supersede D17.в; реестр помечает D1 как SUPERSEDED → D17 → D30.1. `{{text}}` из `prompts/zh-ru/editor.md` НЕ снимать. Моно-вариант живёт как `editor-mono.md` — дормант-референс (D33.4a), не план». Текст пункта не сносить — он объясняет, откуда взялась нота D1. +**Разрушающее:** False + +--- + +## N025 · MAJOR · `docs/architecture/06-memory-risk-registry.md:81` · B-мёртвый-указатель +**Срез:** Ранние арх-доки (кандидаты в архив): docs/architecture/01-decisions.md · 02-mvp-plan.md · 04-unhappy-paths.md · 06-memory-risk-registry.md +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** `snapshotID` (`snapshot.go:82 (до пакета №4 — runner.go:145-163)`) хэширует brief/chunker/план-стадий/сэмплинг ... `RequestHash` (`render.go:158`) ... `render.go:23-29` ... инъектит память в `msgs` (`render.go:133-136`) ... resnapshot-гейт (`bookrun.go:108/stagerun.go:77`) +**Почему протухло:** Колонка «Где в коде/схеме» реестра указывает шесть file:line-якорей, и пять из шести уехали: реальные адреса — MessagesWithInjection render.go:246 (а не render.go:133-136, где сегодня разбор разделителя промпта), RequestHash render.go:292 (а не :158 = fewShotEnabled), snapshotID() snapshot.go:209 (а не :82), resnapshot-гейт целиком в stagerun.go:52-65 (в bookrun.go его НЕТ вовсе, :108 — пустая строка перед CompletedWithFlags), memoryVersion() snapshot.go:190 (а не :175). Плюс имя `render.Messages` больше не место инъекции: Messages() на render.go:229 делегирует в MessagesWithInjection с ПУСТОЙ инъекцией. +**Улика — команда:** `cd /home/ubuntu/projects/textmachine-main/backend && for a in render.go:133 render.go:158 snapshot.go:82 bookrun.go:108 stagerun.go:77 snapshot.go:175; do f=${a%%:*}; n=${a##*:}; printf '%-16s -> ' "$a"; sed -n "${n}p" internal/pipeline/$f | cut -c1-95; done; grep -n '^func MessagesWithInjection\|^func RequestHash\|^func (r \*Runner) snapshotID\|^func (r \*Runner) memoryVersion' internal/pipeline/render.go internal/pipeline/snapshot.go; grep -n 'was started under snapshot' internal/pipeline/stagerun.go` +**Улика — вывод:** render.go:133 -> parts := strings.SplitN(canon, userSeparator, 2) +render.go:158 -> // fewShotEnabled resolves a stage's few_shot toggle: absent (nil) defaults to ON, so +snapshot.go:82 -> } +bookrun.go:108 -> +stagerun.go:77 -> contentHash := msgsContentHash(msgs) +snapshot.go:175 -> s.ModelWire = wire.capability +--- +internal/pipeline/snapshot.go:190:func (r *Runner) memoryVersion() string { +internal/pipeline/snapshot.go:209:func (r *Runner) snapshotID() (id, payload string, err error) { +internal/pipeline/render.go:246:func MessagesWithInjection(tpl *PromptTemplate, v RenderVars, injection string) ([]llm.Message, error) { +internal/pipeline/render.go:292:func RequestHash(req Req +**Положительный контроль:** Тот же sed по якорю, который НЕ уехал, попадает точно: `sed -n '22,28p' internal/pipeline/render.go` → «// render.go enforces the determinism invariant (03-implementation-notes §3.1): the rendered prompt is a PURE function of (snapshot, committed prior outputs, chunk)» — т.е. якорь «render.go:23-29» из того же дока резолвится, инструмент в этой области работает. +**Другие носители того же факта:** docs/architecture/06-memory-risk-registry.md:10 (шапка 25.07 несёт тот же уехавший «snapshot.go:175»), docs/architecture/06-memory-risk-registry.md:32 (A1: «render.Messages … render.go:133-136»), docs/architecture/06-memory-risk-registry.md:37 (A6: «раскладка render.Messages»), docs/architecture/06-memory-risk-registry.md:100 («impl-notes §3.6 (строки 123-124)» — цитируемая фраза про FTS5 живёт на 03-implementation-notes.md:127) +**Предложенное действие:** Пере-нацелить якоря одной правкой: render.go:133-136 → `pipeline.MessagesWithInjection` (render.go:246); render.go:158 → render.go:292; snapshot.go:82 → snapshot.go:209; «bookrun.go:108/stagerun.go:77» → stagerun.go:52-65; snapshot.go:175 → snapshot.go:190; «impl-notes §3.6 (строки 123-124)» → 03-implementation-notes.md:127. Имя `render.Messages` в A1/A6 заменить на `render.MessagesWithInjection` (Messages() передаёт пустую инъекцию). Устойчивее — заменить номера строк на ИМЕНА функций, они пережили уже два рефакторинга. +**Разрушающее:** False + +--- + +## N026 · MAJOR · `docs/architecture/09-target-architecture.md:12` · A-дрейф +**Срез:** Целевая архитектура — docs/architecture/09-target-architecture.md +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** ⚠ **Канон контракта — 0.8.0**, не 0.4.0 и не 0.7.0: пять миноров за трое суток (0.5.0 — D39.161, 0.6.0 — D39.162/163, 0.7.0 — D39.166, **0.8.0 — D39.169**, кадр `session_ended`). Носители — `14-api-contract/openapi.yaml` и `platform/internal/httpapi/capabilities.go`, они сходятся гейтом. +**Почему протухло:** Канон стоит на 0.9.0, а не на 0.8.0: минор 0.9.0 залендён 31.08 паком платформы P12 (D39.180), и ОБА названных в этой же строке носителя показывают 0.9.0. Строка сама называет носителей и сама им противоречит — то есть читатель, послушавшийся её вместо грепа, получит ложное число. +**Улика — команда:** `grep -n '^ version:' docs/architecture/14-api-contract/openapi.yaml; grep -n 'ContractVersion = ' platform/internal/httpapi/capabilities.go; grep -n '| D39.180 |' docs/architecture/05-decisions-index.md | cut -c1-150` +**Улика — вывод:** 5: version: 0.9.0 +13:const ContractVersion = "0.9.0" +244:| D39.180 | 31.08 | **Пак платформы P12 «долги под ногами» ПРИНЯТ И ЗАЛЕНДЖЕН** вместе с кон +**Другие носители того же факта:** docs/README.md:29, docs/product-requirements.md:56, docs/PROGRESS.md:10 +**Предложенное действие:** Заменить «Канон контракта — 0.8.0» на «Канон контракта — 0.9.0 (D39.180, лендинг пака P12 31.08)» и дописать 0.9.0 в перечень миноров. Те же три носителя (README §активные промты, product-requirements ПТ-20, CURRENT-STATE в PROGRESS) правятся тем же заходом — иначе класс E воспроизведётся: product-requirements сам предупреждает «версия здесь живёт хуже всего». +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие верно; предпочтительна названная автором «лучшая» ветка — внести объёмный потолок, гейт схемы и закон раскрытия В САМУ таблицу слоёв и снять оговорку целиком, иначе числительное будет протухать при каждом следующем лендинге. Если правится только текст — не заменять «двух» на «трёх» (то же протухнет), а писать «ряда» + ссылку на реестр нот. + +--- + +## N027 · MAJOR · `docs/architecture/10-prompt-architecture.md:51` · C-исполненное-как-открытое +**Срез:** Архитектура промптов — docs/architecture/10-prompt-architecture.md +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** 4. **Билингв-редактору src→dst вместо голых dst-форм?** — открытый дизайн-вопрос D30.1, после exp15. +**Почему протухло:** Вопрос закрыт и ПОСТРОЕН: WS2 пака-11 отрендерил редакторский констрейнт-блок именно как src→dst-мэппинг, и D39.13 ратифицировал это дословно как «закрытие D30.1» (19.07 — через три дня после рождения дока). В коде это несущая константа снапшота RenderFormatVersion = renderfmt-v3-draft-gender+editor-src2dst+dc3-gender, а доккоммент RenderEditorConstraintBlock прямо говорит «WS2 §2в resolves the D30.1-open question». +**Улика — команда:** `grep -n 'RenderFormatVersion = ' backend/internal/membank/memory.go; sed -n '745,747p' backend/internal/membank/memory.go; grep -n '^| D39.13 ' docs/architecture/05-decisions-index.md; sed -n '212p' docs/archive/architecture/05-decisions-D39-arch-reset.md | grep -o 'src→dst констрейнт-блок редактора.\{0,30\}'` +**Улика — вывод:** memory.go:738: const RenderFormatVersion = "renderfmt-v3-draft-gender+editor-src2dst+dc3-gender" +memory.go:745-747: // RenderEditorConstraintBlock serializes the selected records' CONFIRMED renderings into the +// editor's injection as a src→dst MAPPING (WS2 §2в resolves the D30.1-open question): "源термин → +// «dst»", like the translator block, NOT bare canonical Russian. +05-decisions-index.md:75: | D39.13 | 19.07 | Приёмка пака-11 Block A: WS2 целиком (бюджет в выходных токенах, edit-единицы, src→dst-блок редактора = закрытие D30.1) + WS1a/b... +05-decisions-D39-arch-reset.md:212: **src→dst констрейнт-блок редактора** [закрытие D30.1] +**Другие носители того же факта:** docs/architecture/09-target-architecture.md:144-145 — «Убрать устаревшую D1-моно-рационализацию из renderEditorConstraintBlock (редактор теперь БИЛИНГВ, D30.1); открытый вопрос — давать ли билингв-редактору src→dst-мэппинг, а не голые dst-формы» (живой арх-док, тот же протухший открытый вопрос; заодно имя функции сегодня экспортное — RenderEditorConstraintBlock), docs/architecture/09-target-architecture.md:77 — «инъекция per-role (переводчику src→dst, редактору CONFIRMED-dst-констрейнты)»: половина верна (CONFIRMED-only), половина протухла — редактору сегодня едут src→dst-пары, а не голые dst-формы, docs/research/20-bank-mining.md:780 — «Открытый вопрос D30.1-редактора (src→dst вместо голых dst-форм, memory.go:505-511)»: тело ресёрча, по D23.3 не переписывается, но указатель memory.go:505-511 туда больше не ведёт +**Предложенное действие:** Заменить п.4 на закрытый: «**Билингв-редактору src→dst вместо голых dst-форм — ЗАКРЫТО** (D39.13, пак-11 WS2): констрейнт-блок редактора рендерится как src→dst-мэппинг, CONFIRMED-only; форма фолдится в снапшот через `RenderFormatVersion` (`memory.go:738`). Довод — дизамбигуация омонимичного dst.» Тем же касанием оркестратору: 09-target-architecture.md:144-145 и :77 несут ту же протухшую формулировку. +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие верное по существу, две поправки. (1) Не вешать в правку сырой номер строки `memory.go:738` — именно такие якоря и гниют дрейфом (в этом же доке §2 уже несёт ⚠ «пути таблицы устарели механически»); назвать символ: константа `RenderFormatVersion` в `internal/membank/memory.go`, проводка — `internal/pipeline/chunkrun.go:102`. (2) В 09-target-architecture.md:144-145 протух НЕ только хвост про открытый вопрос: первая половина буллета («убрать устаревшую D1-моно-рационализацию») тоже ИСПОЛНЕНА — доккоммент сегодня читается «the editor is BILINGUAL (editor.md feeds it the source)», — а имя функции в буллете строчное (`renderEditorConstraintBlock`), тогда как в коде символ экспортный `RenderEditorConstraintBlock` (`memory.go:769`; строчного варианта в backend/*.go нет вовсе — он остался только в backend/docs/D15.2-спеке и в телах экспериментов). То есть буллет сносится целиком, а не правится хвостом. + +--- + +## N028 · MAJOR · `docs/architecture/10-prompt-architecture.md:52` · C-исполненное-как-открытое +**Срез:** Архитектура промптов — docs/architecture/10-prompt-architecture.md +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** 5. **Q4a/Q4c exp15** — где жить структуре (переводчик/редактор/отдельный пасс) — решится эмпирикой. +**Почему протухло:** Q4a ИЗМЕРЕН 18.07 и ратифицирован D39.9 («верность НЕ покупается на translate-стадии»), а топологию ролей решил эксп-21 (D39.117: full-regen, починка = пре-гейт ПЕРЕД редактором). Открытым остался только Q4c. Собственная §1 того же дока (испр. 22.08) это уже знает и говорит ровно противоположное — §4 п.5 не был поправлен тем же касанием, и читатель §4 уходит с выводом, что структура «решится эмпирикой», хотя эмпирика получена и ратифицирована. +**Улика — команда:** `grep -n '^| D39.9 \|^| D39.117 ' docs/architecture/05-decisions-index.md; sed -n '391p' docs/experiments/19-editor-contract-q4b.md; sed -n '18p;52p' docs/architecture/10-prompt-architecture.md | cut -c1-160` +**Улика — вывод:** index:71: | D39.9 | 18.07 | Q4a «сильный переводчик»: верность НЕ покупается на translate-стадии (flash уже верен, pro ×3.67 не ратифицирован); смысл-катастрофы рождает glm-РЕДАКТОР → свап-арм приоритетен | закрыт +index:181: | D39.117 | 08.08 | Эксп-21 принят: строка 65 закрыта; починка = пре-гейт перед редактором; опровергнут вывод D39.108 | ЖИВОЕ: диспозиции 142/65/106/55 +exp19:391: Reflow-проход (Q4c) не гонялся — проза и переверстка НЕ сравнивались +doc10:18: ... Q4a ИЗМЕРЕН (D39.9: верность на translate-стадии не покупается) ... Не гонялся только Q4c +doc10:52: 5. **Q4a/Q4c exp15** — где жить структуре ... решится эмпирикой. +**Предложенное действие:** Заменить п.5 на: «**Q4c** (reflow отдельным пассом) — единственный не прогнанный арм exp15 (`19-editor-contract-q4b.md:391`). Q4a измерен и закрыт D39.9; топология ролей решена эксп-21 (D39.117: full-regen, починка — пре-гейтом ПЕРЕД редактором, строка 142).» +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие верное. Добавить в тот же обход одну проверку, которую автор не сделал: 09-target-architecture.md:138 несёт «⚠ Арм «переводчик отдаёт структуру» никогда не тестировался», что тем же D39.9 выглядит протухшим — но осторожно: D39.9 мерил ВЕРНОСТЬ черновика, а не структуру, так что это ЛИД для отдельной сверки, а не готовая правка. Ссылку на Q4c лучше дать якорем секции (`19-editor-contract-q4b.md` §2.10 «Границы»), а не только номером строки. + +--- + +## N029 · MAJOR · `docs/architecture/13-tech-debt-anchors.md:52` · E-второй-носитель +**Срез:** Ранние арх-доки (кандидаты в архив): docs/architecture/01-decisions.md · 02-mvp-plan.md · 04-unhappy-paths.md · 06-memory-risk-registry.md +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** F4-бэкап (83): 0 вхождений VACUUM/integrity_check/backup во всём backend/, tmctl без такой подкоманды (main.go:73-83). +**Почему протухло:** Это ВТОРОЙ (и единственный не-баннированный) носитель статуса строки F4 реестра памяти, и он разошёлся с кодом: `tmctl backup` существует (cmd/tmctl/main.go:243 `case "backup"`, в списке команд main.go:184), `store.IntegrityCheck` с `PRAGMA integrity_check` — internal/store/backup.go:18,79, пре-флайт-бэкап зовётся из translate (main.go:221,236). Всё это приехало коммитом 9f60746 (D39.82) ПОСЛЕ того, как строка сводки была написана (036dc78, D39.67). Живой журнал уже знает правду: docs/PROGRESS.md:93 говорит про «F4-хвост» (т.е. механизм есть, остались доработки), а не про отсутствие. Заодно уехал и якорь: main.go:73-83 сегодня — словарь exit-кодов, а не список подкоманд. +**Улика — команда:** `cd /home/ubuntu/projects/textmachine-main && grep -rniI 'VACUUM|integrity_check|backup' -E --include='*.go' backend/ | grep -v _test | head -6; grep -n 'case "backup"' backend/cmd/tmctl/main.go; git log --oneline --diff-filter=A -1 -- backend/internal/store/backup.go; git log --oneline -1 -S 'F4-бэкап (83)' -- docs/architecture/13-tech-debt-anchors.md` +**Улика — вывод:** backend/cmd/tmctl/main.go:184: "translate", "report", "status", "export", "build", "redrive", "manifest", "backup", "migrate", "seed-lint", +backend/cmd/tmctl/main.go:221: if err := preflightBackup(inv.cfgPath, os.Stdout); err != nil { +backend/cmd/tmctl/main.go:243: case "backup": +backend/internal/store/backup.go:18:// IntegrityCheck runs `PRAGMA integrity_check` against the SQLite file at dbPath … +backend/internal/store/backup.go:79:// integrityCheck runs the pragma over db and demands the single canonical "ok" row … +243: case "backup" +9f60746 Land smallpack tails as D39.82: … tmctl backup with production preflight … +036dc78 Ratify D39.67: … debt anchors moved to living architecture referen +**Другие носители того же факта:** docs/architecture/06-memory-risk-registry.md:88 (строка F4 со статусом «🔶 нет строки бэкапа/интеграла» — покрыта ⚠-баннером 25.07 «статусы ❌/🔶 датированы 04.07», потому НЕ находка), docs/PROGRESS.md:93 (актуальный носитель: «F4-хвост» — механизм есть, остались доработки) +**Предложенное действие:** Переписать пункт сводки: «F4-бэкап: ЗАКРЫТ D39.82 (9f60746) — `tmctl backup` (main.go:243), `store.IntegrityCheck` + PRAGMA integrity_check (store/backup.go:18,79), пре-флайт в translate (main.go:221,236); остаток — строка 93 PROGRESS (dry-run обходит пре-флайт)». Якорь main.go:73-83 заменить на main.go:184 (список команд). +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Находка верна, но предложенное действие бьёт мимо в трёх местах. + +1) НЕ переписывать сводку заново своим текстом. Строка 16 того же файла уже несёт ратифицированную формулировку закрытия («→ ИСПОЛНЕНО D39.82: `tmctl backup` + pre-flight (`backend/cmd/tmctl/backup.go`), строка 83 закрыта; остаток F4-хвоста — строка 93»). Минимальная и достаточная правка — привести сводку в соответствие с ней тем же механизмом, который объявлен баннером дока: вставить в предложение о F4 стрелку «→ ЗАКРЫТО D39.82 (9f60746), см. строку 16» и не трогать саму запись свипа. Сводка — часть протокола верификационного свипа D39.66 (author≠reviewer, evidence-документ); переписывать её вердикт задним числом — то же, что править тело ресёрча по норме D23.3. Помечать закрытие — можно и нужно, стирать улику свипа — нет. + +2) Якорь заменить не на «main.go:184», а на «main.go:183-186 (`dispatchCommands`)»: :184 — лишь одна строка литерала внутри объявления, и она сама поедет при следующей вставке команды. Ещё устойчивее — якорить по ИМЕНИ символа (`dispatchCommands` в `backend/cmd/tmctl/main.go`), без номера строки: именно этот якорь только что и сгнил номером. Отдельно стоит заметить в отчёте, что в репозитории есть анкор-гейт (коммит 37206fd «Refresh two doc anchors … keeping the anchor gate clean»), и он этот случай НЕ поймал — диапазонная форма «main.go:73-83» внутри прозаической строки, видимо, вне его хватки; это отдельный вопрос оркестратору, а не правка дока. + +3) Не расширять правку на строки 16-18. Тело среза (строка 18) содержит те же протухшие утверждения в настоящем времени, но оно стоит НЕПОСРЕДСТВЕННО под стрелкой закрытия — то есть механизм баннера там отработал. Трогать его — оверрич. + +4) Второй носитель в отчёте назвать точно: docs/architecture/06-memory-risk-registry.md:84 (не :88), статус «🔶 нет строки бэкапа», покрыт ⚠-баннером строки 11 — НЕ находка, флагать не надо. + +--- + +## N030 · MAJOR · `docs/architecture/13-tech-debt-anchors.md:72` · D-противоречие-ратифицированному +**Срез:** Реестр загейченных триггеров — docs/architecture/13-tech-debt-anchors.md (весь реестр, §Б-108 первым) +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** сегодня `gender: neuter` в сиде — ТИХИЙ no-op без линта, т.е. дыра хуже «невыразимо» — при стройке носителя добавить и seed-lint-отбраковку неизвестных значений рода +**Почему протухло:** Обе половины ложны и противоречат ратифицированной D39.69 §3 («строки 27/84 ЗАКРЫТЫ … neuter end-to-end (enum · gender_neuter в injection-данных · SeedLint громко отбраковывает неизвестный род)»). В коде: `genderConstraintNote` отдаёт `tx.GenderNeuter` на `case "neuter", "n"` — значит это НЕ no-op; `GenderVocabViolations` вызывается из сид-линта (`memseed.go:533`) — значит линт ЕСТЬ, и его собственный докстринг говорит о neuter в прошедшем времени: «the row-84 class where gender:neuter (now valid) was an unnoticed no-op». Строка 84 в таблице бэклога отсутствует (закрыта), а тело пункта выше (пере-сверка 23.08) уже это признаёт — не обновлён только абзац «Фикс/скоуп», то есть предписание к действию. +**Улика — команда:** `sed -n '846,850p' backend/internal/membank/memory.go; sed -n '531,534p;540,552p' backend/internal/membank/memseed.go; grep -nE '^\| 84 \|' docs/PROGRESS.md; grep -nE '^\| (85|86) \|' docs/PROGRESS.md | cut -c1-40` +**Улика — вывод:** case "neuter", "n": + return tx.GenderNeuter +--- + if bad := GenderVocabViolations(entries); len(bad) > 0 { +--- +var knownGenders = map[string]bool{ + "": true, "male": true, "m": true, "female": true, "f": true, "neuter": true, "n": true, "hidden": true, +} +// GenderVocabViolations lists seed rows whose gender is outside the known vocabulary — the row-84 class where +// gender:neuter (now valid) was an unnoticed no-op. +--- +(grep '^| 84 |' — пусто: строки 84 в бэклоге НЕТ) +78:| 86 | **Мелкие открытые +91:| 85 | **Пилот-преп добор** +**Другие носители того же факта:** docs/architecture/13-tech-debt-anchors.md:104 (Сводка того же среза — вторая копия того же протухшего утверждения), docs/archive/architecture/05-decisions-D39-59-90.md (архивный носитель, легитимен как история) +**Предложенное действие:** Заменить абзац «Фикс/скоуп» пункта на: «→ **ЗАКРЫТО D39.69 §3 (строки 27/84): neuter end-to-end построен** — `genderConstraintNote` `case "neuter","n"` (`memory.go:846-848`), `GenderNeuter` в `lang/embedded.go:398/456`, seed-lint `GenderVocabViolations` (`memseed.go:533/546`), ось = scoped `neuter-directive-v1`-фолд в `memory_version`. **ЖИВОЙ ОСТАТОК ОДИН — косметика:** комментарий схемы `store/migrate.go:184` всё ещё перечисляет `male|female|hidden|''` без neuter.» Пункт (б) про `decl.invariant` оставить. +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие в целом верно и его якоря я проверил (memory.go:848-849 внутри предложенного 846-848 ✓; embedded.go:398 `GenderNeuter string` и :456 `case "gender_neuter":` ✓; memseed.go:533 вызов и :546 докстринг ✓; живой остаток migrate.go:184 `-- male|female|hidden|'' (C2 …)` без neuter ✓). ДВЕ правки к нему. (1) Не вписывать НОВЫЕ голые номера строк: этот файл гниёт именно ими (см. вердикт по строке 134), а в репозитории есть гейт, который сверяет якорь по содержимому ТОЛЬКО в опт-ин токен-форме `` `путь:848`=`case "neuter", "n":` `` (docs/scripts/counts.py, ратифицировано D39.126, бэклог-строка 205). Тело этого же пункта на строке 70 УЖЕ написано в токен-форме — новый абзац обязан быть в ней же, иначе закрытие протухнет третьим кругом. (2) Правку строки 72 нельзя делать в отрыве от строки 104: там ВТОРАЯ, противоположная копия того же факта (находка №2), и починка одной половины оставит документ противоречащим самому себе. + +--- + +## N031 · MAJOR · `docs/architecture/13-tech-debt-anchors.md:100` · C-исполненное-как-открытое +**Срез:** Реестр загейченных триггеров — docs/architecture/13-tech-debt-anchors.md (весь реестр, §Б-108 первым) +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** feed_cap НЕ построен: grep feed_cap/FeedCap по backend/ = 0 +**Почему протухло:** feed_cap ЗАКРЫТ ратифицированной нотой D39.69 §1 («value-порядок отреза бюджета (частота-desc — feed_cap ЗАКРЫТ этим, из строки 86 изъят)»), и код это несёт: terminology.Batch упорядочивает батчи по УБЫВАНИЮ частоты именно чтобы бюджетный потолок резал наименее ценное, а не лексикографический хвост; греп, объявленный нулевым, сегодня даёт два хита. Пункт стоит без стрелки закрытия, вопреки собственной норме файла «исполненный пункт помечать ссылкой на D-номер»; та же ложь повторена в «Сводке среза» (строка 104: «feed_cap: не построен (grep=0)»). Трекер уже исправлен — строка 86 бэклога несёт «D39.69(feed_cap закрыт §1)». +**Улика — команда:** `grep -rn 'feed_cap' backend/ | head; sed -n '1162,1165p' backend/internal/terminology/terminology.go; cd backend && go test ./internal/terminology/ -run TestBatchValueOrder -count=1 -v` +**Улика — вывод:** backend/internal/terminology/series_test.go:199:// (feed_cap) rather than the lexicographic tail. +backend/internal/terminology/terminology.go:1164:// (feed_cap, §7). +--- +// The batches are then ordered by descending source frequency, so a budget ceiling drops the least-frequent terms instead of the lexicographic tail +// (feed_cap, §7). +--- +=== RUN TestBatchValueOrder +--- PASS: TestBatchValueOrder (0.00s) +PASS +ok textmachine/backend/internal/terminology 0.002s +**Другие носители того же факта:** docs/PROGRESS.md:78 (строка бэклога 86 — уже несёт закрытие «D39.69(feed_cap закрыт §1)»), docs/architecture/05-decisions-log.md:194 (тело D39.69 — носитель закрытия), docs/architecture/05-decisions-log.md:182 (тело D39.45 — историческая констатация ДО закрытия), docs/architecture/13-tech-debt-anchors.md:104 (Сводка среза — вторая копия протухшего утверждения в том же файле) +**Предложенное действие:** Дописать по месту стрелку, как велит норма файла: «→ **ЗАКРЫТО D39.69 §1: value-порядок отреза бюджета (частота-desc) построен — `terminology.Batch`, гвоздь `TestBatchValueOrder`; из строки 86 изъят.**» Строку 102 «Занесено правильно … формулировка точна» заменить на «формулировка БЫЛА точна на 01.08; опровергнута лендингом D39.69». В «Сводке среза» (строка 104) убрать фрагмент «feed_cap: не построен (grep=0), и претензия … подтверждена кодом — батчи в лексикографическом порядке src». +**Разрушающее:** False + +--- + +## N032 · MAJOR · `docs/architecture/13-tech-debt-anchors.md:134` · B-мёртвый-указатель +**Срез:** Реестр загейченных триггеров — docs/architecture/13-tech-debt-anchors.md (весь реестр, §Б-108 первым) +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** membank/mempostcheck.go:12 — пост-чек стоит на посылке «SRC actually fired in THIS chunk»; «сработал» = Aho-Corasick ПОДСТРОКА (memory.go:532). Han-ключи намеренно НЕ проверяются на границы: memory.go:1071-1072 +**Почему протухло:** Единственная объявленная функция файла — быть справочником file:line-якорей («Этот файл — справочные file:line-якоря … Актуализируется лендингами паков»). Механизм не работает: сплошной проверкой 59 якорей (по ДОСЛОВНО процитированному в доке токену) 29 не указывают на то, что цитируют. Дрейф не косметический: `status.go:561-582` (RedriveSelector) → 907 (+346); `render.go:308` («incl. dropped c-lite members») → 471; `cheapgates.go:414-416` (гейт magnitudeStem) → 549; `terminologist.go:603` → 380 (−223); `terminology.go:917-919` (Batch) → 1172; `book.go:250` (валидатор ceilings) → 321; `archguard.go:79-82` — цель `touchesPkg` вообще исчезла (переписана в `reaches`/`fromPkg` дофиксом D39.97). Особый случай: якорь KANA/HAN уже правился один раз в этом же файле (1048→1071) и уехал СНОВА — фактически 1077, то есть обе редакции мертвы одновременно. +**Улика — команда:** `grep -n 'KANA and HAN are deliberately NOT boundary-checked' backend/internal/membank/memory.go; grep -n 'type RedriveSelector struct' backend/internal/pipeline/status.go; grep -n 'incl. dropped c-lite members' backend/cmd/tmctl/render.go; grep -n 'len(c.magnitudeStem) == 0' backend/internal/checks/cheapgates.go; grep -n 'run.texts\[i\]' backend/internal/pipeline/terminologist.go | head -1; grep -c 'touchesPkg' backend/internal/archguard/archguard.go` +**Улика — вывод:** memory.go:1077 (док: 1048-1051 и, в исправленной редакции, 1071-1072) +status.go:907 (док: 561-582) +render.go:471 (док: 308) +cheapgates.go:549 (док: 414-416) +terminologist.go:380 (док: 603) +archguard.go: touchesPkg = 0 хитов (при 17 живых func в файле — цель переименована дофиксом D39.97) +Сплошной прогон 59 якорей: == OK=30 DRIFT/GONE=29 == +**Другие носители того же факта:** docs/PROGRESS.md:89 (строка бэклога 81 — несёт ИСПРАВЛЕННЫЙ якорь migrate.go:187/194, тогда как справочник на строке 12 всё ещё пишет :186), docs/PROGRESS.md:251 (строка 111 — свой якорь langpack.go:806-826, тоже мимо: cats[f[0]][f[1]] на 829), docs/PROGRESS.md:255-256 (строки 121/122 явно отсылают за file:line именно в §Б-121/§Б-122 этого файла) +**Предложенное действие:** Не переписывать 29 чисел руками — это третий круг того же гниения (якорь memory.go правился и уехал снова). Завести $0-гвоздь: тест/скрипт, который читает пары «путь:строка + дословная цитата» из этого файла и краснеет, когда цитата не лежит на названной строке (форма уже отработана в репо — `TestMemnormXTextVersionPin` пинит константу против go.mod). До гвоздя — заменить голые номера строк на ЦИТАТУ+имя символа (`membank.InjectivityCollisions`, `RedriveSelector`), которые греп находит без номера, и поставить в шапку файла предупреждение «номера строк — снимок лендинга, сверяйся грепом токена». +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Находка верна, ПРЕДПИСАНИЕ НЕВЕРНО в главном: «завести $0-гвоздь» — предложение построить то, что УЖЕ ПОСТРОЕНО И РАТИФИЦИРОВАНО. Гейт якорей живёт в `docs/scripts/counts.py` (D39.126; исполнен 21.08, носитель — бэклог-строка 205 в docs/PROGRESS.md:185), сверяет якорь ПО СОДЕРЖИМОМУ через опт-ин токен ожидания `` `путь:123`=`подстрока` `` (регексп ANCHOR, counts.py:344-347) и вплетён в pre-commit хук (D39.148, warn-only). Я его исполнил ($0): `python3 docs/scripts/counts.py --lint` → «0 проблемных якорей в 100 живых доках · сверено ПО СОДЕРЖИМОМУ (токен): 137 · **без токена — несверяемы: 708**». Вот и объяснение зелени при 14 уехавших якорях: в самом 13-tech-debt-anchors.md токен-форму несут ВСЕГО ШЕСТЬ якорей (`grep -c '=`' = 4 строки), остальные ~130 гейт не видит по построению. Поэтому правильное действие: не строить второй гвоздь, а МИГРИРОВАТЬ якоря этого файла в токен-форму — ровно тот «органический остаток», который строка 205 уже объявила («токен требуется на ЗАТРОНУТЫХ строках, поэтому счёт растёт сам с каждым касанием»); этот док — концентрат якорей, и на нём органика не сработала, значит он законный кандидат на разовую пакетную миграцию, а не на новый механизм. Совет «поставить в шапку предупреждение о снапшоте» верен и дёшев (форма уже отработана на строке 162 и 94 этого же файла). Две поправки к фактуре самой находки: (1) цель якоря `terminologist.go:603` — это `run.texts[i] = att.text` на строке 817, а не 380 (на 380 другое чтение `run.texts[i]`); (2) при планировании учесть бэклог-строку 244 (docs/PROGRESS.md:206) — известный дефект гейта: хук судит ЗАКОММИЧЕННЫЙ док против РАБОЧЕГО кода и врёт в обе стороны. + +--- + +## N033 · MAJOR · `docs/architecture/13-tech-debt-anchors.md:180` · C-исполненное-как-открытое +**Срез:** Реестр загейченных триггеров — docs/architecture/13-tech-debt-anchors.md (весь реестр, §Б-108 первым) +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** grep sourceScripts/SourceScripts по cheapgates.go — 0 вхождений: механизм SetSourceScripts существует (checkers.go:104-125) и используется другими путями, но lintNumberMagnitude его НЕ читает +**Почему протухло:** Гейт по письму источника ПОСТРОЕН и стоит внутри самой lintNumberMagnitude; код называет заказ по имени в комментарии — «SOURCE-script gate (строка 79, D39.39)». Греп, объявленный нулевым, даёт хит cheapgates.go:555. Есть гвоздь `TestNumberMagnitudeSourceScriptGate`, который проверяет ровно три случая (zh → файрит, en → инертен, ja → файрит). Вердикт строки 182 «Не требуется — дописка 79 точна» сегодня ложен, и это самый дорогой класс: сессия, читающая справочник, возьмётся строить уже построенное. Трекер снова впереди — строка 79 бэклога уже пишет, что этот компонент исполнен. +**Улика — команда:** `grep -ni 'sourcescripts' backend/internal/checks/cheapgates.go; sed -n '552,557p' backend/internal/checks/cheapgates.go; cd backend && go test ./internal/checks/ -run TestNumberMagnitudeSourceScriptGate -count=1 -v` +**Улика — вывод:** 555: if len(c.sourceScripts) > 0 && !c.sourceHasDenseScript() { +--- + // SOURCE-script gate (строка 79, D39.39): the 万/億/兆 markers are Han, so a DECLARED non-dense source + // cannot carry a magnitude — stay inert rather than firing on a stray Han rune quoted inside e.g. an + // English source. An UNDECLARED source (nil scripts) keeps the prior content-only behaviour. + if len(c.sourceScripts) > 0 && !c.sourceHasDenseScript() { + return 0, nil + } +--- +=== RUN TestNumberMagnitudeSourceScriptGate +--- PASS: TestNumberMagnitudeSourceScriptGate (0.00s) +PASS +ok textmachine/backend/internal/checks 0.011s +**Другие носители того же факта:** docs/PROGRESS.md:85 (строка бэклога 79 — уже несёт «Первые два компонента строки (isCyrLetter→isTargetWordLetter · magnitude SourceScripts-гейт) исполнены пакетом-чекеров») +**Предложенное действие:** Пометить пункт стрелкой: «→ **ИСПОЛНЕНО пакетом-чекеров: SOURCE-script-гейт стоит в `lintNumberMagnitude` (`cheapgates.go:555`, `sourceHasDenseScript` — `checkers.go:507`), гвоздь `TestNumberMagnitudeSourceScriptGate` (zh файрит · en инертен · ja файрит); в строке 79 бэклога компонент уже числится исполненным.**» Строку 182 «Не требуется — дописка 79 точна» заменить на констатацию исполнения. Остаток строки 79 (граница плотности через Go-константу `cjkScriptNames`) НЕ трогать — он жив. +**Разрушающее:** False + +--- + +## N034 · MAJOR · `docs/architecture/14-api-contract/README.md:8` · A-дрейф +**Срез:** Контракт API и его зеркала — docs/architecture/14-api-contract/README.md + docs/architecture/14-api-contract/openapi.yaml +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** ⚠ Зонная копия frontend/docs/api-contract/ ВРЕМЕННО ОТСТАЁТ (0.2.3 при каноне +0.8.0) — ратифицировано D39.142 п.5 на время фриза фронта +**Почему протухло:** Канон стоит на 0.9.0 с 31.08 (минор заленджен D39.180 вместе с паком P12), а ревью-шапка всё ещё называет базой 0.8.0. Верно: зеркало 0.2.3 против канона 0.9.0 — СЕМЬ миноров, а не шесть, и цена разморозки зоны в шапке занижена на один ломающий шаг. +**Улика — команда:** `grep -n 'version:' docs/architecture/14-api-contract/openapi.yaml | head -1; grep -n 'version:' frontend/docs/api-contract/openapi.yaml | head -1; grep -n '^## D39.180' docs/architecture/05-decisions-log.md` +**Улика — вывод:** docs/architecture/14-api-contract/openapi.yaml:5: version: 0.9.0 +frontend/docs/api-contract/openapi.yaml:5: version: 0.2.3 +1677:## D39.180 — ПАК ПЛАТФОРМЫ P12 «ДОЛГИ ПОД НОГАМИ» ПРИНЯТ И ЗАЛЕНДЖЕН вместе с контрактным минором 0.9.0 (31.08, оркестратор №21). ✅ +**Другие носители того же факта:** docs/PROGRESS.md:3 — «зеркало отстало на ШЕСТЬ миноров (`frontend/docs/api-contract/openapi.yaml` — `0.2.3` против канона `0.8.0`, и первый из них — ЛОМАЮЩИЙ 0.3.0), 16 операций против 20», frontend/docs/frontend-PROGRESS.md:9 — «Состояние зеркала контракта — 0.2.3 против канона 0.8.0 (и 0.9.0 придёт с лендингом P12): шесть миноров» — P12 уже лёг, скобка протухла вместе с числом +**Предложенное действие:** В шапке README заменить «(0.2.3 при каноне 0.8.0)» на «(0.2.3 при каноне 0.9.0)»; тем же проходом поправить оба других носителя: в docs/PROGRESS.md:3 и frontend/docs/frontend-PROGRESS.md:9 — «ШЕСТЬ миноров» → «СЕМЬ миноров», «против канона 0.8.0» → «против канона 0.9.0», снять скобку «(и 0.9.0 придёт с лендингом P12)». +**Разрушающее:** False + +--- + +## N035 · MAJOR · `docs/architecture/14-api-contract/README.md:981` · C-исполненное-как-открытое +**Срез:** Контракт API и его зеркала — docs/architecture/14-api-contract/README.md + docs/architecture/14-api-contract/openapi.yaml +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** Исчерпанность: платформа отвечает `202` и возвращает прогон в то же состояние (`reconcile.go`, оба выхода `exhausted` из `reopen` → ветка `exhausted` в `Resume` → `httpapi/v0.go`, хендлер `resumeRun`), а контракт теперь требует `409` `run_not_resumable` · `cause.code: ceiling_reached` +**Почему протухло:** Расхождение ЗАКРЫТО: `Resume` больше не отвечает 202 на исчерпанный прогон — вердикт `ceilingSpent` возвращает `ErrCeilingReached`, и `httpapi/v0.go:807` кладёт его в 409 `run_not_resumable` · `cause: ceiling_reached`. Регистр платформы держит PD-282 как `fixed(акт 5)` с пином теста. Ветки `exhausted` в `reopen`/`Resume`, на которую указывает док, в коде НЕТ вовсе — она пере-названа в `ceilingSpent`/`creditUnavailable`. +**Улика — команда:** `grep -n 'ceilingSpent\|ErrCeilingReached' platform/internal/runs/reconcile.go platform/internal/httpapi/v0.go | grep -v _test; grep -n 'exhausted' platform/internal/runs/reconcile.go; grep -o 'PD-282[^|]*| fixed[^|]*|' platform/docs/DEFECT_REGISTER.md` +**Улика — вывод:** platform/internal/runs/reconcile.go:1325: ceilingSpent +platform/internal/runs/reconcile.go:1371: return pgstore.LiveRun{}, ceilingSpent, nil +platform/internal/runs/reconcile.go:1570: case ceilingSpent: +platform/internal/runs/reconcile.go:1578: return pgstore.Run{}, ErrCeilingReached +platform/internal/httpapi/v0.go:807: case errors.Is(err, runs.ErrCeilingReached): +--- grep 'exhausted' (положительный контроль: греп В ЭТОМ ФАЙЛЕ находит) --- +950: // The reason it is not cosmetic: a ceiling reason of `credit_exhausted` lights the ACCOUNT-level +980: // this way. Guessing `credit_exhausted` would light the account-level halted flag +1277: // the account's reason is what made it look like an exha +**Положительный контроль:** grep -n 'exhausted' platform/internal/runs/reconcile.go → 3 хита (950, 980, 1277). Инструмент в этом файле находит слово; отсутствует именно ВЕТКА `case "exhausted"`, а не способность грепа её увидеть. +**Другие носители того же факта:** docs/architecture/14-api-contract/README.md:1724 — Приложение А-2, строка `run_not_resumable`: «где платформа сегодня молча отвечает `202`» с тем же мёртвым указателем «ветка `exhausted` функции `reopen`», platform/docs/DEFECT_REGISTER.md:446 — PD-282, уже помечен fixed (носитель актуален, менять нечего) +**Предложенное действие:** В §3 (строка 981) заменить «платформа отвечает `202` и возвращает прогон в то же состояние (…ветка `exhausted`…)» на «СОШЛОСЬ: платформа отвечает `409` `run_not_resumable` · `cause: ceiling_reached` (`runs/reconcile.go`, вердикт `ceilingSpent` в `Resume` → `httpapi/v0.go:807`); PD-282 закрыт, пин `runs.TestResumeOfARunWithNothingLeftIsRefusedWithTheCeilingReached`». То же — в Приложении А-2, строка 1724: снять «где платформа сегодня молча отвечает `202`» и указатель на ветку `exhausted`, добавить второй вердикт `creditUnavailable` → `cause: credit_unavailable`. +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Предложенное действие верно по сути; добавить четыре вещи. (1) НЕ трогать первую половину строки 981 про паузу — она подтверждена кодом (reconcile.go:1537-1544). (2) В обеих правках назвать ОБА вердикта, а не один: `ceilingSpent` → `cause: ceiling_reached` и `creditUnavailable` → `cause: credit_unavailable` (reconcile.go:1581, v0.go:813) — прежняя редакция дока схлопывала их в один `exhausted`, и разведение как раз и есть суть закрытия. (3) Пин цитировать МОЖНО, но с оговоркой: `TestResumeOfARunWithNothingLeftIsRefusedWithTheCeilingReached` без `TM_PLATFORM_TEST_DSN` уходит в SKIP, поэтому «зелёный тест» доказательством закрытия НЕ является — доказательство это reconcile.go:1570-1578 + v0.go:807-813. (4) Находка назвала не все носители внутри самого дока: третий — README:1265+ (§6в «A. Прогон с исчерпанным бюджетом (PD-282)») повторяет «`Resume` отвечает на оба одинаково: `202` + неизменённый `Run`» и «`reopen` даёт `exhausted` в ДВУХ местах». Это ДАТИРОВАННАЯ запись решения («Синк с платформой 20.08 → канон 0.4.0: что решено и почему»), то есть улика-обоснование, а не живое утверждение: её ТЕЛО переписывать не надо, ей нужен ⚠-указатель «закрыто, PD-282 fixed(акт 5), см. §3». Живой переписи подлежат только 981 и 1724. + +--- + +## N036 · MAJOR · `docs/architecture/14-api-contract/README.md:1637` · A-дрейф +**Срез:** Контракт API и его зеркала — docs/architecture/14-api-contract/README.md + docs/architecture/14-api-contract/openapi.yaml +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** - **Чем и на каком слое сжимать** (middleware, обратный прокси, CDN). Go stdlib не сжимает, + edge-конфига в репозитории нет, `grep -rn "gzip|Content-Encoding" platform --include=*.go` → ноль. +**Почему протухло:** Собственная команда дока, исполненная сегодня, даёт 26 хитов, а не ноль: платформа сжимает САМА, в Go, в `platform/internal/httpapi/conditional.go` (`gzipFloor`, `acceptsGzip`, `head.Set("Content-Encoding","gzip")`). Файл приехал с паком P7 (коммит 9b23e8c), то есть абзац родился верным и сгнил лендингом. Он же противоречит §3 ТОГО ЖЕ дока (строка 994: «Условные чтения (`ETag`/304), сжатие | ПОСТРОЕНО P7 — `httpapi/conditional.go`») и последнему пункту §7 («шаги 1–2 — конфигурация»). +**Улика — команда:** `grep -rnE "gzip|Content-Encoding" /home/ubuntu/projects/textmachine-main/platform --include=*.go | wc -l; grep -rnE "gzip|Content-Encoding" /home/ubuntu/projects/textmachine-main/platform --include=*.go | grep -v _test | head -5; git log --oneline -1 --diff-filter=A -- platform/internal/httpapi/conditional.go` +**Улика — вывод:** 26 +platform/internal/httpapi/conditional.go:5: "compress/gzip" +platform/internal/httpapi/conditional.go:26:// gzipFloor is the size below which compressing costs more than it saves +platform/internal/httpapi/conditional.go:28:const gzipFloor = 1 << 10 +platform/internal/httpapi/conditional.go:64: if acceptsGzip(r) && len(buf) >= gzipFloor { +platform/internal/httpapi/conditional.go:69: head.Set("Content-Encoding", "gzip") +9b23e8c Land the platform reading surface: chapters, units, notes, bank and the event stream, with a materialization debt that survives a restart and account-level halts +**Предложенное действие:** Снести обещание «грепом ноль» и переписать пункт по факту: сжатие ПОСТРОЕНО в приложении (`platform/internal/httpapi/conditional.go`, порог `gzipFloor` 1 КиБ, поток `text/event-stream` не сжимается по построению — регистрируется вне слоя); в эксплуатационном примечании оставить только то, что клиент действительно не наблюдает — не сжимать ВТОРОЙ раз на прокси/CDN. Заодно поправить хвост §7: «шаги 1–2 — конфигурация» на «шаги 1–2 построены P7». +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие в целом верное, но с тремя правками. (1) НЕ подменять число в команде («26 вместо ноля»): команду из абзаца убрать целиком — она BRE-формы и её ноль ничего не значит; вместо ссылки на греп поставить file:line факта (`platform/internal/httpapi/conditional.go:64-70`, порог `gzipFloor` = 1<<10, `Vary: Accept-Encoding` на всяком согласуемом представлении, `acceptsGzip` по RFC 9110 §12.5.3). (2) Клаузу «Go stdlib не сжимает» сохранить как верную, снести только вывод «edge-конфига нет ⇒ деплой обязан устроить сжатие сам»: в эксплуатационном примечании остаётся ровно одно — не жать ВТОРОЙ раз на прокси/CDN и не жать `text/event-stream` (поток по построению не проходит через writeJSON — комментарий conditional.go:20-24 и README:991). (3) Хвост §7 «шаги 1–2 — конфигурация» править обязательно и в ОДНОМ коммите с абзацем 1637 — иначе останется третий расходящийся носитель внутри одного файла (994 говорит «шаги 1–2 закрыты D39.153»). + +--- + +## N037 · MAJOR · `docs/architecture/14-api-contract/README.md:1718` · B-мёртвый-указатель +**Срез:** Контракт API и его зеркала — docs/architecture/14-api-contract/README.md + docs/architecture/14-api-contract/openapi.yaml +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** | `gone` | 410 | `pgstore.ErrNoChapter` → `v0.go:761` … | `run_in_flight` | 409 | `pgstore.ErrRunInFlight` (`v0.go:548`) | … | `payload_too_large` | 413 | `*http.MaxBytesError` → `v0.go:409` | +**Почему протухло:** Вся колонка «Откуда взят (платформа)» Приложения А-2 съехала: реальные производители лежат на 240–270 строк ниже названных, а на названных номерах стоят комментарии и скобки. Это провенанс-карта словаря ошибок — единственное место, откуда следующая сессия узнаёт, где в платформе рождается каждый код, и сегодня она ведёт мимо. Линтер якорей (`python3 docs/scripts/counts.py --lint`) эти якоря НЕ ловит: они «без токена», проверяется только существование файла и длина. +**Улика — команда:** `for l in 409 417 548 550 554 558 562 761; do printf '%4d: ' $l; sed -n "${l}p" platform/internal/httpapi/v0.go | cut -c1-90; done; echo '--- РЕАЛЬНЫЕ ---'; grep -n 'MaxBytesError\|ErrDeadlineExceeded\|ErrNoChapter\|ErrRunInFlight\|ErrBookNotReady\|ErrNotStoppable\|ErrCeilingOutOfBounds\|ErrCeilingNotWired\|func readField' platform/internal/httpapi/v0.go | grep -v _test` +**Улика — вывод:** 409: ) + 417: // + 548: in.File = io.MultiReader(in.File, trailing) + 550: // that is what the deferred release does for every failure below. + 554: h.log.InfoContext(r.Context(), "an upload carried a part after the file and was refused") + 558: h.uploadFailed(w, r, err) + 562: // follows it as given rather than rebuilding the address from an identifier of its own. + 761: return n +--- РЕАЛЬНЫЕ --- +634:func readField(part io.Reader) (string, error) [док: v0.go:394-396] +651: var tooLarge *http.MaxBytesError [док: v0.go:409] +655: case errors.Is(err, os.ErrDeadlineExceeded): [док: v0.go:417] +788: case errors.Is(err, pgstore.ErrNoChapter): [док: v0.go:761] +799: case er +**Положительный контроль:** Тем же методом (sed по номеру) проверены якоря, которые ЖИВЫ: `middleware.go:68` → `Fail(w, r, CodeInternalError)` (в точку), `problem.go:42` → `CodeContentRefused Code = "content_refused"` (в точку), `v0.go:92-96` → строки таблицы `contractSurface` (в точку). Значит метод находит верные цели, и пустота на 409/548/761 — свойство указателей, а не инструмента. +**Другие носители того же факта:** docs/architecture/14-api-contract/README.md:141 — «класс 1 выведен перечислением реальных ветвей `platform/internal/httpapi/v0.go:333-427,542-574`» — тот же съезд, тот же файл, docs/architecture/14-api-contract/README.md:914 — `backend/internal/pipeline/status.go:733-744` («его собственный комментарий предупреждает…»): на 733-744 лежит `orphanStageRows`/`ConfigDrift`, а названный комментарий про `bank-apply` — на status.go:872, docs/architecture/14-api-contract/README.md:604 — `runs/runs.go:187-188` для `book.HasLiveRun` (реально runs.go:295), README:1020 — `runs/runs.go:227-231` для аллоулиста `readyToTranslate` (реально runs.go:382-389) +**Предложенное действие:** Пере-снять всю колонку «Откуда взят» Приложения А-2 и три якоря-спутника (README:141, :604, :914, :1020) против дерева ОДНИМ проходом, ПОСЛЕДНИМ шагом после того как код замер (норма D39.169). Точечно: 400→`v0.go:793-797` + `readField` `v0.go:634-640`, 401→`problem.go:30,111` (в `server.go` производителя нет), 403→`server.go:108-109`, 404 catch-all→`server.go:144`, 408→`v0.go:655`, 410→`v0.go:788-792`, 413→`v0.go:651`, 409-ветки→`v0.go:799/801/805/820`, 503→`v0.go:828`, `books.ErrBadIntake`→`books.go:102-117`. Отдельно завести строку долга: линтер `counts.py --lint` этот класс не покрывает — якорь без токена проверяется только на существование файла. +**Разрушающее:** False + +--- + +## N038 · MAJOR · `docs/architecture/14-api-contract/openapi.yaml:1414` · E-второй-носитель +**Срез:** Контракт API и его зеркала — docs/architecture/14-api-contract/README.md + docs/architecture/14-api-contract/openapi.yaml +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** Today's deployments answer `drafting` or `editing`; other values are not a breaking + change and do not raise this version. +**Почему протухло:** Деплой отвечает ТРЕМЯ значениями: `runStage` (platform/internal/pgstore/readmodel.go:576) выдаёт `re_pass` для всего пере-прохода, помимо `drafting`/`editing`, и значение доезжает на провод (project.go:53 → v0.go:155 `Stage`). Фраза родилась верной в 0.6.0 (58bae30) и сгнила пере-проходом 0.7.0 (7d45e24). Ломающим это не делает (словарь объявлен открытым), но нормативная проза компилируется в JSDoc генерённых типов — клиент учится по ней неправде. +**Улика — команда:** `sed -n '570,579p' platform/internal/pgstore/readmodel.go; grep -n 'Stage' platform/internal/httpapi/project.go platform/internal/httpapi/v0.go | grep -v test; git log --oneline -S"'re_pass'" -- platform/internal/pgstore/readmodel.go` +**Улика — вывод:** 576: runStage = `(case when r.ceiling_chapters = 0 then 're_pass' +577: when ` + editWave + ` and ` + draftBar + ` >= ` + draftWork + ` +578: then 'editing' else 'drafting' end)` +(комментарий 574-575: «always `drafting` where drafting is the only pass there is, and `re_pass` for the whole of a re-pass run, which is neither») +platform/internal/httpapi/project.go:53: Done: r.Progress.Done, Total: r.Progress.Total, Stage: r.Progress.Stage, +platform/internal/httpapi/v0.go:155: Stage string `json:"stage"` +7d45e24 Land the pack that lets a bank correction reach translated text: the run no longer dies on the snapshot guard and a re-pass can be bought +**Другие носители того же факта:** docs/architecture/14-api-contract/README.md:883 — «Сегодняшние значения — `drafting` и `editing`.» (§2.20, тот же факт, та же протухшая пара) +**Предложенное действие:** В openapi.yaml:1414 заменить на «Today's deployments answer `drafting`, `editing` or `re_pass`; other values are not a breaking change and do not raise this version.» и синхронно поправить README:883 («Сегодняшние значения — `drafting`, `editing` и `re_pass` (последнее — на всём пере-проходе).»). ⚠ Правка НОРМАТИВНОГО файла: минор поднимать не надо (словарь открыт по объявлению), но лендинг обязан пройти cmp-сверку и гейт `platform/internal/gates/contract_test.go`. +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Текст правки верен, обоснование и процедура — нет; передать принимающему три поправки. (1) НОСИТЕЛЕЙ ТРИ, а не два: к openapi.yaml:1414 и README:883 добавить комментарий кода `platform/internal/pgstore/books.go:776-777` — он утверждает ту же неправду над самим полем `Stage` и относится к зоне ПЛАТФОРМЫ, значит уходит не правкой оркестратора, а пингом в `platform/docs/platform-PROGRESS.md`. (2) «Гейт `platform/internal/gates/contract_test.go`» страховкой этой правки НЕ является: он читает из канона одну строку `info.version` регэкспом и сверяет с `httpapi.ContractVersion` (contract_test.go:34-40) — нормативной прозы не касается вовсе, а `info.version` эта правка не двигает, так что гейт к ней просто безразличен. (3) «cmp-сверка» невыполнима и её нельзя ставить условием лендинга: байт-зеркало, объявленное самим каноном (openapi.yaml:8-9 «`frontend/docs/api-contract/` is a byte-mirror and a divergence is a defect of one of the two»), СЕГОДНЯ разошлось катастрофически — `frontend/docs/api-contract/openapi.yaml` держит version 0.2.3 и 61 189 байт против канонных 0.9.0 и 147 925 байт (`cmp` расходится уже на строке 5), поля `stage` там нет вовсе. Это отдельный, гораздо более крупный дефект, и правку `stage` на него вешать нельзя — но и рационале находки надо поправить: генерённые типы (`frontend/package.json:19` → `openapi-typescript docs/api-contract/openapi.yaml -o src/api/schema.ts`) компилируются из ЗЕРКАЛА, а не из канона, и в `frontend/src/api/schema.ts` слова `drafting`/`editing`/`re_pass` не встречаются ни разу. Значит «клиент учится по этой прозе неправде» сегодня неверно — клиент вообще не видит этой прозы; вред от протухшей фразы чисто канонический (человек-читатель нормы), и заявлять его надо так. + +--- + +## N039 · MAJOR · `docs/architecture/15-money-path.md:6` · B-мёртвый-указатель +**Срез:** Денежный путь — docs/architecture/15-money-path.md +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** **Стабильна не ЗОНА, а ЧИСТЫЙ ФАЙЛ.** Номер строки ставится только туда, где `git status <цель>` показывает файл чистым … Ось именно такая (D39.177 §2в), и зонная ось её НЕ заменяет +**Почему протухло:** Норму «чистый ФАЙЛ» ратифицировал D39.179 п.4, который сам говорит: «уточняет D39.177 §2в … Ось «зона» была НЕВЕРНА … Носитель нормы — шапка 15-money-path.md». А D39.177 §2(в), на который шапка ссылается, несёт как раз ЗОННУЮ/«живая сессия» ось. То есть единственный указатель на ратификацию ведёт в ноту, которая эту норму ОПРОВЕРГАЕТ, а ратифицирующая нота D39.179 в доке не названа НИ РАЗУ. Читатель, грепнувший D39.177, увидит шапку противоречащей своему же основанию и либо починит её обратно в зонную ось, либо решит, что норма не ратифицирована. +**Улика — команда:** `grep -n 'Норма формы указателей заострена' docs/architecture/05-decisions-log.md; grep -c 'D39\.179' docs/architecture/15-money-path.md` +**Улика — вывод:** 1673:4. **Норма формы указателей заострена (уточняет D39.177 §2в): стабильна не ЗОНА, а ЧИСТЫЙ ФАЙЛ.** … Ось «зона» была НЕВЕРНА и поймана опровергателем на моей же правке … Носитель нормы — шапка `15-money-path.md`. +0 + +Цель ссылки дока (D39.177 §2в): «(в) Якорь по номеру строки в файл, который правит ЖИВАЯ сессия, — заведомо мёртвый … Это касается зонных журналов, канона под минором и кода зоны в работе.» +**Положительный контроль:** grep -o 'D39\.[0-9]*' docs/architecture/15-money-path.md | sort -u → D39.100 D39.110 D39.112 D39.115 D39.122 D39.123 D39.125 D39.131 D39.132 D39.134 D39.154 D39.161 D39.165 D39.167 D39.170 D39.177 D39.84 D39.86 (18 номеров найдено — инструмент в этой области работает, то есть ноль по D39.179 не артефакт грепа) +**Предложенное действие:** Заменить «Ось именно такая (D39.177 §2в)» на «Ось именно такая — ратифицировано D39.179 п.4, который уточняет D39.177 §2в и прямо объявляет зонную ось НЕВЕРНОЙ (там же: «Носитель нормы — шапка 15-money-path.md»)», и внести D39.179 в карту носителей §6. +**Разрушающее:** False + +--- + +## N040 · MAJOR · `docs/architecture/15-money-path.md:19` · A-дрейф +**Срез:** Денежный путь — docs/architecture/15-money-path.md +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** На границе юнита сидел РЕПЭЙР-суб-бюджет — ужесточён до пер-вызовного с ценой вызова (строка 135 закрыта D39.131); эскалационный кап хоп НЕ прицениваает — перелёт ≤1 хопа, задокументирован и запинен +**Почему протухло:** Перечень движковых суб-бюджетов читается как исчерпывающий (репэйр + эскалация) и не знает третьего семейства — книго-широких бюджетов банк-ролей `gates.terminology.budget_usd` и `classify_budget_usd`. Именно оно резало ОПЛАЧЕННУЮ работу на холодном прогоне 31.08 (D39.182 §4: «инцидент холодного прогона был на классификаторе»; комментарий в terminologist.go: «its $0.02 was exhausted … three of four classify batches were never bought»), и именно его поведение ИЗМЕНИЛОСЬ в тот же день: до bb541a8 проход обрывался ПОСЕРЕДИНЕ, после — план режется ЦЕНОЙ ПАРТИИ ДО первого вызова, и срез отчитывается новыми носителями `BatchesDropped`/`ClassifyBatchesDropped`. Денежный вход этого не несёт ни строкой, а калибровка этих цифр ($0.05/$0.02/$0.08 в coldrun-v16) — июльская. ⚠ ПРОВЕРЕНО И ОБРАТНОЕ: утверждение «перелёт ≤1 хопа» ДЕРЖИТСЯ и правки НЕ требует. +**Улика — команда:** `grep -n 'probe+want > plan.budgetUSD' backend/internal/pipeline/terminologist.go; git show bb541a8^:backend/internal/pipeline/terminologist.go | grep -n 'spent+want > plan.budgetUSD'; grep -n 'budget_usd' books/gu-zhenren/coldrun-v16/pipeline.yaml` +**Улика — вывод:** 782: if probe+want > plan.budgetUSD { <- СЕГОДНЯ: план режется ДО первого вызова +725: if want := r.bankCallEstimateUSD(st, msgsPer[i]); !paid && spent+want > plan.budgetUSD { <- ДО bb541a8: обрыв ПОСЕРЕДИНЕ прохода +97: budget_usd: 0.05 +101: classify_budget_usd: 0.02 +110: budget_usd: 0.08 +**Положительный контроль:** ОТРИЦАТЕЛЬНОЕ: grep -in 'classify_budget|terminolog|банк-рол|gates.terminology' docs/architecture/15-money-path.md → 0 хитов (rc=1). ПОЛОЖИТЕЛЬНЫЙ КОНТРОЛЬ в той же области: grep -on 'суб-бюджет|бюджет' docs/architecture/15-money-path.md → 19:суб-бюджет и 22:бюджет — греп по русской теме бюджетов в этом файле работает, значит ноль не артефакт инструмента. +**Предложенное действие:** Дописать в тот же буллет третий пункт: «Банк-роли несут СОБСТВЕННЫЕ книго-широкие бюджеты — `gates.terminology.budget_usd` и `gates.terminology.classify_budget_usd`; с 31.08 (D39.182) план прохода режется ценой партии ДО первого вызова, а не обрывается посередине, и срез отчитывается полями `BatchesDropped`/`ClassifyBatchesDropped` (`backend/internal/pipeline/terminologist.go`, греп `probe+want > plan.budgetUSD`). Это УСЕЧЕНИЕ, а не отказ: проход идёт внутри банк-стопа, после оплаченной черновой волны. ⚠ Цифры этих суб-бюджетов в книжных конфигах калиброваны под ИЮЛЬСКИЕ цены (D39.179 п.1).» И внести `pipeline/terminologist.go` в карту §6. +**Разрушающее:** False + +--- + +## N041 · MAJOR · `docs/architecture/15-money-path.md:21` · D-противоречие-ратифицированному +**Срез:** Денежный путь — docs/architecture/15-money-path.md +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** **Остановка по объёму — ЗАВЕРШЕНИЕ (exit 0), не пауза:** словарь кодов выхода не расширялся, различение живёт в отчёте прогона и в логе; отчёт разводит ДОСТАВКУ и ПЕРЕ-ДЕЛКУ. +**Почему протухло:** «Различение живёт в отчёте прогона и в логе» перестало быть верным 31.08. D39.181 п.2 ратифицировал закон раскрытия: «признак остановки по объёму обязан ехать ЧИСЛОМ» в кадре `finished` шва, и обоснование ноты ровно в том, что прозаическая строка отчёта до потребителя потока не доезжала («said it in ONE PROSE LINE that no consumer of this stream reads»). Поле построено И проведено на обоих исходах. Док остался в редакции ДО закона и посылает читателя-платформу в тот самый канал, который закон объявил недостаточным. +**Улика — команда:** `sed -n '200,206p' backend/internal/runevents/runevents.go; grep -n 'Volume: volumeLedger(res)' backend/internal/pipeline/events.go` +**Улика — вывод:** // run: the line would say `volume` while OutcomeOf(0) said `clean`. … Numbers have no vocabulary to + // contradict, so the fact travels as numbers, and its PRESENCE is the boolean … + // + // Ratified with the disclosure law, D39.181 п.2. + Volume *VolumeLedger `json:"volume,omitempty"` +} + +backend/internal/pipeline/events.go:440: e.emit(runevents.TypeFinished, runevents.Finished{Outcome: runevents.OutcomeFlagged, Volume: volumeLedger(res)}) +backend/internal/pipeline/events.go:442: e.emit(runevents.TypeFinished, runevents.Finished{Outcome: runevents.OutcomeClean, Volume: volumeLedger(res)}) +**Предложенное действие:** Заменить хвост предложения на: «…словарь кодов выхода не расширялся и нового значения `Finished.Outcome` тоже нет — признак едет ЧИСЛАМИ в кадре `finished` шва: `Finished.Volume` — леджер доставки (max_units/delivered/reworked/flagged/free/left_fresh/left_rework), и его ПРИСУТСТВИЕ и есть булев признак остановки по объёму (ратифицировано D39.181 п.2; `backend/internal/runevents/runevents.go`, греп `Volume *VolumeLedger`). Отчёт прогона и лог — вторая копия того же факта, разводящая ДОСТАВКУ и ПЕРЕ-ДЕЛКУ.» +**Разрушающее:** False + +--- + +## N042 · MAJOR · `docs/architecture/16-events-emitter.md:11` · B-мёртвый-указатель +**Срез:** Эмиттер · закон шва · онтология банка — docs/architecture/16-events-emitter.md · 17-seam-inbound-law.md · 18-bank-ontology.md +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** **Что.** Строка 103 бэклога (`docs/PROGRESS.md:149`), дословно ядро ячейки: +**Почему протухло:** Якорь ведёт не туда И цель больше не существует: `docs/PROGRESS.md:149` — это строка бэклога 42, а строк 103 и 165 в таблице нет вовсе (закрыты D39.131 «строки 103+135+165+102 закрыты»). Тот же дубль в §6 строка 123 (`docs/PROGRESS.md:149`, `:113`) — там `:113` показывает строку 160. +**Улика — команда:** `sed -n '149p;113p' docs/PROGRESS.md | cut -c1-70; for n in 100 103 160 165; do printf "row %s -> %s\n" $n "$(grep -c "^| $n |" docs/PROGRESS.md)"; done` +**Улика — вывод:** | 42 | `prompt_override` — легальный обход гарантии слоя-2, сформул +| 160 | **Структура глав, Этап 0** ($0, wire-нейтрально): `titleRaw` +row 100 -> 0 +row 103 -> 0 +row 160 -> 1 +row 165 -> 0 +**Положительный контроль:** Тот же счётчик по соседним номерам находит живые строки: `for n in $(seq 204 216); do printf "%s:%s " $n $(grep -c "^| $n |" docs/PROGRESS.md); done` → `204:1 205:1 206:0 207:1 208:0 209:1 210:1 211:0 212:0 213:1 214:1 215:0 216:1` — инструмент в этой таблице находит строки, значит 103/165 отсутствуют по факту, а не из-за формы грепа. +**Предложенное действие:** Заменить оба якоря на адресацию БЕЗ номера строки и с признанием закрытия: «Строка 103 бэклога (ЗАКРЫТА D39.131; тело — `docs/archive/PROGRESS-*.md` либо цитата ниже)». Номер строки в живом PROGRESS.md для закрытой ячейки не восстановим — её там больше нет. +**Разрушающее:** False + +--- + +## N043 · MAJOR · `docs/architecture/16-events-emitter.md:17` · B-мёртвый-указатель +**Срез:** Эмиттер · закон шва · онтология банка — docs/architecture/16-events-emitter.md · 17-seam-inbound-law.md · 18-bank-ontology.md +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** Trace id чеканится на каждый вызов в `cmd/tmctl/main.go:67-70` — это сегодняшний кандидат в `engine_run_id` (так и записано платформой, `platform/internal/ingest/events.go:75`). +**Почему протухло:** Три якоря §1 («что уже есть со стороны движка», раздел БЕЗ ⚠-баннера) уехали: `cmd/tmctl/main.go:67-70` — это константы полосы отказов (`refusalLast = 19`, `exitSourceUnreadable = 11`), а `traceID()` живёт на `:149`/`:162`; `platform/internal/ingest/events.go:75` — поле `Seq`, а не `engine_run_id` (он в `Hello` около `:83`); процитированные строки денег стоят не на `stagerun.go:489-513`, а на `:517`, `:527`, `:529`. +**Улика — команда:** `sed -n '67p;70p;149p;162p' backend/cmd/tmctl/main.go | cut -c1-70; sed -n '75p' platform/internal/ingest/events.go; grep -n "USD ceiling reached\|denied estimate" backend/internal/pipeline/stagerun.go` +**Улика — вывод:** refusalLast = 19 + exitSourceUnreadable = 11 // the BOOK's source cannot be read +// traceID is the identity of this invocation: its log axis, its req +func traceID() string { + Seq int64 `json:"seq"` +stagerun.go:517: money = fmt.Sprintf(" (committed=$%.6f reserved=$%.6f, denied estimate=$%.6f, ch%d/chunk%d/%s)", +stagerun.go:527: return att, &CeilingHalt{... "pipeline: book USD ceiling reached ($%g)%s — %s or stop: %w" ...} +stagerun.go:529: return att, &CeilingHalt{... "pipeline: daily USD ceiling reached ($%g)%s: %w" ...} +**Предложенное действие:** Пере-нацелить: `cmd/tmctl/main.go:149-162`=`func traceID() string`, `platform/internal/ingest/events.go:83`=`StreamVersion string`/`Hello`, `backend/internal/pipeline/stagerun.go:517-529`=`USD ceiling reached`. Форма с токеном — чтобы гейт их судил. +**Разрушающее:** False + +--- + +## N044 · MAJOR · `docs/architecture/16-events-emitter.md:38` · A-дрейф +**Срез:** Эмиттер · закон шва · онтология банка — docs/architecture/16-events-emitter.md · 17-seam-inbound-law.md · 18-bank-ontology.md +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** «The vocabulary below is therefore the platform's PROPOSAL, written as code so the engine zone can answer it with a diff» (`events.go:5-6`). Состав (`events.go:37-147`): `StreamVersion = "1.0"` +**Почему протухло:** Дословная цитата, которую док обещает в шапке («дословные цитаты — в кавычках-ёлочках»), в дереве больше не существует: `events.go:5-6` теперь читается «The vocabulary below STARTED AS the platform's PROPOSAL … It DID (D39.131…): the emitter EXISTS». Вместе с ней протух и состав: `StreamVersion` в этом файле = `"1.1"` (не 1.0), `Ceiling` несёт ещё и `Scope`, а `Finished.outcome` — шесть значений (добавлены `ceiling` и `stopped`), не четыре. +**Улика — команда:** `grep -rn "is therefore the platform's PROPOSAL" platform/ backend/; echo '--- positive control ---'; sed -n '5,6p' platform/internal/ingest/events.go; grep -n 'const StreamVersion' platform/internal/ingest/events.go; sed -n '/^type Ceiling struct/,/^}/p' platform/internal/ingest/events.go; grep -n 'Outcome[A-Z][a-z]* *=' backend/internal/runevents/runevents.go` +**Улика — вывод:** (греп по цитате пуст) +--- positive control --- +// The vocabulary below started as the platform's PROPOSAL, written as code so the engine zone could +// answer it with a diff. It did (D39.131, engine commit 9cfe080): the emitter EXISTS, it writes +46:const StreamVersion = "1.1" +type Ceiling struct { + Halted bool `json:"halted"` + Scope string `json:"scope,omitempty"` +} +239: OutcomeClean="clean" 240:OutcomeFlagged 241:OutcomeBankStop 242:OutcomeCeiling 243:OutcomeStopped 244:OutcomeFailed +**Положительный контроль:** Греп по цитате целиком пуст, а по её выжившей половине — находит: `grep -rn "the platform's PROPOSAL" platform/internal/ingest/events.go` → `5:// The vocabulary below started as the platform's PROPOSAL, …`. Инструмент в файле работает; изменилась именно формулировка. +**Другие носители того же факта:** docs/PROGRESS.md:205 (строка бэклога 243, пункт arch-10: «`16-events-emitter.md`: словарь и `StreamVersion` разошлись с кодом обеих зон» — находка удержана аудитом 30–31.08 и НЕ применена) +**Предложенное действие:** Заменить цитату на действующую и пометить абзац ⚠-снимком: «состав §2 — снимок предложения платформы на 10.08; действующий словарь — `backend/internal/runevents/runevents.go` (StreamVersion 1.2, шесть outcome, `Ceiling.Scope`) и `platform/internal/ingest/events.go` (1.1)». Это ровно строка бэклога 243 / находка `arch-10`. +**Разрушающее:** False + +--- + +## N045 · MAJOR · `docs/architecture/16-events-emitter.md:66` · B-мёртвый-указатель +**Срез:** Эмиттер · закон шва · онтология банка — docs/architecture/16-events-emitter.md · 17-seam-inbound-law.md · 18-bank-ontology.md +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** **PD-113 (открытый major, закрывается этим паком)** — текущий текст `DEFECT_REGISTER.md:18`: +**Почему протухло:** ВСЕ двенадцать якорей в `platform/docs/DEFECT_REGISTER.md` мертвы — ни один не попадает в свою строку. Фактические номера: PD-60→343, PD-61→344, PD-79→233, PD-95→239, PD-99→251, PD-105→252, PD-107→97, PD-113→345. Хуже: док несёт ДВА взаимно противоречивых набора для одних и тех же PD (§4 против чек-листа §6): PD-79 `:90` против `:174`, PD-99 `:110` против `:92`, PD-107 `:118` против `:56`, PD-61 `:72` против `:46`. Оба набора неверны. +**Улика — команда:** `for p in PD-60 PD-61 PD-79 PD-95 PD-99 PD-105 PD-107 PD-113; do printf "%-7s -> " $p; grep -n "^| $p |" platform/docs/DEFECT_REGISTER.md | cut -d: -f1; done; for l in 18 24 46 56 72 90 92 110 118 174 180 192; do printf "%-4s| " $l; sed -n "${l}p" platform/docs/DEFECT_REGISTER.md | cut -c1-46; done` +**Улика — вывод:** PD-60 -> 343 / PD-61 -> 344 / PD-79 -> 233 / PD-95 -> 239 / PD-99 -> 251 / PD-105 -> 252 / PD-107 -> 97 / PD-113 -> 345 +18 | > объявленной нормой зоны (введены приёмкой P2 +24 | Несущий путь или контрактно видимое поведение +46 | | PD-102 | doc | minor | ... +56 | | PD-219 | bug | minor | ... +72 | | PD-381 | hardening | info | ... +90 | | PD-88 | bug | info | ... +92 | | PD-92 | bug | info | ... +110 | | PD-212 | bug | info | ... +118 | | PD-248 | bug | info | ... +174 | | PD-19 | bug | info | ... +180 | | PD-27 | bug | minor | ... +192 | | PD-39 | bug | info | ... +**Предложенное действие:** Пере-нацелить все двенадцать по СОДЕРЖИМОМУ (номера выше) и свести два набора в один. Лучше — перевести их в форму с токеном (`DEFECT_REGISTER.md:345`=`PD-113`), которую `docs/scripts/counts.py --lint` реально судит: сегодня гейт эти якоря пропускает как «без токена — несверяемы» (708 из 845). +**Разрушающее:** False + +--- + +## N046 · MAJOR · `docs/architecture/17-seam-inbound-law.md:56` · B-мёртвый-указатель +**Срез:** Эмиттер · закон шва · онтология банка — docs/architecture/16-events-emitter.md · 17-seam-inbound-law.md · 18-bank-ontology.md +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** *Почему жёстко:* движок пере-читает сид ПОСРЕДИ прогона (`mining.go:221`), запись в живой прогон +**Почему протухло:** Якорь родился верным и сгнил дрейфом — и уехал на строку, ВЫГЛЯДЯЩУЮ так же. На 23.08 (коммит `4f56160`, тот же, что написал якорь) `mining.go:221` был `if err := r.seedGlossary(ctx); err != nil {` — тот самый пере-сид. Сегодня 221 — это `if !stopping {` ВНУТРИ стоп-ветки (первое из двух одинаковых условий файла), а пере-сид уехал на `:243`. Читатель, проверяющий довод ратифицированного закона п.2, попадает не в тот механизм и не заметит подмены. +**Улика — команда:** `git show 4f56160:backend/internal/pipeline/mining.go | sed -n '221p'; sed -n '221p;243,244p' backend/internal/pipeline/mining.go; grep -n 'seedGlossary(ctx)' backend/internal/pipeline/mining.go` +**Улика — вывод:** if err := r.seedGlossary(ctx); err != nil { <- было на :221 в 4f56160 + if !stopping { <- стало на :221 сегодня + if err := r.seedGlossary(ctx); err != nil { + return false, fmt.Errorf("pipeline: re-seed the bank with the auto rows: %w", err) +243: if err := r.seedGlossary(ctx); err != nil { +**Предложенное действие:** Заменить на форму с токеном, которую судит гейт: `backend/internal/pipeline/mining.go:243`=`return false, fmt.Errorf("pipeline: re-seed the bank with the auto rows` (токен уникален и переживёт сдвиг лучше номера). +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Находка верна, предложенный ЯКОРЬ неверен и провалит гейт. counts.py:anchor_window даёт для `:243` окно ровно [243,243], а предложенный токен `return false, fmt.Errorf("pipeline: re-seed the bank with the auto rows` физически лежит на строке 244 — гейт напечатает «ожидаемого ... в цели НЕТ (цель уехала)». Верная форма: `backend/internal/pipeline/mining.go:243-244`=`re-seed the bank with the auto rows` (диапазон объединяется, токен попадает в окно). Полный путь от корня ОБЯЗАТЕЛЕН: голое `mining.go:221` гейт считает внешней ссылкой и не проверяет, поэтому починка без пути оставит якорь ровно так же беззащитным. Правка одного якоря в ратифицированном доке — обслуживание, не пере-ратификация. + +--- + +## N047 · MAJOR · `docs/architecture/17-seam-inbound-law.md:71` · C-исполненное-как-открытое +**Срез:** Эмиттер · закон шва · онтология банка — docs/architecture/16-events-emitter.md · 17-seam-inbound-law.md · 18-bank-ontology.md +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** загрузчике сида (строка **212**) новое поле теряется МОЛЧА. +**Почему протухло:** Загрузчик сида СТРОГИЙ с пака входной двери: `backend/internal/seed/decode.go` называет строку 212 своим именем и ставит `dec.KnownFields(true)` — неизвестный ключ теперь громкая ошибка с номером строки, а не тихая потеря. Строка 212 закрыта и снята с бэклога. Довод п.5 («иначе новое поле теряется МОЛЧА») сегодня опирается на несуществующий дефект. +**Улика — команда:** `grep -n 'backlog row 212' backend/internal/seed/decode.go; grep -n 'KnownFields(true)' backend/internal/seed/decode.go; for n in $(seq 209 216); do printf "%s:%s " $n $(grep -c "^| $n |" docs/PROGRESS.md); done` +**Улика — вывод:** 16:// struct does not have is a typo, and yaml.v3 DROPS one silently, so `gendr:` for `gender:` reads as +17:// "not set" and the run looks normal (backlog row 212). The same class the strict `book.yaml` decoder +61: dec.KnownFields(true) +209:1 210:1 211:0 212:0 213:1 214:1 215:0 216:1 +**Положительный контроль:** Тот же счётчик по соседям возвращает единицы (209, 210, 213, 214, 216) — таблица читается, и ноль у 212 означает отсутствие строки, а не промах грепа. +**Предложенное действие:** Заменить довод: «…а строгий с D39.158 загрузчик сида (`backend/internal/seed/decode.go:61`=`dec.KnownFields(true)`, строка 212 ЗАКРЫТА) роняет неизвестный ключ ГРОМКО — именно поэтому пиннинг бинаря остаётся единственной защитой от чтения нового файла старым кодом». Правка ратифицированного носителя. +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Находка верна, атрибуция в предложенной формулировке неточна. Строгость КОДА приехала лендингом d1eb8a9 (пак D39.158), но строку 212 закрыла и сняла с таблицы ЭРРАТА 27.08-д = D39.159 п.7 — на неё и надо ссылаться, иначе следующий читатель пойдёт грепать 212 в теле D39.158 и не найдёт (я проверил: в теле D39.158 номера 212 нет вовсе). Форма якоря: `backend/internal/seed/decode.go:61`=`dec.KnownFields(true)` — корректна и гейт её судит. ⚠ Заодно, при той же правке абзаца: п.5 ссылается ещё и на «строку бэклога 139», а `grep -c '^| 139 |' docs/PROGRESS.md` = 0 при живых соседях — этот номер надо пере-судить тем же заходом, иначе абзац починят наполовину. + +--- + +## N048 · MAJOR · `docs/architecture/17-seam-inbound-law.md:97` · C-исполненное-как-открытое +**Срез:** Эмиттер · закон шва · онтология банка — docs/architecture/16-events-emitter.md · 17-seam-inbound-law.md · 18-bank-ontology.md +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** | приём правок банка движковым глаголом | строка **199(а)** — развилка владения `book.yaml` растворяется этим | +**Почему протухло:** Первая строка таблицы «Двери, УЖЕ подразумеваемые живыми строками» описывает как ПРЕДСТОЯЩУЮ ту самую дверь, которую собственная шапка дока объявляет построенной («⚠ ИСПОЛНЕН 27.08: первая дверь по этому закону ПОСТРОЕНА и заленджена — `tmctl bank-apply`»). Три соседние строки таблицы получили зачёркивание и пометку **ПОСТРОЕН**, эта — нет. Строка 199 закрыта (D39.156 п.3 «растворением», D39.158 п.5) и снята с бэклога; D39.166 отдельно фиксирует «Бэклог держал ДВЕ ЗАКРЫТЫЕ строки (199, 215)». +**Улика — команда:** `grep -n 'bank-apply' backend/cmd/tmctl/main.go | head -3; for n in $(seq 195 203); do printf "%s:%s " $n $(grep -c "^| $n |" docs/PROGRESS.md); done; grep -n 'Бэклог держал ДВЕ ЗАКРЫТЫЕ строки' docs/architecture/05-decisions-log.md | cut -c1-90` +**Улика — вывод:** backend/cmd/tmctl/main.go:2:// backup / migrate / seed-lint / bank-apply (dispatchCommands). +backend/cmd/tmctl/main.go:185: "bank-apply", +backend/cmd/tmctl/main.go:249: case "bank-apply": +195:1 196:1 197:1 198:1 199:0 200:0 201:1 202:1 203:1 +1121:**3. Бэклог держал ДВЕ ЗАКРЫТЫЕ строки** (199, 215) вопреки собственному правилу +**Положительный контроль:** Соседи 195–198 и 201–203 возвращают по единице — таблица бэклога читается грепом, и ноль у 199 не артефакт формы. +**Другие носители того же факта:** platform/docs/platform-PROGRESS.md:2750 (исторический фикс-лист приёмки: «Труба доставки решений банка в движок — единый бэклог, строка **199(а)**» — тоже описывает дверь как предстоящую) +**Предложенное действие:** Привести строку к форме соседей: «~~приём правок банка движковым глаголом~~ **ПОСТРОЕН** 27.08 (`tmctl bank-apply`, D39.158); строка **199(а)** ЗАКРЫТА растворением (D39.156 п.3) и снята с бэклога». Заодно перечитать строку 103 таблицы («приоритет ПАДАЕТ после двери 199(а)») — падение уже произошло. +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие верное, но чинить надо ОБЕ строки одним заходом, а не одну: строка 103 той же таблицы («`tmctl init` (форма В) … приоритет ПАДАЕТ после двери 199(а) — та забирает её главный довод») стоит в будущем времени о двери, которая построена, — падение уже произошло, и её надо переписать в прошедшее. Атрибуцию брать точную: дверь — D39.158 (лендинг d1eb8a9), развилка 199(а) растворена D39.156 п.3, сама строка снята с бэклога сверткой D39.166 п.3. ⚠ ВТОРОЙ НОСИТЕЛЬ из находки трогать НЕ НАДО: platform/docs/platform-PROGRESS.md:2750 сидит внутри раздела «### Фикс-лист приёмки — завести строками СВОЕГО бэклога» (:2738) — это ХРОНИКА чужой зоны, запись о том, что было верно на своей дате, а не живой носитель факта; правка чужого журнала задним числом стирает след, а не чинит его. + +--- + +## N049 · MAJOR · `docs/architecture/17-seam-inbound-law.md:100` · C-исполненное-как-открытое +**Срез:** Эмиттер · закон шва · онтология банка — docs/architecture/16-events-emitter.md · 17-seam-inbound-law.md · 18-bank-ontology.md +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** ⚠ Родня строки **231**: та просит ту же способность («оценить, ничего не покупая») со стороны ШВА, и решаться они, скорее всего, одним глаголом +**Почему протухло:** Строка 231 закрыта паком «деньги» (D39.170, 29.08) и снята с бэклога — сам PROGRESS.md говорит это в лоб. Док цитирует её ДВАЖДЫ как живую: на строке 100 («родня», решится одним глаголом) и на строке 101 («Родня — строка 231 … смету пере-прохода до покупки показать нечем, и это названная цена минора 0.7.0»). Второе утверждение прямо опровергнуто телом D39.170: слепое окно закрыто, `foldMemoryForRead` стал первым ответом читающего пути. +**Улика — команда:** `for n in $(seq 228 234); do printf "%s:%s " $n $(grep -c "^| $n |" docs/PROGRESS.md); done; grep -n 'строк 231 и 236 в таблице БОЛЬШЕ НЕТ' docs/PROGRESS.md; grep -n 'строка бэклога$' -A1 docs/architecture/05-decisions-log.md | sed -n '1,2p'; sed -n '1271,1273p' docs/architecture/05-decisions-log.md | cut -c1-160` +**Улика — вывод:** 228:1 229:1 230:1 231:0 232:1 233:1 234:1 +docs/PROGRESS.md:285:> (⚠ строк 231 и 236 в таблице БОЛЬШЕ НЕТ — 231 съел пак «деньги», 236 сужена D39.175; прежняя редакция +(D39.170) «Слепое окно закрыто. Следствия проведены этим же лендингом: строка бэклога +**231** закрывается, комментарий платформы получает строку в её регистр, а проводка полей через шов +становится ВОЗМОЖНОЙ» +**Положительный контроль:** Соседние номера 228–230 и 232–234 дают по единице — счётчик строк работает; ноль у 231 совпадает с явной запиской PROGRESS.md:285 и с телом D39.170. +**Другие носители того же факта:** docs/PROGRESS.md:285 (несёт ВЕРНУЮ версию факта — «строк 231 и 236 в таблице БОЛЬШЕ НЕТ»; расхождение именно между ним и доком 17) +**Предложенное действие:** Снять обе ссылки на 231: в строке 100 оставить только 124(в); в строке 101 заменить хвост на «⚠ испр.: строка 231 ЗАКРЫТА D39.170 (29.08) — смета пере-прохода доезжает до покупателя; движковая оценка $/глава (строка 166) остаётся открытой САМА ПО СЕБЕ, без родни». Правка ратифицированного носителя. +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие в целом верное, но формулировку для строки 101 надо взять точнее, иначе док качнётся в противоположную ложь. «Смету показать нечем» неверно, однако смета до сих пор НЕ доезжает до покупателя через шов: платформа сознательно НЕ берёт `rebill_units`/`rebill_usd`, и это записано её же кодом (`platform/internal/ingest/resync.go:37-39`=`The engine's re-bill projection (rebill_units/rebill_usd) is deliberately NOT taken`, регистровая строка PD-427), а D39.170 отдельно оговаривает, что проводка полей «становится ВОЗМОЖНОЙ — но не выполняется». Верная замена хвоста: «⚠ испр.: строка 231 ЗАКРЫТА D39.170 (29.08) — движок оценивает пере-проход $0-глаголом `status`, свернув банк; открытым остаётся не ГЛАГОЛ, а проводка полей через шов (PD-427 платформы). Движковая оценка $/глава (строка 166) остаётся открытой сама по себе, без родни». Правка ратифицированного носителя ⇒ нотой оркестратора. + +--- + +## N050 · MAJOR · `docs/glossary.md:11` · C-исполненное-как-открытое +**Срез:** Глоссарий и продуктовые требования — docs/glossary.md · docs/product-requirements.md +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** **P12** «долги под ногами» — семь открытых major + снятие обхода `--verify-bank`, ВЫДАН 30.08, на приёмке. +**Почему протухло:** P12 принят и залёнджен 31.08 нотой D39.180 (коммит 0a680a3), обязательство D39.158 закрыто целиком, регистр: major 2 против 7. Глоссарий лежит в онбординг-пути и сообщает свежей сессии, что пак ещё на приёмке. +**Улика — команда:** `git log --oneline -1 0a680a3; git log -1 --format='%h %ad' --date=short -- docs/glossary.md; grep -m1 '| D39.180 |' docs/architecture/05-decisions-index.md | cut -c1-200` +**Улика — вывод:** 0a680a3 Land platform pack P12 with contract minor 0.9.0: the bank-stop bypass is gone, a chapter counts by the pass current for it, and the boundary now judges prose too +df4b064 2026-08-30 +| D39.180 | 31.08 | **Пак платформы P12 «долги под ногами» ПРИНЯТ И ЗАЛЕНДЖЕН** вместе с контрактным минором **0.9.0**. Приёмка — 5 линз, 6 major / 16 minor / 0 блокеров, 8 дофиксов исполнены. Обязательство D39.158 ... закрыто целиком +**Другие носители того же факта:** docs/PROGRESS.md:37 — «1. Приёмка пака P12 — платформа, сессия textmachine-main-b5, работает» в блоке «ОТКРЫТО НА ОРКЕСТРАТОРЕ №21 (снимок 31.08)»; блок помечен как снимок, но пункты 1–3 уже исполнены +**Предложенное действие:** Заменить хвост строки на «**P12** «долги под ногами» — семь открытых major + снятие обхода `--verify-bank`, ПРИНЯТ и ЗАЛЕНДЖЕН 31.08, D39.180, вместе с минором 0.9.0». +**Разрушающее:** True + +--- + +## N051 · MAJOR · `docs/glossary.md:22` · E-второй-носитель +**Срез:** Глоссарий и продуктовые требования — docs/glossary.md · docs/product-requirements.md +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** - **V0–V5** — секции идей владельца в `START_PROMT.MD` (живой бриф; разобраны в `product-requirements.md`). +**Почему протухло:** В START_PROMT.MD есть секция V6 от 18.08.2026 (идея переделки архитектуры пайплайна), разобранная в реестр 23.08 строками ПТ-37..ПТ-43. Парный носитель — шапка product-requirements.md — уже говорит «идеи V0–**V6**». Диапазон в глоссарии не обновили, и он занижает список секций владельца ровно на ту, что предлагает переделку пайплайна. +**Улика — команда:** `grep -nE 'Иде[ия].*V[0-9]' START_PROMT.MD; grep -o 'идеи V0–\*\*V6\*\*' docs/product-requirements.md` +**Улика — вывод:** 52:Идеи которые возникли постфактум V1, 06.07.2025 +66:Идеи V2 не касающиеся самого перевода +72:Идеи V3 пайплайна перевода +84:Идеи проекта V4, 12.07.2026 +92:Идея проекта V5, 26.07.2026 +97:Идеи проекта V6, 18.08.2026 +идеи V0–**V6** +**Другие носители того же факта:** docs/product-requirements.md:3 — «Источники: `../START_PROMT.MD` (ЖИВОЙ бриф, идеи V0–**V6**...)» — ВЕРНЫЙ носитель +**Предложенное действие:** Заменить на «**V0–V6** — секции идей владельца в `START_PROMT.MD` (живой бриф; разобраны в `product-requirements.md`; V6 от 18.08 — ПТ-37..ПТ-43)». Впредь диапазон в глоссарии двигать тем же коммитом, что разбирает новую V-секцию. +**Разрушающее:** True + +--- + +## N052 · MAJOR · `docs/product-requirements.md:32` · E-второй-носитель +**Срез:** Глоссарий и продуктовые требования — docs/glossary.md · docs/product-requirements.md +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** $0.85/ранобэ (D30.4); ре-проба исполнена и строка 74 закрыта (замер D39.86/91, закрытие D39.95); пере-калибровка цены — при следующем платном прогоне +**Почему протухло:** Число — июльские деньги ДО пере-пина цен DeepSeek 13–15.08. Два других живых носителя того же числа несут ⚠-оговорку про множитель ×4.2–4.4 (STACK.md) и ратифицированный ×4.47 (D39.179 п.1); ПТ-12 её НЕ несёт и стоит со статусом ✅. Плюс триггер сработал: платный прогон 31.08 БЫЛ, и он замерил ≈$1.14 за ДЕСЯТЬ глав — то есть десять глав сегодня дороже, чем весь «ранобэ» по этой ячейке. +**Улика — команда:** `grep -n '0\.85/ранобэ' docs/STACK.md docs/PROGRESS.md docs/product-requirements.md | cut -c1-120; sed -n '69p' docs/archive/reports/COLDRUN_V16_REPORT_2026-08-31.md` +**Улика — вывод:** docs/STACK.md:21:- **Цена:** ~$0.85/ранобэ (⚠ число ДО пере-пина цен 13–15.08: таблица запинена ПИКОМ, счёт шип... +docs/product-requirements.md:32:| ПТ-12 | Низкий COGS ... | ✅ | ~$0.85/ранобэ (D30.4)... +docs/PROGRESS.md:15:> - **Стек ...** ~$0.85/ранобэ (D30.4, пере-калибровка при следующем платном прогоне) +Полная цепь на десяти главах в СЕГОДНЯШНИХ ценах стоит **≈$1.14 по собственной оценке движка** +**Другие носители того же факта:** docs/STACK.md:21 — то же число С ⚠-оговоркой про пере-пин, docs/PROGRESS.md:15 — то же число, оговорка про пере-пин стоит рядом отдельным блоком, docs/architecture/02-mvp-plan.md:40 — «~$0.7/ранобэ ... после D30.1 ~$0.85 — D30.4», без оговорки, docs/architecture/01-decisions.md:5 — «COGS ~$0.85/ранобэ после флипа D1 (D30.4)», но док под ⚠-баннером «проигрывает D-логу» — легитимно, docs/architecture/05-decisions-index.md:46 — строка реестра D30 +**Предложенное действие:** Дописать в носитель ПТ-12 ту же ⚠-оговорку, что несут STACK.md и PROGRESS: «⚠ число ДО пере-пина цен 13–15.08 (×4.47 на том же заходе — D39.179 п.1); платный прогон 31.08 состоялся и дал ≈$1.14 за 10 глав (COLDRUN_V16_REPORT), пере-калибровка НЕ исполнена». Статус ✅ вынести оркестратору отдельным вопросом — понижать его я права не имею. +**Разрушающее:** False + +--- + +## N053 · MAJOR · `docs/product-requirements.md:56` · A-дрейф +**Срез:** Глоссарий и продуктовые требования — docs/glossary.md · docs/product-requirements.md +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** ⚠ 30.08 РАТИФИЦИРОВАН минор **0.9.0** ... — он придёт с лендингом пака P12, до него канон 0.8.0 +**Почему протухло:** P12 залёнджен 31.08 (0a680a3) вместе с минором, канон в дереве стоит на 0.9.0. Верно: канон 0.9.0 с 31.08 (D39.180), зеркало фронта отстаёт уже на СЕМЬ миноров, а не шесть. Ячейка сама предупреждает «версия здесь живёт хуже всего — правится ПЕРВОЙ»; минор прошёл, ячейка нет. +**Улика — команда:** `grep -n '^ version:' docs/architecture/14-api-contract/openapi.yaml; git log -1 --format='%h %ad' --date=short -- docs/architecture/14-api-contract/openapi.yaml; grep -m1 '| D39.180 |' docs/architecture/05-decisions-index.md | cut -c1-120` +**Улика — вывод:** 5: version: 0.9.0 +0a680a3 2026-08-31 +| D39.180 | 31.08 | **Пак платформы P12 «долги под ногами» ПРИНЯТ И ЗАЛЕНДЖЕН** вместе с контрактным минором **0.9.0** +**Другие носители того же факта:** docs/architecture/09-target-architecture.md:12 — «⚠ Канон контракта — 0.8.0, не 0.4.0 и не 0.7.0» (протухло так же), docs/architecture/14-api-contract/README.md:8-9 — «0.2.3 при каноне 0.8.0» (протухло; зона уже запинговала — platform/docs/platform-PROGRESS.md:346), docs/PROGRESS.md:3 — «зеркало отстало на ШЕСТЬ миноров (0.2.3 против канона 0.8.0)» (протухло: миноров семь), docs/README.md:29 — «минор 0.8.0 ПРИНЯТ и заленджен 29.08 (D39.169)» (фраза верна, но читается как последний минор) +**Предложенное действие:** В ПТ-20 заменить хвост на: «⚠ 30.08 ратифицирован и 31.08 ЗАЛЕНДЖЕН вместе с паком P12 минор **0.9.0** (D39.180, `0a680a3`) — канон стоит на 0.9.0; зеркало фронта отстаёт на СЕМЬ миноров». Тем же проходом поправить четыре носителя из other_carriers (они не в моём срезе). +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие в целом верное, но одну его половину надо адресовать точнее: фразы «отстаёт на СЕМЬ миноров» в самой ПТ-20 НЕТ — ячейка говорит только «зеркало ФРОНТА ратифицированно отстаёт на 0.2.3», и это утверждение ОСТАЁТСЯ ВЕРНЫМ (frontend/docs/api-contract/openapi.yaml = 0.2.3). Счёт «шесть миноров» живёт в docs/PROGRESS.md:3 — правку числа шесть→семь вносить ТУДА, а в ПТ-20 менять только хвост про 0.9.0/P12. Ячейку 0.9.0 при этом писать без нового номера строки openapi.yaml (сама ячейка предупреждает, что номер двигается). + +--- + +## N054 · MAJOR · `docs/product-requirements.md:63` · D-противоречие-ратифицированному +**Срез:** Глоссарий и продуктовые требования — docs/glossary.md · docs/product-requirements.md +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** остаток: страница лимитов и оповещение на фронте (после P7) +**Почему протухло:** D39.147 (слово владельца 17.08) АМЕНДИРОВАЛ условие разморозки: «лендинг P7 зону БОЛЬШЕ НЕ размораживает», S5-промт не выдаётся, разморозка — отдельным словом владельца. P7 залёнджен 20.08 (D39.153), а остаток ПТ-35 по-прежнему адресован «после P7» — гейт, который прошёл, ничего не открыв. Тот же коммит D39.147 п.3 синхронизировал ПТ-20 и ПТ-35 не тронул. +**Улика — команда:** `sed -n '500p' docs/architecture/05-decisions-log.md | grep -o 'лендинг P7 зону БОЛЬШЕ НЕ размораживает.\{0,90\}'; grep -m1 '| D39.153 |' docs/architecture/05-decisions-index.md | cut -c1-90` +**Улика — вывод:** лендинг P7 зону БОЛЬШЕ НЕ размораживает**, S5-промт не выдаётся; разморозка — отдельным словом владельца по достижении сквозного пути +| D39.153 | 20.08 | Приёмка и лендинг P7 платформы (пять актов, читающая поверхность): **ПРИН +**Другие носители того же факта:** docs/glossary.md:11 — верный носитель: «зона ЗАМОРОЖЕНА, и лендинг P7 её НЕ разморозил — D39.147», docs/product-requirements.md:56 (ПТ-20) — верный носитель того же факта в том же доке +**Предложенное действие:** Заменить «(после P7)» на «(после РАЗМОРОЗКИ фронта — D39.147: лендинг P7 её не размораживает, разморозка отдельным словом владельца)». +**Разрушающее:** True + +--- + +## N055 · MAJOR · `frontend/README.md:7` · A-дрейф +**Срез:** Доки замороженной зоны фронта — frontend/**/*.md (10 файлов, 5167 строк) +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** Продуктовых экранов +ещё нет — они на S4–S7 (`docs/BACKLOG.md`, Ф-1). +**Почему протухло:** S4 исполнен 14.08 и ПРИНЯТ 15.08 (D39.135, `267aa35`): библиотека, интейк, форма запуска прогона, настройки, читалка — построенные экраны. Перечисление пройденных этапов в шапке обрывается на S3.7. +**Улика — команда:** `ls frontend/src/showcase/ | grep -E 'Library|AddBook|RunStart|Settings|Reader'; grep -n '15.08 — приёмка S4' frontend/docs/frontend-PROGRESS.md` +**Улика — вывод:** AddBook.module.css / AddBook.tsx / Library.module.css / Library.tsx / Reader.module.css / Reader.tsx / RunStart.module.css / RunStart.tsx / Settings.module.css / Settings.tsx +465:### 15.08 — приёмка S4 оркестратором №17: ПРИНЯТО и залендено (`267aa35`); контракт 0.2.3 в каноне (D39.135) +**Другие носители того же факта:** frontend/docs/BACKLOG.md:7 (Ф-1, столбец «Вес»: «по очереди сессий (S0/S1 — текущая)») +**Предложенное действие:** В шапке дописать «…и S4 (библиотека, загрузка книги, разбор, запуск прогона; принят D39.135 15.08)»; фразу «Продуктовых экранов ещё нет» заменить на «Построены экраны S4; S5–S7 — впереди, зона ЗАМОРОЖЕНА (D39.136 п.2 + D39.147)». +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Формулировка предложенной замены верна, но названный «второй носитель» — ОШИБКА: строка Ф-1 стоит на `frontend/docs/BACKLOG.md:7`, а не на 7 в смысле находки… проверено: Ф-1 действительно на строке 7, и её столбец «Вес» гласит «по очереди сессий (S0/S1 — текущая)» — это тоже протухло (текущая не S0/S1), так что носитель назван верно, но его правка — отдельный пункт, а не «тот же факт». ⚠ И тут, и в Ф-1 исполнитель — фронт-сессия: `frontend/README.md` в зоне фронта, зона заморожена (D39.147). Оркестратору доступна только запись в `frontend/docs/frontend-PROGRESS.md` («шапка README зоны отстала на S4 — поправить первым касанием»). + +--- + +## N056 · MAJOR · `frontend/README.md:66` · D-противоречие-ратифицированному +**Срез:** Доки замороженной зоны фронта — frontend/**/*.md (10 файлов, 5167 строк) +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** эта копия — байт-зеркало канона `docs/architecture/14-api-contract/`; правит её + фронт-сессия, переносит в канон диффом оркестратор при лендинге (прецедент Ф-47/0.2.2). +**Почему протухло:** Зеркало НЕ байт-равно: 0.2.3/16 операций против канона 0.9.0/20 операций, разные md5. И правило «зеркало = байт-копия» ратифицированно ПРИОСТАНОВЛЕНО на время фриза (D39.142 п.5) — нормативен только канон. README — точка входа зоны и учит обратному. +**Улика — команда:** `md5sum frontend/docs/api-contract/openapi.yaml docs/architecture/14-api-contract/openapi.yaml; grep -m1 'version:' в обоих; awk-счёт операций` +**Улика — вывод:** 4e4ab5e6d313bd56d395032a718ab18a frontend/docs/api-contract/openapi.yaml +34bb4b6d0496f4377633597cd12bc73a docs/architecture/14-api-contract/openapi.yaml +зеркало: version: 0.2.3, 15 путей, 16 операций +канон: version: 0.9.0, 17 путей, 20 операций (добавлены /capabilities, /books/{bookId}/events, /runs/{runId}; bank/decisions → bank/corrections; снят /runs/{runId}/events) +**Другие носители того же факта:** frontend/docs/api-contract/openapi.yaml (info.description: «byte-mirror of it, and a divergence is a defect»), frontend/docs/frontend-PROGRESS.md:47-48, frontend/docs/frontend-PROGRESS.md:9 (пинг №20 п.4 — называет 0.8.0, канон уже 0.9.0) +**Предложенное действие:** Дописать в этот буллет: «⚠ С фриза зеркало отстаёт РАТИФИЦИРОВАННО (D39.142 п.5): нормативен только канон, зеркало на 0.2.3. Синк байт-в-байт (`cmp`) + перегенерация типов — первое касание зоны при разморозке». +**Разрушающее:** False + +--- + +## N057 · MAJOR · `frontend/docs/BACKLOG.md:50` · D-противоречие-ратифицированному +**Срез:** Доки замороженной зоны фронта — frontend/**/*.md (10 файлов, 5167 строк) +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** Модель владельца и движка совпадают: подписывается банк ЦЕЛИКОМ, стоп снимается только когда решён каждый термин (`pipeline/mining.go:201`), в контракте это `POST /runs/{id}/resume` с `409` при неполном наборе. +**Почему протухло:** D39.144 (16.08) снял гейт полноты. Канон: `awaiting_bank` с ЛЮБЫМ состоянием решений → 202. Якорь `mining.go:201` тоже уехал: там сегодня стоит прямая ссылка на D39.144 и слова владельца, что марш по пер-термным решениям — НЕ продукт. +**Улика — команда:** `sed -n "$(grep -n '/runs/{runId}/resume:' docs/architecture/14-api-contract/openapi.yaml | cut -d: -f1),+20p" docs/architecture/14-api-contract/openapi.yaml; sed -n '202,206p' backend/internal/pipeline/mining.go` +**Улика — вывод:** | the run stands in | this call answers | +| `awaiting_bank` — with ANY state of decisions | continues it — `202` | +| `paused` — ANY reason, `null` included | `409` `run_not_resumable`, `cause.code: ceiling_reached` | +--- +mining.go:202: // THE FLAG (D39.42 п.5 · D39.144, owner's words 26.08: «стоп на банке памяти — это просто флажок»). +mining.go:205-207: «only then: a flag that re-stopped on everything still undecided would turn «остановиться один раз» into a march over per-term decisions the product does not have» +**Другие носители того же факта:** frontend/docs/BACKLOG.md:16 (Ф-14: «стоп снимается ТОЛЬКО полным набором (mining.go:200-202)»), frontend/docs/frontend-PROGRESS.md:508 (пинг №17 — модель названа, строки не поправлены) +**Предложенное действие:** Переписать хвост Ф-44 по D39.144: «подписывается ВЕСЬ банк одним ОК (= `resume`); гейт полноты снят, `pending_decisions`/`complete` информационные; пер-термная масс-подпись НЕ строится». Якорь `mining.go:201` заменить на `mining.go:202-207`. +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Содержание правки верно; уточнить якорь: тело флага занимает `mining.go:202-207` (строка 202 — заголовок коммента с ссылкой на D39.144), а не «201». ⚠ Не переписывать Ф-44 в «строка закрыта»: пинг №17 (`frontend-PROGRESS.md:508`) ратифицирует не закрытие, а ПЕРЕЧТЕНИЕ — «строки Ф-44/Ф-45/Ф-46 при разморозке читать через эту модель: действие подписи = один ОК + опциональные точечные правки». Хвост «экран действия строить при разморозке» остаётся живым. Исполнитель — фронт-сессия (зона заморожена). + +--- + +## N058 · MAJOR · `frontend/docs/BACKLOG.md:51` · B-мёртвый-указатель +**Срез:** Доки замороженной зоны фронта — frontend/**/*.md (10 файлов, 5167 строк) +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** Контракт это выражает (`BankDecision`: `promote` с непустым `dst` либо `decline`, частичное накопление на сервере), то есть редактирование строки — это отправка решения, а не PATCH. +**Почему протухло:** В каноне 0.9.0 нет ни схемы `BankDecision`, ни значения `promote`, ни пути `POST …/bank/decisions`, ни «частичного накопления»: канон 0.5.0 снёс их и завёл `POST /books/{bookId}/bank/corrections` со словарём `approve`/`decline`, применяемый ВСЁ-ИЛИ-НИЧЕГО. +**Улика — команда:** `grep -c 'promote\|BankDecision' docs/architecture/14-api-contract/openapi.yaml; grep -n 'bank/corrections' -A 12 docs/architecture/14-api-contract/openapi.yaml | head -14; grep -n 'снесены путь' docs/architecture/14-api-contract/README.md` +**Улика — вывод:** 0 (промоут/BankDecision в каноне отсутствуют) +/books/{bookId}/bank/corrections: post: operationId: applyBankCorrections + «The per-term half of the bank model: signing is ONE act over the whole bank (`resumeRun`), and what exists per term is a CORRECTION — `approve` a rendering, or `decline` a surface … The whole document is applied as one act, **all or nothing**» +README.md:75: ⚠ 0.5.0 ломающий по построению: снесены путь `POST …/bank/decisions` и схема `BankDecision` +**Положительный контроль:** grep -c 'decline' docs/architecture/14-api-contract/openapi.yaml → 17 (инструмент в этой области находит словарь решений; ноль по `promote`/`BankDecision` — не опечатка грепа) +**Другие носители того же факта:** frontend/docs/api-contract/openapi.yaml:1149-1169 (зеркало 0.2.3, отстаёт ратифицированно), frontend/src/api/schema.ts:853-864, frontend/src/mock/handlers.ts:195 +**Предложенное действие:** Заменить в Ф-45 «`BankDecision`: `promote`/`decline`, частичное накопление» на «`POST /books/{bookId}/bank/corrections`, `BankCorrection`: `approve`/`decline`, всё-или-ничего, превью перед сохранением, эффект — на СЛЕДУЮЩЕМ прогоне». +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Текст замены верен, но ОСТАЛЬНЫЕ НОСИТЕЛИ ПРАВИТЬ НЕЛЬЗЯ, и это важнее самой правки: (1) `frontend/docs/api-contract/openapi.yaml` — не «отстающий док», а РАТИФИЦИРОВАННО замороженное зеркало (D39.142 п.5; `14-api-contract/README.md:102`), синк 0.2.3→0.9.0 — первое касание зоны при разморозке, одним актом с перегенерацией типов; (2) `frontend/src/api/schema.ts` — ГЕНЕРИРУЕМЫЙ файл (`openapi-typescript` из зеркала, `FRONTEND_PLAN.md:125`): править руками = завести четвёртую точку истины, он переедет сам вместе с зеркалом; (3) `frontend/src/mock/handlers.ts` — код замороженной зоны. Единственное, что стоит сделать сейчас: строка в `frontend/docs/frontend-PROGRESS.md`, что Ф-45 читается через `bank/corrections` (`approve`/`decline`, всё-или-ничего, превью). + +--- + +## N059 · MAJOR · `frontend/docs/BACKLOG.md:62` · C-исполненное-как-открытое +**Срез:** Доки замороженной зоны фронта — frontend/**/*.md (10 файлов, 5167 строк) +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** Живой канал принадлежит ПРОГОНУ (`GET /runs/{runId}/events`), а книга в `uploading`/`parsing` прогона не имеет вовсе: значит «разбор закончился» не приходит ничем. +**Почему протухло:** Канон 0.9.0 перевесил поток на КНИГУ: `GET /books/{bookId}/events`, и его описание прямо называет причину — «a book is received and cut into chapters before any run exists, and those minutes are what a user watches». Пути `/runs/{runId}/events` в каноне больше нет. +**Улика — команда:** `grep -n '^ /runs/{runId}/events:\|^ /books/{bookId}/events:' docs/architecture/14-api-contract/openapi.yaml; sed -n '619,634p' docs/architecture/14-api-contract/openapi.yaml` +**Улика — вывод:** 619: /books/{bookId}/events: (пути /runs/{runId}/events в каноне НЕТ) +623: get: +625: operationId: streamBookEvents +626: summary: Live events of a book (SSE). +628: `text/event-stream`, on the BOOK and not on a run: a book is received and cut into chapters +629: before any run exists, and those minutes are what a user watches. +**Положительный контроль:** grep -n '^ /runs/{runId}' канона → /runs/{runId}: · /runs/{runId}/stop: · /runs/{runId}/resume: (греп по путям /runs/ в этом файле находит три штуки — отсутствие /runs/{runId}/events не артефакт шаблона) +**Другие носители того же факта:** frontend/docs/frontend-PROGRESS.md:495 (пинг №17 п.1) +**Предложенное действие:** Пере-диспозиционировать Ф-56: опрос `intakePollMs = 3000` в `src/api/queries.ts` + `useIntakeEnd.ts` СНОСЯТСЯ при разморозке, подписка переезжает на `GET /books/{bookId}/events`. +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие верно, но формулировка «опрос СНОСИТСЯ» слишком категорична для строки бэклога: `intakePollMs` обслуживает ДВА чтения (`queries.ts:171` библиотека и `:178` карточка книги), и снос — не одна строка, а переезд обоих на подписку плюс `useIntakeEnd.ts`. Записывать так: «Ф-56 ОТВЕЧЕНА каноном (Б-6а, `GET /books/{bookId}/events`); опрос остаётся легитимным лечением до синка зеркала; при разморозке — переезд обоих чтений (`queries.ts:171,178`) и `useIntakeEnd.ts` на поток». Исполнитель — фронт-сессия. + +--- + +## N060 · MAJOR · `frontend/docs/BACKLOG.md:63` · C-исполненное-как-открытое +**Срез:** Доки замороженной зоны фронта — frontend/**/*.md (10 файлов, 5167 строк) +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** Кода на замену немного (одна ручка + чтение вместо константы), но ручки в контракте нет: `POST /books` принимает любой `LangCode`, а отказ по неподдерживаемой паре не выражен ни одним классом ответа. +**Почему протухло:** Ручка построена: канон 0.9.0 даёт `GET /capabilities` с обязательным `language_pairs` («Every pair the deployment knows about, unavailable ones included»). Ф-57 держит как открытую развилку то, что контракт уже решил. +**Улика — команда:** `awk '/^ Capabilities:/{f=1} f{print NR": "$0; c++} c>26{exit}' docs/architecture/14-api-contract/openapi.yaml` +**Улика — вывод:** 1286: Capabilities: +1290: required: +1291: - contract_version +1292: - language_pairs +1293: - intake_enabled +1294: - intake_max_bytes +1295: - export_formats +1296: - bank_corrections_enabled +1297: - page_size_default +1306: language_pairs: +1309: Every pair the deployment knows about, unavailable ones included: "absent" and "listed as unavailable" are different facts to a user waiting for one. +**Другие носители того же факта:** frontend/docs/frontend-PROGRESS.md:496 (пинг №17 п.2 — предсказано, носитель не поправлен) +**Предложенное действие:** Пере-диспозиционировать Ф-57 в работу первого касания: «`src/showcase/languages.ts` перестаёт быть константой и становится чтением `GET /capabilities.language_pairs`» — продуктовой развилки больше нет. +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Находка верна, но список носителей НЕПОЛОН — есть ТРЕТИЙ, и он в коде: докблок `frontend/src/showcase/languages.ts:3-8` дословно повторяет ту же протухшую посылку («the contract has no handle for "what this deployment can translate" … the gap is written down rather than papered over (BACKLOG Ф-57: a handle in the contract)»). Комментарий переедет вместе с заменой константы на чтение `GET /capabilities`, но в диспозицию его назвать надо, иначе при разморозке поправят строку бэклога и оставят ложь в коде. Исполнитель — фронт-сессия первого касания. + +--- + +## N061 · MAJOR · `frontend/docs/BACKLOG.md:68` · C-исполненное-как-открытое +**Срез:** Доки замороженной зоны фронта — frontend/**/*.md (10 файлов, 5167 строк) +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** `POST /books` кладёт название один раз (рукой или из имени файла, 0.2.3), дальше только чтения: `GET /books/{bookId}` и ни одного `PATCH`/`PUT`. … Пока её нет, ошибку в названии лечит только удаление и повторная загрузка, а ручки удаления в контракте тоже нет. +**Почему протухло:** Канон 0.9.0 несёт и `PATCH /books/{bookId}` (merge patch, только `title`, 409 при живом прогоне), и `DELETE /books/{bookId}` (204, 409 при живом прогоне). Обе половины утверждения ложны. +**Улика — команда:** `sed -n '281,325p' docs/architecture/14-api-contract/openapi.yaml | grep -n 'patch:\|delete:\|operationId\|summary'` +**Улика — вывод:** patch: + operationId: updateBook + summary: Rename a book. + «Merge patch (RFC 7386). **Only `title` may be changed.** … Accepted while a run is live» + delete: + operationId: deleteBook + summary: Delete a book. + «Removes the book and everything derived from it … `409` while a run is live — stop it first.» +**Другие носители того же факта:** frontend/docs/frontend-PROGRESS.md:498 (пинг №17 п.4) +**Предложенное действие:** Закрыть Ф-62 как «отвечена каноном (Б-5/0.4.0+)», оставив ЗОННЫЙ хвост: построить экраны переименования и удаления при разморозке; жанр из формы снять (Б-23). +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие верно по существу. Одно уточнение: «жанр из формы снять (Б-23)» — это НЕ хвост Ф-62, а отдельное слово владельца (D39.136 / пинг №17 п.4: «жанр из продукта УХОДИТ совсем»), и вешать его хвостом на строку про переименование значит спрятать самостоятельное решение внутри чужой строки — завести отдельной зонной строкой при разморозке. Исполнитель — фронт-сессия (зона заморожена, D39.147). + +--- + +## N062 · MAJOR · `frontend/docs/FRONTEND_PLAN.md:692` · D-противоречие-ратифицированному +**Срез:** Доки замороженной зоны фронта — frontend/**/*.md (10 файлов, 5167 строк) +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** 1. **Прогресс — пофазный, не «готово N из M».** Юнит становится `done` только когда есть и + черновые строки всех членов, и строка редактуры … Тип прогресса — две пары `{ done, total }` (черновик ∥ редактура), + а не одно число. +**Почему протухло:** Ратифицированный канон 0.9.0 кладёт на провод ОДНУ пару {done,total}, считаемую в ГЛАВАХ по всем проходам, плюс открытую строку `stage`. Клиент, написанный по этому пункту, спроектирует тип, которого контракт не отдаёт. +**Улика — команда:** `sed -n '1396,1414p' docs/architecture/14-api-contract/openapi.yaml` +**Улика — вывод:** required: [done, total, stage, eta_seconds] + properties: + done: + description: Chapters of this run's own work that are finished. + total: + description: Chapters of work this run bought, counted across the passes it owes. + stage: + description: | + What the run is doing NOW — the caption beside the bar, phrased by the client. + … Today's deployments answer `drafting` or `editing` +**Другие носители того же факта:** frontend/docs/STACK_DECISIONS.md:249, frontend/docs/FRONTEND_SESSION_PROMPT.md:437 +**Предложенное действие:** Во всех ТРЁХ носителях заменить «две пары {done,total} (черновик ∥ редактура)» на «одна пара {done,total} в ГЛАВАХ за все проходы + открытая строка `stage`; фазы с провода сняты (Б-0/D39.138, канон ≥0.3.0)». +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Формулировка замены верна по существу, но: (а) третий носитель `FRONTEND_SESSION_PROMPT.md` §9 стоит под ⚠-баннером, который перечисляет ЖИВЫЕ секции — §9 в их числе НЕ назван; правку туда вносить можно, но не как в живое задание, а строкой-эрратой под тем же баннером; (б) ⚠ ЗЕРКАЛО `frontend/docs/api-contract/openapi.yaml` НЕ ТРОГАТЬ: его отставание на 0.2.3 РАТИФИЦИРОВАНО на время фриза (D39.142 п.5; `docs/architecture/14-api-contract/README.md:102` — «зонное зеркало фронта стоит на 0.2.3: при разморозке фронт платит переход целиком»), и правка зеркала врозь с каноном = вторая точка правки; (в) исполнитель — фронт-сессия первого касания (зона заморожена, D39.147), заказ уже стоит в пинге №17 аддендум п.1. + +--- + +## N063 · MAJOR · `frontend/docs/STACK_DECISIONS.md:245` · C-исполненное-как-открытое +**Срез:** Доки замороженной зоны фронта — frontend/**/*.md (10 файлов, 5167 строк) +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** ## 8. Что придётся достроить в движке + +Через оркестратора, в зону `backend/`, в порядке критичности. Обосновано разбором кода. +**Почему протухло:** Все ЧЕТЫРЕ пункта («пофазный прогресс», «персист манифеста глав и чанков», «JSON-выход таблицы подписи банка», «приём внешнего TRACEPARENT в cmd/tmctl») в движке ПОСТРОЕНЫ. Раздел учит сессию, что этих поверхностей нет и их надо заказывать через оркестратора. +**Улика — команда:** `sed -n '9,13p' backend/internal/pipeline/artifact.go; sed -n '17,18p' backend/internal/pipeline/contractblockers_test.go; grep -n 'TM_TRACE_ID' backend/cmd/tmctl/main.go | head -2` +**Улика — вывод:** artifact.go:9-11: «the sidecars beside the project DB … the chapter/chunk manifest (row 100), the machine bank-stop table (row 101), the bank export (row 125)» +contractblockers_test.go:17-18: «the five engine surfaces the API contract is blocked on — per-phase progress (backlog row 99), the persisted chapter/chunk manifest (100), the machine bank-stop table (101), the bank export (125)…» + TestPhaseProgressSplitsTheWaves +main.go:163: v := os.Getenv("TM_TRACE_ID") +**Предложенное действие:** Заменить §8 целиком на строку-указатель: «все четыре поверхности построены (манифест/bank-stop/bank-export — D39.122, `pipeline/artifact.go`; трасса — TM_TRACE_ID, `cmd/tmctl/main.go:163`); актуальный список долгов движка — единый бэклог `docs/PROGRESS.md`». ⚠ Уже названо пингом №17 (frontend-PROGRESS.md:504 п.2) — носитель не поправлен. +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие «заменить §8 ЦЕЛИКОМ» НЕВЕРНО в двух местах. +(1) §8 содержит не только четыре пункта: ниже них лежат ⚠-абзацы «Подпись термина — это НЕ UPDATE строки» (`ReplaceBank` жив — `backend/internal/store/glossary.go:129`; `seedGlossary` жив — `backend/internal/membank/memseed.go:21`), «Выравнивание колонок достраивать не нужно», «Спаны внутри текста не делаем (решение владельца 02.08)» и «После апгрейда бинарника движка…». Снос всего §8 унёс бы живые ратифицированные предупреждения. Правится только НУМЕРОВАННЫЙ список из четырёх пунктов. +(2) Предложенный текст-замена сам ставит МЁРТВЫЙ указатель: «bank-stop — D39.122, `pipeline/artifact.go`». Машинная (JSON) таблица подписи БЫЛА построена D39.122 (коммит `0e69bc1`) и СНЕСЕНА 27.08 паком шва (`d1eb8a9`, D39.158) как не имеющая читателя ни в одной зоне — см. `backend/docs/SEAM_PACK_FINDINGS.md:1415` («Контрольный греп `bank-stop.json|TableJSONPath|…` по `*.go` → 0»). Сегодня существует только человеческий `.bank-stop.txt` (`mining.go:370`), и он пишется ОБЫЧНЫМ `os.WriteFile` (`mining.go:198`), т.е. в список `writeFileAtomic` не входит вовсе. Комментарий `artifact.go:11`, которым опровергаемая находка доказывает существование поверхности, сам протух — это отдельная находка ЗОНЫ БЭКЕНДА, а не улика для фронта. +Верная замена п.3: «машинная таблица подписи была построена (row 101, D39.122) и снесена как беспотребительская (D39.158); фронту её заменяет проекция платформы `GET /books/{bookId}/bank` + движковый экспорт банка `.bank.json` (row 125, `pipeline/bankexport.go:85`)». +(3) ⚠ Зона `frontend/` ЗАМОРОЖЕНА (D39.147, пинг №18) и не является зоной записи оркестратора (CLAUDE.md, таблица ролей: оркестратор пишет `docs/` + корневые доки + зонный журнал `frontend/docs/frontend-PROGRESS.md`). Правка САМОГО `STACK_DECISIONS.md` — работа фронт-сессии первого касания, и она УЖЕ заказана пингом №17 «при разморозке обновить все три». Единственное, что здесь принадлежит оркестратору сегодня, — ⚠-баннер/запись в зонном журнале. + +--- + +## N064 · MAJOR · `platform/README.md:10` · A-дрейф +**Срез:** Доки зоны платформы: platform/README.md · platform/deploy/README.md · platform/docs/{ENGINEERING_STANDARDS,PLATFORM_DIRECTION,STACK_DECISIONS}.md · platform/docs/p8-review/* · platform/docs/p9/* +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** Батарея зоны: `make check` (build · vet · fmt · lint · test -race). `make vuln` и `make fuzz` — +**Почему протухло:** Перечень устарел с лендингом пака sqlc 29.08: `check: build vet fmt lint sqlc-check` — пятый шаг `sqlc-check` в списке отсутствует. Хуже перечня — умолчание о ТУЛЕ: `build` и `lint` зависят от `tools-check`, который ЖЁСТКО ОТКАЗЫВАЕТ без `sqlc v1.31.1` на хосте. Ни рецепт стенда в `STACK_DECISIONS` («Собрать бинари и шаблон книги» — три `go build` и `sed`), ни `deploy/README.md`, ни `ENGINEERING_STANDARDS` §3.1 нигде не называют установку sqlc: слово «sqlc» встречается во всём срезе ровно ОДИН раз — в таблице пинов `STACK_DECISIONS:27`. Сессия, собравшая стенд по рецепту зоны, получает не «зелёную батарею со скипами», а `make: *** [Makefile:62: tools-check] Error 1` — и это выглядит как поломка дерева, а не как недостающий инструмент. Прямо ломает критерий приёмки §3.1 «`make check` зелёный офлайн». +**Улика — команда:** `cd platform && which sqlc; make tools-check 2>&1 | head -3; grep -n 'check: build' Makefile; grep -c sqlc README.md docs/ENGINEERING_STANDARDS.md deploy/README.md; awk 'NR>=430 && NR<=520 && /sqlc/' docs/STACK_DECISIONS.md | wc -l` +**Улика — вывод:** (which sqlc: пусто — не установлен) +sqlc v1.31.1 required (the generated query layer is committed, so the version decides the diff); got: none +install: go install github.com/sqlc-dev/sqlc/cmd/sqlc@v1.31.1 +make: *** [Makefile:62: tools-check] Error 1 +99:check: build vet fmt lint sqlc-check +README.md:0 +docs/ENGINEERING_STANDARDS.md:0 +deploy/README.md:0 +0 (в разделе «Стенд разработчика» слова sqlc нет вовсе) +**Положительный контроль:** `golangci-lint --version` → «has version 2.12.2» — тот же `tools-check` ВИДИТ установленный пиненный тул и на нём не падает; падение происходит именно на sqlc, то есть проверка различает установленное и отсутствующее, а не отказывает всегда. +**Другие носители того же факта:** platform/docs/STACK_DECISIONS.md:49 (§6, тот же перечень «build · vet · fmt · lint · test -race»), platform/docs/STACK_DECISIONS.md:439-455 (рецепт стенда: три go build + шаблон, установки sqlc нет), platform/docs/ENGINEERING_STANDARDS.md:63 («`make check` зелёный офлайн» — недостижимо без тула) +**Предложенное действие:** Дописать в перечень `sqlc-check` (README:10 и STACK_DECISIONS:49) и добавить в рецепт стенда строку `go install github.com/sqlc-dev/sqlc/cmd/sqlc@v1.31.1` рядом со сборкой бинарей, с пометкой «без него `make check` не доходит до тестов вовсе — это НЕ скип, а отказ tools-check». +**Разрушающее:** False + +--- + +## N065 · MAJOR · `platform/deploy/README.md:252` · C-исполненное-как-открытое +**Срез:** Доки зоны платформы: platform/README.md · platform/deploy/README.md · platform/docs/{ENGINEERING_STANDARDS,PLATFORM_DIRECTION,STACK_DECISIONS}.md · platform/docs/p8-review/* · platform/docs/p9/* +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** стенде (P6), но сам шаг `tmctl migrate` не гонялся: движковой команды ещё нет. Пометить сделанным +**Почему протухло:** Глагол `tmctl migrate` существует и заленджен (`backend/cmd/tmctl/migrate.go`, в диспетчере `main.go:184/:245`, свой код выхода `exitSchemaMismatch = 13`), а сам шаг ПРОГНАН на стенде — это записано двумя абзацами выше в ЭТОМ ЖЕ файле («⚠ Проверено исполнением на стенде (P7): … `tmctl migrate --config ` делает пред-миграционный бэкап и переводит v14 → v15») и подтверждено строкой регистра PD-201 («ПОЛОВИНА ЗАКРЫТА P7 (проверка исполнением): рантбук деплоя прогнан end-to-end на стенде живым `migrate`»). Документ спорит сам с собой на протяжении 60 строк, и стоящая последней — ложная редакция. Оператор, читающий рантбук сверху вниз, получает вывод «порядок апгрейда движка не проверен и проверить нечем», хотя проверен. Тем же протухли следующие три строки («Хвост, который НЕ построен… движковый `migrate` ПРИДЁТ с машиноразличимой ошибкой») — форма ошибки УЖЕ финализирована (`schema_mismatch found=N expected=M`, D39.134) и названа в этом же файле; не построен только автомат самолечения, что и есть живой остаток PD-201. +**Улика — команда:** `ls backend/cmd/tmctl/migrate.go && grep -n '"migrate"' backend/cmd/tmctl/main.go | head -2 && grep -n 'exitSchemaMismatch = 13' backend/cmd/tmctl/main.go; awk -F'|' '/^\| PD-201 /{print $(NF-3)}' platform/docs/DEFECT_REGISTER.md | grep -o 'ПОЛОВИНА ЗАКРЫТА P7.\{0,120\}'` +**Улика — вывод:** backend/cmd/tmctl/migrate.go +184: "translate", "report", "status", "export", "build", "redrive", "manifest", "backup", "migrate", "seed-lint", +245: case "migrate": +72: exitSchemaMismatch = 13 // this project's schema is not this binary's: run `tmctl migrate` (row 174) +ПОЛОВИНА ЗАКРЫТА P7 (проверка исполнением):** рантбук деплоя прогнан end-to-end на стенде живым `migrate` — проектная БД, отведённая на схему v14 при бинаре v15 +**Другие носители того же факта:** platform/docs/archive/platform-PROGRESS-P4-P6.md:175 (архив, под баннером — легитимно) +**Предложенное действие:** Снести абзац 251–253 целиком (его содержимое опровергнуто абзацем 215–217 того же файла) и заменить на: «Проверка исполнением ИСПОЛНЕНА паком P7: v14→v15 прогнан на стенде, exit 13 + `schema_mismatch found=14 expected=15` наблюдён живьём». Абзац 255–258 пере-формулировать: форма ошибки известна, НЕ построен только автомат самолечения (PD-201, живой остаток). +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Не «заменить абзац 251–253 на новую формулировку про P7» — это завело бы ВТОРОЙ носитель того же факта в одном файле, ровно тот класс, из-за которого он и разошёлся. Абзац 251–253 снести и на его месте оставить одну отсылку вверх («проверка исполнением — см. врезку P7 выше»), а сам факт держать в единственном месте (строки 187–189). Абзац 255–258 не сносить: его живой остаток — автомат самолечения — по регистру PD-201 действительно open; пере-написать только зачин «движковый `migrate` ПРИДЁТ с машиноразличимой ошибкой» на «форма ошибки финализирована (D39.134, `schema_mismatch found=N expected=M`); не построен автомат». + +--- + +## N066 · MAJOR · `platform/docs/PLATFORM_DIRECTION.md:159` · C-исполненное-как-открытое +**Срез:** Доки зоны платформы: platform/README.md · platform/deploy/README.md · platform/docs/{ENGINEERING_STANDARDS,PLATFORM_DIRECTION,STACK_DECISIONS}.md · platform/docs/p8-review/* · platform/docs/p9/* +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** ⚠ **НЕ ИСПОЛНЕНО на 14.08**, срок «до первого хендлера» пройден — см. баннер статуса выше (PD-44) +**Почему протухло:** sqlc ВЗЯТ и заленджен 29.08 (`D39.172`: «Пак `sqlc` принят и заленджен: 40 запросов пяти файлов `pgstore` на типизированный слой, пин 1.31.1, `sqlc diff` в `make check`»; коммит 63fcee5). В дереве лежат `platform/sqlc.yaml`, `internal/pgstore/queries/{credits,identity,idempotency,sessions}.sql` (ровно 40 `-- name:`), генерённые `*.sql.go`, пин `SQLC_VERSION := 1.31.1` в `tools-check` и цель `sqlc-check` внутри `check`. Ратифицированный документ направления при этом объявляет инструмент неисполненным, а его верхний баннер («ЧИТАТЬ ПРЕЖДЕ ВСЕГО») — работой, ожидающей отдельной сессии («Носитель работы — `BACKLOG.md` П-19»). Побочно устарела и граница взятия в баннере: «пять файлов… `idempotency.go`(7) · `sessions.go`(5) · `observe.go`(1) = **41 запрос**» — в дереве ЧЕТЫРЕ файла запросов, idempotency 6, sessions 6, observe не конвертирован вовсе, итог 40 (`D39.172` и П-19 бэклога уже исправлены на 40, направление — нет). Документ обязателен к ссылке в КАЖДОМ промте платформы, поэтому следующая платформенная сессия читает «инструмент не взят» первым делом. +**Улика — команда:** `git log -1 --format='%h %ad %s' --date=short -- platform/sqlc.yaml | cut -c1-90; ls platform/internal/pgstore/queries/; grep -rh '^-- name:' platform/internal/pgstore/queries/*.sql | wc -l; grep -n 'sqlc-check\|SQLC_VERSION' platform/Makefile | head -4` +**Улика — вывод:** 63fcee5 2026-08-29 Land the sqlc pack: the coverage answer said almost nothing… +credits.sql idempotency.sql identity.sql sessions.sql +40 +26:SQLC_VERSION := 1.31.1 +30:.PHONY: build vet fmt lint test check tools-check version-check vuln fuzz sqlc-check sqlc-generate +78:sqlc-check: tools-check +99:check: build vet fmt lint sqlc-check +**Другие носители того же факта:** platform/docs/PLATFORM_DIRECTION.md:106-116 (верхний баннер: «БЕРЁМ… отдельной сессией», «= 41 запрос», «Носитель работы — BACKLOG.md П-19»), platform/docs/PLATFORM_DIRECTION.md:145 («`sqlc` («до первого хендлера»): НЕ ВЗЯТ»), platform/docs/STACK_DECISIONS.md:27 (ВЕРНАЯ редакция: «Взят по решению владельца 20.08», 40 запросов), platform/BACKLOG.md:31 (П-19, уже исправлен D39.172 на «40 КОНВЕРТИРУЕМЫХ») +**Предложенное действие:** Клетку таблицы 159 пере-подписать: «ВЗЯТ и заленджен 29.08 (D39.172, 63fcee5): 40 запросов четырёх файлов `pgstore/queries/`, пин 1.31.1 в `tools-check`, `sqlc diff` в `make check`». Баннер 106–116 свернуть в исторический (с датой) и поправить счёт 41→40; строку 145 «НЕ ВЗЯТ» пометить как историю P7. П-19 в BACKLOG.md — закрыть/пере-скоупить (зона платформы, не мой ход). +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие верно, но формулировку замены поправить, чтобы она не спорила с ратифицированной нотой: писать «40 запросов, вынесенных в ЧЕТЫРЕ файла `pgstore/queries/`» (D39.172 говорит «сорок запросов пяти файлов pgstore» — это счёт по исходным Go-файлам, и переписывать его в «четырёх файлов» нельзя, номер бьёт док). Строку 145 «НЕ ВЗЯТ» и блок 136–147 не сносить: это записанный ДОВОД отказа P7, улика решения; пометить датой как историю пака. П-19 в `platform/BACKLOG.md` — зона платформы, не ход оркестратора: пинг, а не правка. + +--- + +## N067 · MAJOR · `platform/docs/STACK_DECISIONS.md:621` · B-мёртвый-указатель +**Срез:** Доки зоны платформы: platform/README.md · platform/deploy/README.md · platform/docs/{ENGINEERING_STANDARDS,PLATFORM_DIRECTION,STACK_DECISIONS}.md · platform/docs/p8-review/* · platform/docs/p9/* +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** /sys/fs/cgroup/user.slice/user-1000.slice/user@1000.service/tm-runs.slice/cgroup.subtree_control`. +**Почему протухло:** Лечебная команда «самой частой грабли стенда» указывает на путь, которого не существует: `tm-runs.slice` по конвенции имён systemd вложен в `tm.slice` (срез `X-Y.slice` живёт в `X.slice/X-Y.slice`), поэтому реальный путь — `…/user@1000.service/`**`tm.slice/`**`tm-runs.slice/cgroup.subtree_control`. Оператор получает «No such file or directory» — ровно тот симптом, ради устранения которого правка 29.08 и делалась: тогда `tm.slice` заменили на `tm-runs.slice`, но нужны ОБА уровня. Зонный журнал уже замерил верную вложенность и записал её (`platform/docs/platform-PROGRESS.md:599`: «Сам `tm-runs.slice` лежит глубже, чем его ищут: `user@1000.service/`**`tm.slice`**`/tm-runs.slice`»), но рецепт с ней не сверили. Цена: `TestARunIsBoundedByItsOwnCgroup` остаётся красным, а сессия, честно исполнившая рецепт, ищет дефект в своём диффе. +**Улика — команда:** `B=/sys/fs/cgroup/user.slice/user-1000.slice/user@1000.service; test -e $B/tm-runs.slice/cgroup.subtree_control && echo 'РЕЦЕПТНЫЙ ПУТЬ ЕСТЬ' || echo 'РЕЦЕПТНЫЙ ПУТЬ ОТСУТСТВУЕТ'; test -e $B/tm.slice/tm-runs.slice/cgroup.subtree_control && echo 'РЕАЛЬНЫЙ ПУТЬ (через tm.slice) ЕСТЬ'; cat $B/tm.slice/cgroup.subtree_control; find /sys/fs/cgroup -maxdepth 3 -name '*-*.slice' -type d | head -3` +**Улика — вывод:** РЕЦЕПТНЫЙ ПУТЬ ОТСУТСТВУЕТ +РЕАЛЬНЫЙ ПУТЬ (через tm.slice) ЕСТЬ +memory pids +/sys/fs/cgroup/user.slice/user-1000.slice +/sys/fs/cgroup/system.slice/system-modprobe.slice +/sys/fs/cgroup/system.slice/system-getty.slice +**Положительный контроль:** Тот же find на этом хосте находит РЕАЛЬНЫЕ вложенные срезы (`user.slice/user-1000.slice`, `system.slice/system-modprobe.slice`, `system.slice/system-getty.slice`) — инструмент видит срезы; и `.../tm.slice/tm-runs.slice/cgroup.subtree_control` существует, то есть отсутствует именно рецептный путь, а не каталог cgroup вообще. +**Другие носители того же факта:** platform/docs/p8-review/README.md:23 («опустевший `cgroup.subtree_control` у `tm.slice`, лечится строкой из рецепта») — называет другой уровень, чем рецепт, на который ссылается, platform/docs/STACK_DECISIONS.md:485 (кандидат-замена условия — «состояние `cgroup.subtree_control` среза `tm-runs.slice`», тот же недоуказанный путь) +**Предложенное действие:** В строке 621 вставить недостающий уровень: `…/user@1000.service/tm.slice/tm-runs.slice/cgroup.subtree_control`. Заодно свериться, что `tm.slice/cgroup.subtree_control` тоже несёт `+memory +pids` (на этом хосте несёт) — без родителя лист контроллеров не получит. +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие верно, но одного вставленного уровня мало: рецепт обязан лечить ОБА уровня, иначе он останется ложно-успокаивающим на свежей машине. Правильная форма — `echo "+memory +pids" > …/user@1000.service/tm.slice/cgroup.subtree_control` (если пуст), затем то же в `…/tm.slice/tm-runs.slice/cgroup.subtree_control`: лист не получит контроллеров, которых не раздал родитель. Заодно поправить `p8-review/README.md:23` — там назван `tm.slice` там, где симптом у `tm-runs.slice`, и это второй разошедшийся носитель одного факта. + +--- + +## N068 · MINOR · `CLAUDE.md:8` · B-мёртвый-указатель +**Срез:** Корневые онбординг-доки: CLAUDE.md · docs/README.md · docs/STACK.md +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** «бэкенд только под русский» — объявленная АНТИЦЕЛЬ (`architecture/09-target-architecture.md` §0.1) +**Почему протухло:** Путь написан от `docs/`, а файл — корневой: от корня репозитория `architecture/09-target-architecture.md` не существует; реальный путь — `docs/architecture/09-target-architecture.md`. Ровно этот дефект CLAUDE.md:94 объявляет ИСПРАВЛЕННЫМ своими же словами: «(пути от КОРНЯ репозитория — прежняя редакция писала их от `docs/`, и от корня они не резолвились)» — правка прошла только по секции онбординга и до шапки не дошла. ⚠ Сам якорь §0.1 ЖИВ — битый только префикс пути. +**Улика — команда:** `for p in architecture/09-target-architecture.md docs/architecture/09-target-architecture.md; do [ -e "$p" ] && echo "$p EXISTS" || echo "$p MISSING"; done; grep -n '^## 0.1' docs/architecture/09-target-architecture.md` +**Улика — вывод:** architecture/09-target-architecture.md : MISSING +--- POSITIVE CONTROL --- +docs/architecture/09-target-architecture.md : EXISTS (44039 байт, Aug 30 19:22) +--- якорь жив --- +44:## 0.1 Инвариант общности (поправка владельца, 2026-07-13) — НЕСУЩИЙ +**Положительный контроль:** Тот же `[ -e ]` в том же дереве отвечает EXISTS на `docs/architecture/09-target-architecture.md` и MISSING на тот же путь без префикса `docs/`; файл читается и в нём находится §0.1. Значит отрицание — по факту, а не по опечатке в имени. +**Предложенное действие:** Дописать префикс: `docs/architecture/09-target-architecture.md` §0.1 — привести шапку к норме, которую CLAUDE.md:94 уже объявила действующей. +**Разрушающее:** False + +--- + +## N069 · MINOR · `CLAUDE.md:21` · B-мёртвый-указатель +**Срез:** Корневые онбординг-доки: CLAUDE.md · docs/README.md · docs/STACK.md +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** код и комментарии — по-английски (`architecture/12-go-style-notes` §1) +**Почему протухло:** Второй уцелевший docs-относительный путь, битый ДВАЖДЫ: нет префикса `docs/` И нет расширения `.md`. Реальный носитель — `docs/architecture/12-go-style-notes.md`, и правильную форму эта же CLAUDE.md печатает на строке 70 (гардрейл общности). Как и на строке 8, объявленная на строке 94 правка «пути от КОРНЯ» сюда не дошла. ⚠ Якорь §1 ЖИВ — битый только путь. +**Улика — команда:** `for p in architecture/12-go-style-notes architecture/12-go-style-notes.md docs/architecture/12-go-style-notes.md; do [ -e "$p" ] && echo "$p EXISTS" || echo "$p MISSING"; done; grep -n '^## §' docs/architecture/12-go-style-notes.md | head -3` +**Улика — вывод:** architecture/12-go-style-notes : MISSING +architecture/12-go-style-notes.md : MISSING +--- POSITIVE CONTROL --- +docs/architecture/12-go-style-notes.md : EXISTS (11134 байт, Aug 30 23:44) +--- якорь жив --- +5:## §0. Общность движка (директива владельца 24.07 — жёсткая) +14:## §1. Go-ответы (только то, о чём реально спорим) +**Положительный контроль:** Тот же `[ -e ]` в том же дереве отвечает EXISTS на `docs/architecture/12-go-style-notes.md`, и grep в нём находит §1 на строке 14. Отрицание по двум коротким формам получено тем же инструментом. +**Другие носители того же факта:** CLAUDE.md:70 — та же цель, форма ПРАВИЛЬНАЯ (`docs/architecture/12-go-style-notes.md`) +**Предложенное действие:** Привести к форме строки 70: `docs/architecture/12-go-style-notes.md` §1. +**Разрушающее:** False + +--- + +## N070 · MINOR · `CLAUDE.md:93` · A-дрейф +**Срез:** Корневые онбординг-доки: CLAUDE.md · docs/README.md · docs/STACK.md +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** ⚠ «Живая голова» целиком в обязательное чтение НЕ входит: это 33 ноты +**Почему протухло:** Число родилось верным и сгнило дрейфом. На 24.08 (ff03dca — коммит, вписавший фразу) голова, как её определяет титул D-лога («голова D39.124+ (подрезка D39.139)»), была D39.124…D39.156 — РОВНО 33 ноты. Сегодня голова — D39.124…D39.184, то есть 61 нота (а всех живых тел в файле 87 — сверх головы ещё D22, D25, D29, D30 и два десятка D39.25…D39.106). Число занижает объём почти вдвое; вывод «целиком не читать» от этого крепче, но сессия, планирующая бюджет чтения по этому числу, ошибётся вдвое. +**Улика — команда:** `grep -c '^## D' docs/architecture/05-decisions-log.md; grep -o '^## D39\.[0-9]*' docs/architecture/05-decisions-log.md | sed 's/^## D39\.//' | awk '$1+0>=124' | wc -l; git show ff03dca:docs/architecture/05-decisions-log.md | grep -o '^## D39\.[0-9]*' | sed 's/^## D39\.//' | awk '$1+0>=124' | wc -l` +**Улика — вывод:** === TODAY: total ## D headings === +87 +=== TODAY: head D39.124+ === +61 +=== на ff03dca (2026-08-24 15:54, коммит фразы) === +33 +--- титул D-лога определяет голову одинаково тогда и сейчас: «голова D39.124+ (подрезка D39.139)» --- +**Предложенное действие:** Заменить «это 33 ноты» на форму, которая не гниёт: «это ВСЯ голова D39.124+, сегодня шестой десяток нот». Если число всё же нужно — поставить 61 И завести его шестой сверкой в `docs/scripts/counts.py --check` (скрипт уже считает голову по трём носителям, но литерал в CLAUDE.md не сторожит — именно поэтому он и уехал на 28 нот). +**Разрушающее:** False + +--- + +## N071 · MINOR · `backend/README.md:20` · B-мёртвый-указатель +**Срез:** Доки зоны бэкенда: backend/README.md + backend/docs/*.md (9 файлов) +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** Ингест (txt/epub, кодировки, ruby) + чанкер: `Ingest`/`IngestEncoded` → `Document` +**Почему протухло:** Экспортированной `chunk.Ingest` не существует — в пакете живёт неэкспортированная `ingest(p string)`, обёртка над `IngestEncoded`. Родилась ошибка в ТОМ ЖЕ коммите e33e3be (25.07), который и разэкспортировал функцию, и написал эту строку README: сессия, грепающая `chunk.Ingest`, получает ноль. +**Улика — команда:** `cd backend && grep -rn '^func [Ii]ngest' internal/chunk/*.go; grep -rn 'chunk\.Ingest' --include=*.go . | head -3; git blame -L 20,20 --date=short README.md | cut -c1-60` +**Улика — вывод:** internal/chunk/ingest.go:67:func ingest(p string) (*Document, error) { +internal/chunk/ingest.go:77:func IngestEncoded(p, encoding, sourceLang string) (*Document, error) { +--- +internal/pipeline/events.go:577: doc, err := chunk.IngestEncoded(r.Book.SourceFile, ...) +internal/pipeline/contractblockers_test.go:381: doc, err := chunk.IngestEncoded(...) +internal/pipeline/bookbuild_test.go:29: doc, err := chunk.IngestEncoded(epubPath, "", "") +--- +e33e3be2 (Claude (backend session) 2026-07-25 20) +**Положительный контроль:** Тот же греп в той же области находит живой символ: `grep -rn 'chunk\.Ingest' --include=*.go .` → три вызова `chunk.IngestEncoded` (events.go:577, contractblockers_test.go:381, bookbuild_test.go:29). Пустота относится именно к `Ingest`, а не к инструменту. +**Предложенное действие:** Заменить «`Ingest`/`IngestEncoded`» на «`IngestEncoded` (единственный экспорт; `ingest` — внутренняя терсная обёртка)». +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие в целом верно, но формулировка неточна: `ingest` — не «терсная обёртка», а точка входа по расширению (она разветвляется на `ingestTXT`/`ingestEPUB`), и главное — она ТЕСТОВАЯ: вне пакета недостижима, вызывается только из `internal/chunk/ingest_test.go`. Точнее: «`IngestEncoded` → `Document` (единственный экспорт пакета; неэкспортированная `ingest` — вход по умолчанию для тестов пакета)». + +--- + +## N072 · MINOR · `backend/README.md:94` · B-мёртвый-указатель +**Срез:** Доки зоны бэкенда: backend/README.md + backend/docs/*.md (9 файлов) +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** Известный техдолг (не трогать молча; ЕДИНЫЙ трекер — таблица «Бэклог» в `docs/PROGRESS.md`, номера строк = она) +**Почему протухло:** README объявляет, что все его «строка N» разрешаются в таблицу «Бэклог», и цитирует тринадцать номеров. Семь из них в таблице отсутствуют — закрыты и по правилу таблицы вынесены: 53, 100, 101, 125, 145, 174, 236 (236 закрыта D39.175, 174 — паком migrate, 100/101/125 — контракт-блокерами). Живы только 49, 49а, 50, 79, 146, 173. Для номеров-провенансов это не техдолг, но обещание разрешимости README даёт всем. +**Улика — команда:** `for id in 49 49а 50 53 79 100 101 125 145 146 173 174 236; do printf '%-5s: ' "$id"; grep -qE "^\| *\*{0,2}${id}\*{0,2} *\|" docs/PROGRESS.md && echo ЖИВА || echo 'НЕТ В ТАБЛИЦЕ'; done` +**Улика — вывод:** 49 : ЖИВА +49а : ЖИВА +50 : ЖИВА +53 : НЕТ В ТАБЛИЦЕ +79 : ЖИВА +100 : НЕТ В ТАБЛИЦЕ +101 : НЕТ В ТАБЛИЦЕ +125 : НЕТ В ТАБЛИЦЕ +145 : НЕТ В ТАБЛИЦЕ +146 : ЖИВА +173 : ЖИВА +174 : НЕТ В ТАБЛИЦЕ +236 : НЕТ В ТАБЛИЦЕ +**Положительный контроль:** Тот же паттерн в той же области находит 189 существующих ID таблицы (`grep -oE '^\| *\*{0,2}[0-9]+[а-я]?\*{0,2} *\|' docs/PROGRESS.md | wc -l` → 189), в том числе шесть из тринадцати цитируемых README — значит пустота по остальным семи не артефакт грепа. +**Предложенное действие:** Добавить в шапку секции одну фразу: «номер, которого в таблице нет, — ЗАКРЫТАЯ строка: её тело ушло в D-ноту, ищи грепом номер в `05-decisions-log.md`». Это дешевле, чем переписывать 7 провенансов, и лечит класс, а не случай. +**Разрушающее:** False + +--- + +## N073 · MINOR · `backend/docs/MONEY_HONESTY_REPORT.md:787` · C-исполненное-как-открытое +**Срез:** Доки зоны бэкенда: backend/README.md + backend/docs/*.md (9 файлов) +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** Ничего не закоммичено; лендинг за оркестратором. +**Почему протухло:** Заленджено — коммитом bb541a8 (31.08) и принято нотой D39.182. Фраза стоит в секции, которая сама называет себя «§11. Последний абзац — СОСТОЯНИЕ, не план», а `docs/PROGRESS.md:299-300` отправляет читателя в этот файл как в долговременный носитель — значит абзац читается как текущее состояние. Тот же файл (строка 2) и записка-план (строка 3) указывают на `docs/BACKEND_MONEY_HONESTY_SESSION_PROMPT.md`, которого в `docs/` нет: промт переехал в `docs/archive/prompts/`. +**Улика — команда:** `git log --format='%h %ad %s' --date=short -1 -- backend/docs/MONEY_HONESTY_REPORT.md | cat; grep -n '^## D39.182' docs/architecture/05-decisions-log.md | cut -c1-120; ls docs/BACKEND_MONEY_HONESTY_SESSION_PROMPT.md; ls docs/archive/prompts/BACKEND_MONEY_HONESTY_SESSION_PROMPT.md` +**Улика — вывод:** bb541a8 2026-08-31 Land the disclosure law and its eleven instances: two of the three acceptance blockers were defects inside the cure itself +1733:## D39.182 — БЭКЕНД-ПАК «ДЕНЬГИ И ЧЕСТНОСТЬ ВЫДАЧИ» ПРИНЯТ И ЗАЛЕНДЖЕН +ls: cannot access 'docs/BACKEND_MONEY_HONESTY_SESSION_PROMPT.md': No such file or directory +docs/archive/prompts/BACKEND_MONEY_HONESTY_SESSION_PROMPT.md +**Положительный контроль:** Указатель проверен на живом образце той же формы: `ls docs/ORCHESTRATOR_SESSION_PROMPT.md` резолвится (32182 байта), то есть отсутствие относится к файлу, а не к каталогу или команде. +**Другие носители того же факта:** backend/docs/MONEY_HONESTY_PLAN-NOTE.md:3 — тот же мёртвый указатель на промт, backend/docs/MONEY_HONESTY_REPORT.md:2 — тот же указатель, docs/PROGRESS.md:298 — тот же указатель (зона оркестратора) +**Предложенное действие:** Добавить в шапку отчёта (тело по D23.3 не переписывать) дату-баннер: «⚠ Дописано 01.09: пак ЗАЛЕНДЖЕН bb541a8 и принят D39.182; §11 «ничего не закоммичено» — состояние на момент сдачи. Промт переехал в `docs/archive/prompts/BACKEND_MONEY_HONESTY_SESSION_PROMPT.md`.» То же — в шапку записки-плана. +**Разрушающее:** False + +--- + +## N074 · MINOR · `backend/docs/SEAM_FIX3_PLAN.md:1` · F-целиком-историческое +**Срез:** Доки зоны бэкенда: backend/README.md + backend/docs/*.md (9 файлов) +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** # План фикс3 (промт BACKEND_SEAM_FIX3_SESSION_PROMPT.md) + +> Комплектность: `grep -cE '^### 4\.' docs/BACKEND_SEAM_FIX3_SESSION_PROMPT.md` → **6**. +**Почему протухло:** Четыре записки-плана паков шва — чистые списки намерений отработавших сессий. Паки приняты и заленджены (D39.158: «Четыре пака зоны бэкенда приняты и заленджены одним деревом»), а их промты архивированы коммитом 31b96d1 — так что первая строка каждой записки и её команда комплектности указывают в несуществующий файл. Ни один ЖИВОЙ док и ни одна строка кода на эти четыре записки не ссылается: единственные ссылки — из `SEAM_PACK_FINDINGS.md` и из уже архивированных промтов. ⚠ ВАЖНОЕ ИСКЛЮЧЕНИЕ: `SEAM_PACK_FINDINGS.md` в архив НЕ кандидат — на неё ссылается ратифицированная нота D39.158 как на доказательную базу. +**Улика — команда:** `ls docs/BACKEND_SEAM_*; ls docs/archive/prompts/ | grep -i seam; grep -rn 'SEAM_FIX3_PLAN\|SEAM_FIX2_PLAN\|SEAM_FIX_PLAN\|SEAM_PACK_PLAN' --include='*.md' --include='*.go' . | grep -v '^./backend/docs/SEAM' | grep -v '^./docs/archive/'; sed -n '698p' docs/architecture/05-decisions-log.md | cut -c1-160` +**Улика — вывод:** ls: cannot access 'docs/BACKEND_SEAM_*': No such file or directory +BACKEND_SEAM_FIX2_SESSION_PROMPT_2026-08-27.md +BACKEND_SEAM_FIX3_SESSION_PROMPT_2026-08-27.md +BACKEND_SEAM_FIX_SESSION_PROMPT_2026-08-27.md +BACKEND_SEAM_PACK_SESSION_PROMPT_2026-08-27.md +--- ссылки из живых доков/кода: (пусто) --- +Четыре пака зоны бэкенда приняты и заленджены одним деревом. Механика и доказательная база — `backend/docs/SEAM_PACK_FINDINGS.md` (вход §26 и §43) +**Положительный контроль:** Инструмент в этой области ссылки находит: тем же грепом видны хиты из `SEAM_PACK_FINDINGS.md` и из `docs/archive/prompts/*`, а `ls docs/ORCHESTRATOR_SESSION_PROMPT.md` подтверждает, что живые промты в `docs/` резолвятся — то есть ноль по `docs/BACKEND_SEAM_*` относится к файлам, а не к команде. +**Другие носители того же факта:** backend/docs/SEAM_FIX_PLAN.md:1 — тот же мёртвый указатель на `docs/BACKEND_SEAM_FIX_SESSION_PROMPT.md`, backend/docs/SEAM_FIX2_PLAN.md:1,3 — на `BACKEND_SEAM_FIX2_SESSION_PROMPT.md`, backend/docs/SEAM_PACK_PLAN.md:3 — на `docs/BACKEND_SEAM_PACK_SESSION_PROMPT.md` +**Предложенное действие:** Вынести ЧЕТЫРЕ записки (SEAM_PACK_PLAN · SEAM_FIX_PLAN · SEAM_FIX2_PLAN · SEAM_FIX3_PLAN) в `docs/archive/` рядом с их промтами, с ⚠-баннером «план отработавшего пака, принят D39.158». `SEAM_PACK_FINDINGS.md` ОСТАВИТЬ на месте — на неё смотрит D39.158; если её всё же двигать, сначала править ноту. +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Находка верна, ДЕЙСТВИЕ — нет, в двух местах. (1) Автор объявил SEAM_PACK_FINDINGS.md неприкосновенной, но не заметил, что она же держит ссылки на переносимые файлы: строки 679-680 прямо говорят «Едут вместе с деревом и без них его нельзя принять: `backend/docs/SEAM_FIX_PLAN.md` … `backend/docs/SEAM_PACK_PLAN.md`», плюс :3, :270, :1327. Перенос без правки этих пяти ссылок ПОРОДИТ ровно тот класс дефекта, который находка чинит, — и породит его в доке, куда ратифицированная D39.158 посылает читателя. Значит: либо переносить вместе с обновлением пяти указателей в FINDINGS, либо не переносить вовсе, а поставить каждой записке ⚠-баннер «план отработавшего пака, принят D39.158; промт — `docs/archive/prompts/BACKEND_SEAM_*_2026-08-27.md`» — второе дешевле и по правилам проекта достаточно: баннер И ЕСТЬ механизм легитимности исторического. (2) Зоны: `backend/docs/` — зона БЭКЕНДА, `docs/archive/` — зона оркестратора; перенос файлов из чужой зоны оркестратор в одиночку не делает. Это либо пинг бэкенд-сессии, либо явное согласование. Рекомендую минимальный ход: ⚠-баннеры на месте (правит бэкенд-зона), FINDINGS не трогать вовсе. + +--- + +## N075 · MINOR · `docs/PROGRESS.md:3` · B-мёртвый-указатель +**Срез:** ГОЛОВА журнала: CURRENT-STATE и очередь — docs/PROGRESS.md, строки 1–18 (шапка ⟶ ТЕКУЩЕЕ СОСТОЯНИЕ, ~34 КБ) +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** Контекст заказа (промт `BACKEND_COLDRUN_V16_SESSION_PROMPT.md` отработан, в `archive/prompts/`) +**Почему протухло:** Промт НЕ в `docs/archive/prompts/` — он до сих пор лежит в корне `docs/`. В архиве промтов лежат два ДРУГИХ холодно-прогонных промта (`BACKEND_COLDRUN_SESSION_PROMPT.md`, `BACKEND_COLDRUN_DEBUG_SESSION_PROMPT.md`), с которыми V16 легко спутать. Тот же дефект у второго отработанного промта: `docs/BACKEND_MONEY_HONESTY_SESSION_PROMPT.md` (пак принят 31.08, D39.182) тоже остался в корне, хотя норма зоны — выносить отработанный промт в архив (ср. `archive/prompts/BACKEND_MONEY_PACK_SESSION_PROMPT_2026-08-29.md`, `.../BACKEND_BOOKWRITER_PACK_SESSION_PROMPT_2026-08-30.md`). +**Улика — команда:** `find . -L -name 'BACKEND_COLDRUN*' -not -path './books/*'; ls docs/BACKEND_*.md` +**Улика — вывод:** ./docs/BACKEND_COLDRUN_V16_SESSION_PROMPT.md +./docs/archive/prompts/BACKEND_COLDRUN_DEBUG_SESSION_PROMPT.md +./docs/archive/prompts/BACKEND_COLDRUN_SESSION_PROMPT.md + +ls docs/BACKEND_*.md → +docs/BACKEND_COLDRUN_V16_SESSION_PROMPT.md +docs/BACKEND_MONEY_HONESTY_SESSION_PROMPT.md +**Положительный контроль:** ls docs/archive/prompts/ | grep -i 'v16\|COLDRUN' → BACKEND_COLDRUN_DEBUG_SESSION_PROMPT.md · BACKEND_COLDRUN_SESSION_PROMPT.md · ORCHESTRATOR_SESSION_PROMPT_v16_2026-08-16.md. Каталог существует, содержит и «COLDRUN», и «v16» — пустой результат по V16-промту означает отсутствие файла, а не опечатку в шаблоне. +**Предложенное действие:** Одно из двух, но одно обязательно: либо вынести оба отработанных промта (`docs/BACKEND_COLDRUN_V16_SESSION_PROMPT.md` и `docs/BACKEND_MONEY_HONESTY_SESSION_PROMPT.md`) в `docs/archive/prompts/` с датой в имени, как принято, и оставить указатель шапки как есть; либо оставить файлы на месте и исправить текст на «промт отработан, лежит в `docs/`». +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие верно, и лучше исполнить именно вторую, критериальную его половину: заменить ручной счёт на «ни один пак платформы после P7 этот долг не взял (проверка: `grep -rn 'createExport\|getExport' platform/internal/ --include=*.go` — пусто)», чтобы число не гнило при каждом следующем лендинге. Если счёт всё-таки оставлять — «шесть паков (P8-FIX · P8-REVIEW · P9 · P10 · P11 · P12), плюс непронумерованный `sqlc`». + +--- + +## N076 · MINOR · `docs/PROGRESS.md:3` · A-дрейф +**Срез:** ГОЛОВА журнала: CURRENT-STATE и очередь — docs/PROGRESS.md, строки 1–18 (шапка ⟶ ТЕКУЩЕЕ СОСТОЯНИЕ, ~34 КБ) +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** «развязка git с origin» ОПРОВЕРГНУТА — `main` и `origin/main` на одном коммите. +**Почему протухло:** Сегодня `main` опережает `origin/main` на 48 коммитов. Несущий вывод («развязки нет») выжил — `origin/main` остаётся ПРЕДКОМ `main`, расхождения по-прежнему нет, — но предъявленная улика «на одном коммите» ложна, и читатель шапки заключает, что всё запушено. Замер помечен «проверено деревом 30.08»: с тех пор легло 48 коммитов, ни один не запушен. +**Улика — команда:** `git rev-parse --short main origin/main; git rev-list --left-right --count main...origin/main; git merge-base --is-ancestor origin/main main && echo 'origin/main — ПРЕДОК main'` +**Улика — вывод:** 162ab9b +8977001 +48 0 +origin/main — ПРЕДОК main + +git log -1 --date=short --format='%h %ad %s' origin/main → +8977001 2026-08-30 Hand the orchestrator role over with the shift's seven own errors named in full +**Другие носители того же факта:** docs/PROGRESS.md:11 — в листе владельца тот же предмет стоит ОТКРЫТЫМ: «**развязка git с origin** (локальная линия ИСТИННА, force-push его рукой; не пуллить)». Две половины шапки говорят о развязке разное: строка 3 объявляет её опровергнутой, строка 11 держит её на владельце. +**Предложенное действие:** Заменить улику на измеримую и датированную: «`origin/main` — ПРЕДОК `main`, расхождения нет; локально не запушено N коммитов (замер <дата>, `git rev-list --left-right --count main...origin/main`)». Одновременно свести со строкой 11: либо снять пункт с листа владельца, либо переписать вывод строки 3 как «развязки нет, но линия не запушена». +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие верно, и лучше исполнить именно вторую, критериальную его половину: заменить ручной счёт на «ни один пак платформы после P7 этот долг не взял (проверка: `grep -rn 'createExport\|getExport' platform/internal/ --include=*.go` — пусто)», чтобы число не гнило при каждом следующем лендинге. Если счёт всё-таки оставлять — «шесть паков (P8-FIX · P8-REVIEW · P9 · P10 · P11 · P12), плюс непронумерованный `sqlc`». + +--- + +## N077 · MINOR · `docs/PROGRESS.md:3` · A-дрейф +**Срез:** ГОЛОВА журнала: CURRENT-STATE и очередь — docs/PROGRESS.md, строки 1–18 (шапка ⟶ ТЕКУЩЕЕ СОСТОЯНИЕ, ~34 КБ) +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** Долг числится с P8 (`platform/BACKLOG.md` П-17), с тех пор легло пять паков и ни один его не взял. +**Почему протухло:** «Пять» было верно до 31.08 (P8-FIX · P8-REVIEW · P9 · P10 · P11). После P7, где долг выдачи был отложен в P8, по реестру нот легло ШЕСТЬ P-нумерованных паков платформы (добавился P12, D39.180, 31.08) плюс непронумерованный пак `sqlc` (D39.172) — семь лендингов. Лендинг P12 объявлен В ЭТОЙ ЖЕ строке шапки, а счётчик паков рядом не пересчитан. Сам указатель на П-17 при этом ВЕРЕН — его выходная ячейка дословно несёт «НЕ вошло и отложено в P8: `updateBook`/`deleteBook`/`getRun`, экспорт (`createExport`/`getExport`), эскроу (П-18)». +**Улика — команда:** `grep -nE '\| D39\.(15[0-9]|16[0-9]|17[0-9]|18[0-9]) \|' docs/architecture/05-decisions-index.md | grep -iE 'пак P|P8|P9|P10|P11|P12|sqlc'` +**Улика — вывод:** 218:| D39.154 | 22.08 | Приёмка и лендинг P8-FIX платформы ... +223:| D39.159 | 27.08 | Пак P8-REVIEW принят ... +226:| D39.162 | 28.08 | **Платформенный пак P9 принят и заленджен + контрактный минор 0.6.0** ... +230:| D39.166 | 28.08 | **Пак P10 принят и заленджен + контрактный минор 0.7.0** ... +233:| D39.169 | 29.08 | **Платформенный пак P11 принят и заленджен + контрактный минор 0.8.0** ... +236:| D39.172 | 29.08 | **Пак `sqlc` принят и заленджен** ... +244:| D39.180 | 31.08 | **Пак платформы P12 «долги под ногами» ПРИНЯТ И ЗАЛЕНДЖЕН** ... +**Предложенное действие:** Заменить «пять паков» на «шесть паков (P8-FIX · P8-REVIEW · P9 · P10 · P11 · P12), плюс непронумерованный `sqlc`». Ещё лучше — снять счёт руками и написать критерий («ни один пак платформы после P7 его не взял»), чтобы число не гнило при каждом лендинге. +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие верно, и лучше исполнить именно вторую, критериальную его половину: заменить ручной счёт на «ни один пак платформы после P7 этот долг не взял (проверка: `grep -rn 'createExport\|getExport' platform/internal/ --include=*.go` — пусто)», чтобы число не гнило при каждом следующем лендинге. Если счёт всё-таки оставлять — «шесть паков (P8-FIX · P8-REVIEW · P9 · P10 · P11 · P12), плюс непронумерованный `sqlc`». + +--- + +## N078 · MINOR · `docs/PROGRESS.md:15` · C-исполненное-как-открытое +**Срез:** ГОЛОВА журнала: CURRENT-STATE и очередь — docs/PROGRESS.md, строки 1–18 (шапка ⟶ ТЕКУЩЕЕ СОСТОЯНИЕ, ~34 КБ) +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** ~$0.85/ранобэ (D30.4, пере-калибровка при следующем платном прогоне) +**Почему протухло:** Условие, по которому число обещано пере-калибровать, ИСПОЛНИЛОСЬ: платный прогон отработал 31.08 и дал собственную оценку движка $1.1377277707 на десяти главах при куплённых $0.436110 (об этом сообщает та же шапка строкой 3). Триггер нажат, пере-калибровка не сделана и нигде не отмечена, а формулировка «при следующем платном прогоне» отсылает читателя в будущее, которое уже прошло. Само число $0.85 я НЕ объявляю ложным: единица измерения («ранобэ» целиком) и десять глав прогона несопоставимы напрямую — опровергнут именно статус ожидания. +**Улика — команда:** `sed -n '15p' docs/PROGRESS.md | grep -o '~\$0\.85/ранобэ ([^)]*)'; sed -n '69,72p' docs/archive/reports/COLDRUN_V16_REPORT_2026-08-31.md` +**Улика — вывод:** ~$0.85/ранобэ (D30.4, пере-калибровка при следующем платном прогоне) + +Полная цепь на десяти главах в СЕГОДНЯШНИХ ценах стоит **≈$1.14 по собственной оценке движка** +(`projected_book_usd = 1.1377277707`) против приора ≈$0.58 и санкции $0.80. Прогон остановлен **не +потолком, а снапшот-гардом**, потребовавшим `--resnapshot` после покупки двумя порциями. +Куплено **$0.436110**, доставлено **3 юнита из 14** (черновая волна 14/14, редакторская 3/14). +**Другие носители того же факта:** docs/STACK.md:21 — «~$0.85/ранобэ (⚠ число ДО пере-пина цен 13–15.08 ... пере-калибровать первым же платным прогоном ... пере-калибровка при следующем платном прогоне (112 залендена, D39.97))» — тот же просроченный триггер, названный ДВАЖДЫ в одной ячейке, docs/product-requirements.md:32 — ПТ-12 «Низкий COGS» со статусом ✅ и тем же числом ~$0.85/ранобэ +**Предложенное действие:** Во всех трёх носителях заменить обещание на факт: «платный прогон отработал 31.08 (`archive/reports/COLDRUN_V16_REPORT_2026-08-31.md`), собственная оценка движка — $1.1377 на десяти главах gu-zhenren при куплённых $0.436110; пере-калибровка $/ранобэ НЕ сделана — завести строку бэклога». Число $0.85 не трогать, пока не будет замера в тех же единицах. +**Разрушающее:** False + +--- + +## N079 · MINOR · `docs/PROGRESS.md:146` · C-исполненное-как-открытое +**Срез:** ЕДИНЫЙ БЭКЛОГ docs/PROGRESS.md — таблица (строки 57–272 файла), проверка на класс C «исполненное как открытое» +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** скоро (гейт: первый холодный прогон, §6 отчёта) +**Почему протухло:** Гейт пал: первый холодный прогон отработал 31.08 и выдал ровно то число, на котором строка условила решение — `families_held=0` (строка требует ослаблять гард «если families_held велик»). Ответ получен и он отрицательный: ослаблять нечего. Открытым может остаться только пере-подтверждение `family_max_members=24` на ДЛИННОЙ книге — прогон был десятиглавным. +**Улика — команда:** `grep -n 'families=11' docs/archive/reports/COLDRUN_V16_REPORT_2026-08-31.md` +**Улика — вывод:** 448:families=11 families_refused=0 families_held=0`. ⚠ Есть ВТОРАЯ строка про семейства с ДРУГИМИ именами +449:полей (`refused_by_cap`/`held_by_series`) — греп по `families_refused` её не найдёт; здесь не печаталась. +**Другие носители того же факта:** docs/archive/reports/COLDRUN_V16_REPORT_2026-08-31.md §9 (подписано «Строка 154 — ЗАКРЫТА числами» — крест-накрест с таблицей) +**Предложенное действие:** Сузить строку 157: записать замер `families=11 · families_refused=0 · families_held=0` (10 глав, 31.08) и вывод «ослаблять гард не нужно»; живым остатком оставить только пере-подтверждение `family_max_members=24` на длинной книге, с новым гейтом вместо отработавшего. +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие верно, но сужать надо аккуратнее, чем написано: `families_held=0` замерен на ДЕСЯТИГЛАВНОЙ книге, а строка требует пере-подтверждения на ДЛИННОЙ — значит вывод «ослаблять гард не нужно» пишется с явной оговоркой «на 10 главах», иначе он сам протухнет на первой длинной книге. Тем же касанием починить указатель гейта «§6 отчёта»: в отчёте холодного прогона §6 — это «Две ловушки `build`», числа семейств в §9; ставить греп-указатель (`families_held` по отчёту), а не номер секции. + +--- + +## N080 · MINOR · `docs/PROGRESS.md:183` · B-мёртвый-указатель +**Срез:** ЕДИНЫЙ БЭКЛОГ docs/PROGRESS.md — таблица (строки 57–272 файла), проверка на класс C «исполненное как открытое» +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** `unspecified` ратифицирован в каноне (`openapi.yaml:1784-1787`) +**Почему протухло:** Оба line-якоря строки 203 уехали после контрактного минора 0.9.0 (P12, `0a680a3`, 31.08). `openapi.yaml:1784-1787` сегодня показывает поля `id`/`units_done` схемы прогресса; ратификация `unspecified` живёт на 1903–1906. Соседний якорь `README.md:1376` («клетка приложения А заполнена `unspecified`») показывает текст про идемпотентность; настоящие цели — `README.md:1361` (ратификация) и `:1700` (клетка таблицы). ⚠ Этот же якорь строка уже чинила 20.08 («первая редакция писала `:1391`») — гниёт вторично. Третий якорь строки, `README.md:559`, ПРОВЕРЕН и верен. +**Улика — команда:** `sed -n '1784,1787p' docs/architecture/14-api-contract/openapi.yaml; grep -n 'unspecified' docs/architecture/14-api-contract/openapi.yaml | head -3; sed -n '1376p' docs/architecture/14-api-contract/README.md; grep -n 'unspecified' docs/architecture/14-api-contract/README.md | head -3` +**Улика — вывод:** id: { $ref: '#/components/schemas/Id' } + units_done: + type: integer + minimum: 0 +--- +1903: ⚠ **`unspecified` is reserved and is the server's answer when it has no code for what it +1906: required, so a server has to write something. Writing `unspecified` is the obligation; +--- +ключ возвращает ответ, принадлежащий чужому запросу. Это нарушение самой гарантии, ради которой +--- +1361:сервер обязан прислать значение, которого словарь не содержит. `unspecified` ратифицирован как +1700:| незнакомая причина | 8 (по умолчанию) | **`unspecified`** (обязанность сервера, п.5; клиент рисует ту же нейтральную фразу, что и на незнакомый код) | +**Предложенное действие:** Пере-нацелить два якоря строки 203: `openapi.yaml:1784-1787` → `:1903-1906`, `README.md:1376` → `:1361` (ратификация) или `:1700` (клетка приложения А). ⚠ Лучше сразу на форму без номера (греп отличительного токена `unspecified is reserved`), иначе следующий контрактный минор убьёт их в третий раз. +**Разрушающее:** False + +--- + +## N081 · MINOR · `docs/PROGRESS.md:193` · B-мёртвый-указатель +**Срез:** ЕДИНЫЙ БЭКЛОГ docs/PROGRESS.md — таблица (строки 57–272 файла), проверка на класс C «исполненное как открытое» +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** «declining a surface removes EVERY window of that surface» (`docs/architecture/14-api-contract/openapi.yaml:2051`) +**Почему протухло:** Якорь уехал на 28 строк после контрактного минора 0.9.0 (P12, 31.08): цитируемая фраза стоит на `:2079`, а `:2051` показывает заголовок совсем другой схемы `BankCorrection:`. Цитата верна, адрес — нет. +**Улика — команда:** `sed -n '2051p' docs/architecture/14-api-contract/openapi.yaml; grep -n 'removes EVERY window of that surface' docs/architecture/14-api-contract/openapi.yaml` +**Улика — вывод:** BankCorrection: +--- +2079: the old one standing, and declining a surface removes EVERY window of that surface. +**Предложенное действие:** Пере-нацелить якорь строки 228 с `openapi.yaml:2051` на `:2079`, а лучше — на греп-форму по самой цитате, поскольку канон движется каждым минором. +**Разрушающее:** False + +--- + +## N082 · MINOR · `docs/PROGRESS.md:207` · C-исполненное-как-открытое +**Срез:** ЕДИНЫЙ БЭКЛОГ docs/PROGRESS.md — таблица (строки 57–272 файла), проверка на класс C «исполненное как открытое» +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** новые файлы стейджить явно и проверять состав `git show --name-only`, а не доверять форме +**Почему протухло:** Строка исполнена НАПОЛОВИНУ, и строка об этом не знает: 31.08 коммитом `94381bb` в `docs/scripts/githooks/pre-commit` добавлен warn-блок, который перед каждым коммитом печатает неотслеживаемые файлы в каталогах коммита и дословно советует ту самую норму («git add -- <путь>, затем проверь состав: git show --name-only»). Открытой осталась ровно одна половина — строка в `CLAUDE.md` §«Git-координация мультисессий», которой там действительно нет. +**Улика — команда:** `grep -n 'неотслеживаемые файлы в каталогах ЭТОГО коммита\|git show --name-only' docs/scripts/githooks/pre-commit; grep -n 'неотслеж\|untracked\|show --name-only' CLAUDE.md` +**Улика — вывод:** 115: echo "pre-commit ⚠ неотслеживаемые файлы в каталогах ЭТОГО коммита — pathspec-форма их РОНЯЕТ:" >&2 +117: echo ' Нужны в коммите? git add -- <путь>, затем проверь состав: git show --name-only' >&2 +--- +(по CLAUDE.md — НИ ОДНОГО вхождения) +**Положительный контроль:** grep -n 'pathspec' CLAUDE.md → 80: «…снимай не чужое из индекса, а свою задачу — коммить pathspec-формой.» — греп по CLAUDE.md в этой области содержимое находит, значит пустота по «неотслеживаемым» — факт. +**Другие носители того же факта:** docs/scripts/githooks/pre-commit:99-121 (построенная половина), CLAUDE.md §«Git-координация мультисессий» (носитель незакрытой половины) +**Предложенное действие:** Дописать в строку 245, что машинная половина УЖЕ стоит (`docs/scripts/githooks/pre-commit`, `94381bb`, warn-only), и сузить остаток до одной фразы в `CLAUDE.md`, которую вписывает владелец; иначе следующая сессия построит хук второй раз. +**Разрушающее:** False + +--- + +## N083 · MINOR · `docs/README.md:20` · A-дрейф +**Срез:** Корневые онбординг-доки: CLAUDE.md · docs/README.md · docs/STACK.md +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** **`--lint` якорей — при коммите, задевающем ЛЮБОЙ док или `CLAUDE.md`; `--check` чисел и головы — при коммите с D-логом или PROGRESS** +**Почему протухло:** Половина фразы верна, половина протухла. `--check` действительно гейчен составом коммита (`docs/scripts/githooks/pre-commit:74`). `--lint` НЕ гейчен ничем: блок 3 (строки 89–97) исполняется при ЛЮБОМ непустом индексе — единственный ранний выход хука это `[ -z "$staged" ] && exit 0` на строке 37. Сам хук объясняет отмену гейта на строках 42–47. Следствие уже живёт строкой бэклога 244 («гейт якорей ломается всякий раз, когда зона правит КОД…»): то есть проект знает поведение, а карта всё ещё обещает старое. +**Улика — команда:** `grep -n 'printf .%s\\n. "$staged" | grep' docs/scripts/githooks/pre-commit; sed -n '86,97p' docs/scripts/githooks/pre-commit; sed -n '42,44p' docs/scripts/githooks/pre-commit` +**Улика — вывод:** 52:if printf '%s\n' "$staged" | grep -qx "$dlog"; then +74:if printf '%s\n' "$staged" | grep -qE "^($dlog|$prog)$"; then +--- блок 3, строки 86-90 (ГЕЙТА ПО СОСТАВУ НЕТ) --- +# 3) Якоря file:line живых доков (D39.148 …) +if command -v python3 >/dev/null 2>&1 && [ -f docs/scripts/counts.py ]; then + if ! out=$(python3 docs/scripts/counts.py --lint --from-index 2>&1); then +--- строки 42-44 --- +# ⚠ БЫСТРОГО ВЫХОДА ПО «коммит не задевает docs/» БОЛЬШЕ НЕТ (21.08, разбор с внешним ревьюером). +**Положительный контроль:** grep по условной форме `printf '%s\n' "$staged" | grep` находит ДВА гейта — строки 52 и 74 (блоки «голова» и «числа»). Значит инструмент такие условия в этом файле видит, и их отсутствие перед блоком 3 — реальное, а не промах грепа. +**Другие носители того же факта:** docs/scripts/githooks/pre-commit:6 (собственная шапка хука несёт то же устаревшее описание: «при коммите, задевающем ЛЮБОЙ док, проверяет file:line-якоря») +**Предложенное действие:** Заменить на: «`--lint` якорей — при ЛЮБОМ коммите с непустым индексом, включая коммиты чистого кода (гейт по составу снят 21.08 — цели якорей живут в коде, и именно кодовый коммит их двигает; шумовое следствие — строка бэклога 244); `--check` чисел и головы — только при коммите с D-логом или PROGRESS». Ту же поправку внести в шапку-комментарий `docs/scripts/githooks/pre-commit:6`. +**Разрушающее:** False + +--- + +## N084 · MINOR · `docs/README.md:31` · A-дрейф +**Срез:** Корневые онбординг-доки: CLAUDE.md · docs/README.md · docs/STACK.md +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** Отработанные промты — `archive/prompts/`, отчёты с ревью-шапками — `archive/reports/`. +**Почему протухло:** Правило не исполнено ровно для одного файла — и это тот файл, который холодная бэкенд-сессия увидит первым в `ls docs/`. `BACKEND_COLDRUN_V16_SESSION_PROMPT.md` объявлен отработавшим в этой же таблице (строка 25: «Прежний пак — холодный прогон v16 (**D39.179**): отработал…»), его отчёт уже лежит в `docs/archive/reports/COLDRUN_V16_REPORT_2026-08-31.md`, а сам промт остался в `docs/` и в `archive/prompts/` не переезжал ни разу. То есть расхождение РОВНО ОДНО и только в эту сторону: все четыре промта, названные таблицей активными, физически лежат в `docs/`, а ни один файл из `archive/prompts/` активным не объявлен. +**Улика — команда:** `ls docs/*.md; ls docs/archive/prompts/ | grep -ci COLDRUN_V16; ls docs/archive/prompts/BACKEND_MONEY_HONESTY_SESSION_PROMPT.md docs/archive/prompts/PLATFORM_P12_SESSION_PROMPT.md; git log --oneline --follow -- docs/BACKEND_COLDRUN_V16_SESSION_PROMPT.md` +**Улика — вывод:** docs/BACKEND_COLDRUN_V16_SESSION_PROMPT.md (55493 байт, Aug 31 01:28) +--- в archive/prompts его нет --- +0 +--- POSITIVE CONTROL: промты, отработавшие в тот же день 31.08, переехали --- +docs/archive/prompts/BACKEND_MONEY_HONESTY_SESSION_PROMPT.md +docs/archive/prompts/PLATFORM_P12_SESSION_PROMPT.md +--- история файла: один коммит (выдача), переезда нет --- +8e475fa Issue the cold-run prompt under the owner's sanctioned budget and repair the money doc … +**Положительный контроль:** `ls docs/archive/prompts/ | grep -i coldrun` находит ДВА других coldrun-промта (BACKEND_COLDRUN_DEBUG_SESSION_PROMPT.md, BACKEND_COLDRUN_SESSION_PROMPT.md), а промты обоих паков 31.08 лежат там же. Значит каталог грепается и переезды в него реально происходят — ноль по COLDRUN_V16 не артефакт грепа. +**Другие носители того же факта:** docs/README.md:25 — та же таблица объявляет пак v16 отработавшим, docs/archive/reports/COLDRUN_V16_REPORT_2026-08-31.md:51 — адресует промт по нынешнему пути `docs/BACKEND_COLDRUN_V16_SESSION_PROMPT.md` +**Предложенное действие:** Вынести `docs/BACKEND_COLDRUN_V16_SESSION_PROMPT.md` в `docs/archive/prompts/` ОДНИМ коммитом и тем же коммитом поправить ссылку `docs/archive/reports/COLDRUN_V16_REPORT_2026-08-31.md:51`. ⚠ Прежде чем двигать — сверить, закрыта ли развилка владельца «довести ≈$1.00 / остановиться» (README:25 говорит — НА ВЛАДЕЛЬЦЕ): если не закрыта, переезд НЕ делать, а вместо этого дописать в строке 25 явное «промт отработал, но ФИЗИЧЕСКИ ещё в `docs/` — ждёт слова владельца по развилке», чтобы холодная сессия не приняла его за свой. +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Переезд ДЕЛАТЬ — оговорка «если развилка не закрыта, не двигать» снята замером: закрыта. Одним коммитом pathspec-формой: (1) `git mv` промта в docs/archive/prompts/ — проверено, что переезд link-safe: внутри промта НОЛЬ относительных markdown-ссылок (`grep -cE '\]\(\.\.?/'` → 0, тот же ноль у уже переехавшего money-промта), все пути — репо-корневые в бэктиках, так что норма пере-углубления D39.80 здесь не срабатывает; (2) тем же коммитом починить README:25, где ДВЕ протухшие половины, а не ноль: «развилка … НА ВЛАДЕЛЬЦЕ» → «закрыта словом владельца «не доводить» 31.08, $0.436110 из $0.80 (D39.184)» и «отчёт ждёт лендинга» → «отчёт заленджен, `archive/reports/COLDRUN_V16_REPORT_2026-08-31.md`». (3) docs/PROGRESS.md:3 правки НЕ требует — переезд делает его утверждение истинным; после коммита сверить это грепом. (4) А вот предложенную находкой правку docs/archive/reports/COLDRUN_V16_REPORT_2026-08-31.md:51 я бы НЕ делал в том же движении: это ТЕЛО архивного отчёта с ревью-шапкой, и оно фиксирует, где промт лежал на момент прогона; конвенция в каталоге и так смешанная (MINIRUN_REPORT_2026-07-25.md:5 цитирует архивный путь, COLDRUN_V16 — docs/). Если оркестратор хочет живой указатель — правка допустима, но она отдельное решение, а не часть переезда. + +--- + +## N085 · MINOR · `docs/STACK.md:7` · B-мёртвый-указатель +**Срез:** Корневые онбординг-доки: CLAUDE.md · docs/README.md · docs/STACK.md +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** `configs/pipeline-c1.yaml:36-60` +**Почему протухло:** Блок стадии draft лежит на строках 60–84 (`stages:` — 59, `- name: draft` — 60, следующая стадия `- name: edit` — 85). На заявленных 36–60 сегодня блок `retries` (включая `regenerate_echo_before_escalate`, вписанный паком «деньги и честность выдачи» 31.08) и ровно одна строка `stages:`. Указатель уехал вниз на ~24 строки. ⚠ Шапка ЭТОГО ЖЕ дока (строка 3) запрещает адресовать носители номером строки именно из-за этого класса («испр. 22.08 аудитом доков: номера уехали и вели в чужие блоки») — исключение выжило в таблице под ней и уже сгнило. Линтер `counts.py --lint` этот якорь НЕ ловит — он в его классе «без токена — несверяемые: 708». +**Улика — команда:** `grep -n -E '^\s*(stages:|- name:|escalation:|gates:|chains:)' backend/configs/pipeline-c1.yaml; awk 'NR>=36&&NR<=38' backend/configs/pipeline-c1.yaml` +**Улика — вывод:** 59:stages: +60: - name: draft +61: role: translator +62: model: deepseek-v4-flash # интерим — переводчик по exp13 … +85: - name: edit +86: role: editor +111:gates: +142:escalation: +143: chains: +--- что РЕАЛЬНО на 36-38 (блок retries) --- + # • budget_usd > 0 (книга, готовая к боевому прогону …) + # самого ключа): ре-ген ЗАМЕНЯЕТ хоп … + # (`coldrun-v16`, книжный конфиг с budget_usd: 0.08) … +**Предложенное действие:** Убрать номер строки и адресовать КЛЮЧОМ, как требует шапка самого дока: «`configs/pipeline-c1.yaml`, стадия `draft`». Содержание строки (thinking ON, явный `reasoning:"low"`, модель `deepseek-v4-flash`) пере-проверено и ВЕРНО — битый только номер. +**Разрушающее:** False + +--- + +## N086 · MINOR · `docs/STACK.md:15` · B-мёртвый-указатель +**Срез:** Корневые онбординг-доки: CLAUDE.md · docs/README.md · docs/STACK.md +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** `configs/pipeline-c1.yaml:118-135` +**Почему протухло:** Блок `escalation` лежит на строках 142–159 (`escalation:` — 142, `chains:` — 143, `default:` — 153, `adult:` — 154, `budget_usd: 0` — 159). На заявленных 118–135 сегодня СОВСЕМ ДРУГИЕ гейты — `sanitizer:` (118) и `regression_guard:` (128). Указатель ведёт не в пустоту, а в ЧУЖОЙ блок, то есть даёт читателю неверный ответ — самый дорогой вид промаха и ровно тот, от которого предостерегает шапка дока (строка 3). +**Улика — команда:** `awk 'NR>=118&&NR<=129{printf "%d: %s\n",NR,$0}' backend/configs/pipeline-c1.yaml | head -4; grep -n 'escalation:\|chains:\|budget_usd: 0\|default: \[\|adult: \[' backend/configs/pipeline-c1.yaml` +**Улика — вывод:** 118: sanitizer: +119: # Output-санитайзер (D30.3): детерминированный вердикт-гейт «мгновенной +128: regression_guard: +129: # Пост-reflow регресс-гард (D38-инфра, research/18 §C#5) … +--- +142:escalation: +143: chains: +153: default: [deepseek-v4-pro, glm-5.1, gemini-3.1-pro-preview] +154: adult: [grok-4.3] +159: budget_usd: 0 +**Предложенное действие:** Адресовать ключом: «`configs/pipeline-c1.yaml`, блок `escalation` (`chains.default` / `chains.adult` / `budget_usd`)». Содержательные утверждения строки (цепочка из трёх, `budget_usd: 0` в шиппинге ⇒ эскалация не исполняется, слаг ИМЕННО `-preview`) пере-проверены и ВЕРНЫ — трогать их не надо. +**Разрушающее:** False + +--- + +## N087 · MINOR · `docs/STACK.md:16` · B-мёртвый-указатель +**Срез:** Корневые онбординг-доки: CLAUDE.md · docs/README.md · docs/STACK.md +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** кредиты xAI — лист владельца (PROGRESS строка 6) +**Почему протухло:** Указатель РОДИЛСЯ ВЕРНЫМ 09.08 (085dbb9): строка 6 `docs/PROGRESS.md` тогда читалась «**Открыто на владельце:** прогон Sol-пакетов руками (150) · кредиты xAI (арбитр grok + судья 18+) …» — то есть автор не ошибался, сгнил механизм. Сегодня строка 6 — про выданный 27.08 промт контрактного минора, блок «Открыто на владельце» уехал на строку 11 и кредитов xAI не называет вовсе; во всём живом PROGRESS.md слово «xAI» осталось только в строках бэклога 144 и 67, ни одна из которых не «лист владельца». +**Улика — команда:** `git show 085dbb9:docs/PROGRESS.md | sed -n '6p'; sed -n '6p' docs/PROGRESS.md; grep -n 'xAI' docs/PROGRESS.md; grep -n 'Открыто на владельце' docs/PROGRESS.md` +**Улика — вывод:** --- на 085dbb9 (09.08, коммит-автор указателя) строка 6 --- +> - **Открыто на владельце:** прогон Sol-пакетов руками (150) · кредиты xAI (арбитр grok + судья 18+) · Приложение А контракта (148 … +--- сегодня строка 6 --- +> - ⚠ **27.08: ВЫДАН ПРОМТ КОНТРАКТНОГО МИНОРА** (`CONTRACT_MINOR_SESSION_PROMPT.md`, пункт 2б очереди D39.156) … +--- xAI сегодня: только строки бэклога --- +134:| 144 | **Вахта: grok `reasoning_tokens` ВНЕ `completion_tokens`** … +233:| 67 | **Прод-гигиена: … xAI-ключ off перед продом** (D27) … +--- лист владельца сегодня --- +11:> - **Открыто на владельце:** … лестница D25.1 … лейбл главы без заголовка … +**Положительный контроль:** grep -n 'xAI' docs/PROGRESS.md сегодня возвращает ДВА попадания (строки 134 и 233) — инструмент слово «xAI» в этом файле находит, значит его отсутствие на строке 6 и в блоке «Открыто на владельце» (строка 11) — реальный факт, а не пустой греп. Контроль второго рода: тот же `sed -n '6p'` на историческом дереве НАХОДИТ кредиты xAI — значит и чтение строки по номеру работает. +**Предложенное действие:** Снять номер строки файла (он в PROGRESS.md нестабилен по построению — шапка пере-собирается каждым лендингом). Либо (а) заменить на «кредиты xAI — на владельце; носителя в CURRENT-STATE СЕЙЧАС НЕТ — был на 09.08, вымыт пере-сборкой шапки», либо (б) завести строку единого бэклога под кредиты xAI и ссылаться на её НОМЕР СТРОКИ БЭКЛОГА — он стабилен, и соседний указатель той же ячейки («строка 150 бэклога») пере-проверен и ЖИВ. +**Разрушающее:** False + +--- + +## N088 · MINOR · `docs/architecture/01-decisions.md:19` · A-дрейф +**Срез:** Ранние арх-доки (кандидаты в архив): docs/architecture/01-decisions.md · 02-mvp-plan.md · 04-unhappy-paths.md · 06-memory-risk-registry.md +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** План выделения пакетов: `pkg/llm` (llm.go + httpllm.go + адаптеры + failover), `pkg/ledger` (pricing + reserve/settle), `pkg/obs` (trace + logging), `pkg/store` (миграции). +**Почему протухло:** Каталога `pkg/` в бэкенде нет вовсе; план исполнен под `internal/`: internal/llm, internal/ledger, internal/obs, internal/store. Имена совпали, префикс — нет, и живые доки (PROGRESS, 13-tech-debt-anchors, 05-decisions-log, research/21) ссылаются уже на `internal/...`. Читатель, идущий по 01-decisions за адаптером, ищет несуществующий путь. +**Улика — команда:** `cd /home/ubuntu/projects/textmachine-main/backend && ls pkg/ ; ls internal/ | head -20; cd /home/ubuntu/projects/textmachine-main && grep -rln 'internal/llm' --include='*.md' docs/ | grep -v archive` +**Улика — вывод:** ls: cannot access 'pkg/': No such file or directory +archguard +bookfile +checks +chunk +config +lang +ledger +llm +membank +miner +obs +pipeline +runevents +seed +standdata +store +terminology +text +--- +docs/PROGRESS.md +docs/experiments/00-provider-quirks.md +docs/architecture/13-tech-debt-anchors.md +docs/research/21-llm-transport-survey.md +docs/architecture/05-decisions-log.md +**Положительный контроль:** Отрицательный результат («pkg/ нет») предъявлен вместе с положительным: тот же `ls` по соседнему пути печатает 18 реальных пакетов, а грепом по живым докам находятся пять файлов, называющих `internal/llm` — инструмент в этой области находит и каталоги, и упоминания. +**Другие носители того же факта:** docs/research/10-vojo-ai-bot-review.md:145 (первоисточник плана `pkg/llm`/`pkg/ledger`/`pkg/obs`/`pkg/store`; тело ресёрча по D23.3 не переписывается) +**Предложенное действие:** В строке 19 заменить `pkg/*` на `internal/*` (internal/llm · internal/ledger · internal/obs · internal/store) с пометкой «план исполнен под internal/». Ресёрч 10-vojo-ai-bot-review.md не трогать — он улика того, как план выглядел. +**Разрушающее:** False + +--- + +## N089 · MINOR · `docs/architecture/01-decisions.md:39` · A-дрейф +**Срез:** Ранние арх-доки (кандидаты в архив): docs/architecture/01-decisions.md · 02-mvp-plan.md · 04-unhappy-paths.md · 06-memory-risk-registry.md +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** Хранилище: **SQLite (один файл на книгу) + FTS5** (FTS5 — НЕ в горячем пути: unicode61 не сегментирует CJK → молча вернёт ничего). +**Почему протухло:** FTS5 в движке нет НИ на горячем, ни на холодном пути: во всём backend/ ноль виртуальных таблиц и единственное упоминание FTS5 — отрицание в комментарии горячего пути («$0, no LLM, no embeddings, no FTS5», membank/memory.go:32). Драйвер FTS5 умеет (03-implementation-notes.md:21), но индекс никогда не создавался. Р3 читается как «хранилище состоит из SQLite и FTS5», хотя вторая половина — ненаступившее. +**Улика — команда:** `cd /home/ubuntu/projects/textmachine-main/backend && grep -rniI 'fts5|fts4|CREATE VIRTUAL TABLE' -E --include='*.go' --include='*.yaml' . ; echo '--- positive control ---'; grep -rniI 'CREATE TABLE' --include='*.go' . | wc -l; grep -rniI 'PRAGMA' --include='*.go' internal/store/*.go | grep -v _test | head -3` +**Улика — вывод:** internal/membank/memory.go:32:// Contract / research/13 Q3). $0, no LLM, no embeddings, no FTS5 — a multi-pattern +--- positive control --- +19 +internal/store/backup.go:18:// IntegrityCheck runs `PRAGMA integrity_check` against the SQLite file at dbPath and returns a loud error +internal/store/backup.go:67: v.Add("_pragma", "busy_timeout(5000)") +internal/store/backup.go:69: v.Add("_pragma", "query_only(1)") +**Положительный контроль:** Тот же грепом по тем же файлам находятся 19 объявлений `CREATE TABLE` и три места с `PRAGMA` — т.е. инструмент видит DDL и SQLite-фичи в этом дереве; ноль по FTS5/VIRTUAL TABLE — это отсутствие факта, а не отсутствие поиска. +**Другие носители того же факта:** docs/architecture/06-memory-risk-registry.md:100 («Горячий путь — НЕ FTS5», согласуется с кодом), docs/architecture/03-implementation-notes.md:21 и :127 (FTS5 в драйвере есть; в горячий путь не тащить — согласуется), docs/research/05-memory-glossary.md:179 (первоисточник «SQLite + FTS5 + sqlite-vec»; тело ресёрча не переписывается) +**Предложенное действие:** Снять «+ FTS5» из формулы хранилища в Р3, оставив факт: «SQLite (один файл на книгу); FTS5 драйвер умеет, но НЕ используется — ни на горячем пути (CJK не сегментируется), ни на холодном; горячий путь — Aho-Corasick по нормализованному чанку (internal/membank/memory.go)». +**Разрушающее:** False + +--- + +## N090 · MINOR · `docs/architecture/01-decisions.md:55` · B-мёртвый-указатель +**Срез:** Ранние арх-доки (кандидаты в архив): docs/architecture/01-decisions.md · 02-mvp-plan.md · 04-unhappy-paths.md · 06-memory-risk-registry.md +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** **Решение владельца (04.07): удалён только Anthropic — за ДОРОГОВИЗНУ** (git `c7f8a95`, `85f24fb`), не за недоступность +**Почему протухло:** Обоих коммитов в истории репозитория НЕ существует (`git cat-file -t c7f8a95` → «Not a valid object name»). Реальные коммиты того же дня и того же содержания — `c4676d5` «Drop Anthropic from the model stack per owner decision» и `c504cfe` «Mark native Anthropic adapter deprecated (kept as reference for Phase-2 Gemini adapter)», оба 2026-07-04. История не усечена и не переписана: 924 коммита от 9bb00d4 (04.07), `.git/shallow` нет. Само решение верно и живо (адаптер на месте — internal/llm/provider_anthropic.go), протухла только квитанция. +**Улика — команда:** `cd /home/ubuntu/projects/textmachine-main && git cat-file -t c7f8a95; git log --all --oneline --grep='nthropic' | head -5; git log -1 --format='%h %ad %s' --date=short c4676d5; git rev-list --count HEAD; ls .git/shallow` +**Улика — вывод:** fatal: Not a valid object name c7f8a95 +210b38e Ratify D39.121: post-review self-plan ... +c504cfe Mark native Anthropic adapter deprecated (kept as reference for Phase-2 Gemini adapter); record real DeepSeek cache-hit confirmation +c4676d5 Drop Anthropic from the model stack per owner decision: remove provider and Claude models, repoint escalation and C2 judge to non-Anthropic +7954325 Add Phase 0 backend skeleton: LLM core with native Anthropic adapter ... +c4676d5 2026-07-04 Drop Anthropic from the model stack per owner decision ... +924 +ls: cannot access '.git/shallow': Not a directory +**Положительный контроль:** Та же команда на хэшах из соседних доков резолвит: `git log --oneline -1 2f91b04` → «2f91b04 Land pack-13 release QA: … percent and latin and broken-word checkers …» (цитируется шапкой 04-unhappy-paths.md:3), `git log --oneline -1 636919c` → «636919c Add per-model capability wire-shape layer (D3.1) and fold context-assembly, memory-version, and resolved capability into the job snapshot (D5.2)» (ци +**Другие носители того же факта:** docs/architecture/01-decisions.md:19 (второе вхождение `c7f8a95` в том же доке, Р1), docs/research/11-gap-2.md:7 (⚠-шапка ресёрча: «Anthropic выброшен из стека целиком (Р4/D3, `c7f8a95`)» — тело ресёрча по D23.3 не переписывается), docs/archive/research/11-gap-1.md:12 (архив, под ⚠-баннером) +**Предложенное действие:** В 01-decisions.md заменить обе цитаты на реальные: строка 19 — git `c4676d5` (выброс) / `c504cfe` (пометка DEPRECATED); строка 55 — git `c4676d5`, `c504cfe`. Живой носитель ресёрча 11-gap-2.md:7 не переписывать (тело — улика), но при следующем касании его шапки — там же. +**Разрушающее:** False + +--- + +## N091 · MINOR · `docs/architecture/01-decisions.md:93` · A-дрейф +**Срез:** Ранние арх-доки (кандидаты в архив): docs/architecture/01-decisions.md · 02-mvp-plan.md · 04-unhappy-paths.md · 06-memory-risk-registry.md +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** Локальный режим MVP: SQLite (modernc.org/sqlite, CGO-free, без нативных расширений — см. Р3) для проектных данных; отдельный файл БД на книгу для банка памяти. +**Почему протухло:** Отдельного файла БД под банк памяти НЕТ и не было построено: у книги ровно ОДНА база `.db` (config/book.go:189), и банк памяти живёт в ней же — один и тот же мигратор internal/store/migrate.go создаёт и jobs/checkpoints (проектные данные), и glossary/glossary_aliases/glossary_revisions/retrieval_state/ruby_readings (банк). Соседний живой док читает раскладку правильно — 03-implementation-notes.md:234 говорит «раскладка «файл БД на книгу» из Р3/Р6», т.е. ОДИН файл; расходится только формулировка Р6. +**Улика — команда:** `cd /home/ubuntu/projects/textmachine-main/backend && grep -n 'b.ProjectDB = filepath.Join' internal/config/book.go && grep -n 'CREATE TABLE IF NOT EXISTS \(jobs\|checkpoints\|glossary\|glossary_aliases\|retrieval_state\|ruby_readings\)' internal/store/migrate.go && grep -rn 'sql.Open("sqlite"' --include='*.go' . | grep -v _test.go` +**Улика — вывод:** 189: b.ProjectDB = filepath.Join(dir, b.BookID+".db") +29: CREATE TABLE IF NOT EXISTS jobs ( +46: CREATE TABLE IF NOT EXISTS checkpoints ( +154: CREATE TABLE IF NOT EXISTS ruby_readings ( +177: CREATE TABLE IF NOT EXISTS glossary ( +207: CREATE TABLE IF NOT EXISTS glossary_aliases ( +223: CREATE TABLE IF NOT EXISTS glossary_revisions ( +239: CREATE TABLE IF NOT EXISTS retrieval_state ( +--- +internal/store/backup.go:71: db, err := sql.Open("sqlite", "file:"+dbPath+...) +internal/store/store.go:80/89/142 (Open writer, Open reader, OpenReadOnly) +**Положительный контроль:** Тот же грепом ищутся ВСЕ открыватели БД в дереве и находятся четыре — все они открывают ОДИН путь (store.Open/OpenReadOnly + бэкап того же файла). Т.е. грепу есть что находить: если бы второй файл существовал, его открыватель попал бы в этот же список. +**Другие носители того же факта:** docs/architecture/01-decisions.md:39 (Р3 «SQLite (один файл на книгу)» — эта формулировка ВЕРНА и коду соответствует), docs/architecture/03-implementation-notes.md:234 («раскладка «файл БД на книгу» из Р3/Р6» — верно) +**Предложенное действие:** Заменить в Р6 «отдельный файл БД на книгу для банка памяти» на «ОДИН файл `.db` на книгу — и проектные данные, и банк памяти в нём (internal/store/migrate.go)», чтобы Р6 перестал расходиться с Р3 и с 03-implementation-notes.md:234. +**Разрушающее:** False + +--- + +## N092 · MINOR · `docs/architecture/03-implementation-notes.md:212` · B-мёртвый-указатель +**Срез:** Заметки реализации и Go-нормы — docs/architecture/03-implementation-notes.md · docs/architecture/12-go-style-notes.md +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** Подтверждённые исправлены **двумя волнами** (коммиты `dc6ea8e` → `a32ec2e` → фиксы v2) +**Почему протухло:** Ни `dc6ea8e`, ни `a32ec2e` в репозитории не существуют — `git cat-file -t` отвечает «Not a valid object name» на оба, при том что тот же вызов на реальных хэшах возвращает `commit`. Реальные коммиты двух волн селфревью Фазы 0 — `e861541` (волна 1: shared retry engine, billed-decode settle, empty-completion fail-loud, snapshot pinning с `--resnapshot`, process file lock, local model pricing, single-pass render, attempt hash dimension — дословно перечень §6 волны 1) и `214e005` (волна 2: snapshot coverage, response-price fallback, usage clamp, job-status lifecycle, config guards, source normalization, hash robustness — дословно перечень волны 2). Строка вдобавок противоречит себе: здесь порядок `dc6ea8e → a32ec2e`, а ниже «Волна 1 (коммит a32ec2e)». +**Улика — команда:** `cd /home/ubuntu/projects/textmachine-main && for c in dc6ea8e a32ec2e; do echo -n "$c: "; git cat-file -t $c 2>&1|head -1; done; echo POSCTRL; git cat-file -t 7954325; git log --oneline | grep -iE 'self-review|review fixes|Harden Phase 0'` +**Улика — вывод:** dc6ea8e: fatal: Not a valid object name dc6ea8e +a32ec2e: fatal: Not a valid object name a32ec2e +POSCTRL +commit +9d78385 Apply external review fixes: snapshot local overrides, gate-enabled guard, TM-key doc note, db-lock gitignore, techdebt disposition +214e005 Harden Phase 0 after self-review: snapshot coverage, response-price fallback, usage clamp, job-status lifecycle, config guards, source normalization, hash robustness +e861541 Fix review findings: shared retry engine, billed-decode settle, empty-completion fail-loud, snapshot pinning with resnapshot flag, process file lock, local model pricing, single-pass render, attempt hash dimension +**Положительный контроль:** git cat-file -t 7954325 → commit (тот же инструмент на реальном коротком хэше отвечает; значит пустой ответ на dc6ea8e/a32ec2e — отсутствие объекта, а не поломка команды) +**Предложенное действие:** Заменить в строке 212 `dc6ea8e` → `e861541`, `a32ec2e` → `214e005`; ниже в подзаголовке «Волна 1 (коммит a32ec2e)» → «Волна 1 (коммит `e861541`)», «Волна 2 (эта)» → «Волна 2 (коммит `214e005`)». Внешнее ревью §6 приземлено `9d78385`/`56670e3` — можно добавить туда же. +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Правка нужна, но НЕ той подстановкой, что предложена: находка в одной строке отображает a32ec2e→214e005, а строкой ниже тот же токен a32ec2e→e861541 — это несогласованно и спорит с архивным носителем. Верное отображение по СОДЕРЖАНИЮ коммитов: dc6ea8e = каркас `7954325` («Add Phase 0 backend skeleton…»), a32ec2e = волна 1 `e861541` (subject дословно повторяет перечень §6 волны 1), волна 2 = `214e005` (blame: этим коммитом строка и написана, его же подзаголовок «Волна 2 (эта)»). Внешнее ревью — `56670e3`/`9d78385`. Архивную копию `docs/archive/PROGRESS-2026-07-04-10.md:164-165` НЕ править (замороженная хроника); если её мёртвые хэши мешают — только пометка, не переписывание. Рекомендую в §6 добавить полсловом «(исходные хэши в отчёте были указаны ошибочно; в истории их нет)», чтобы следующий ревизор не искал переписанную историю. + +--- + +## N093 · MINOR · `docs/architecture/03-implementation-notes.md:267` · C-исполненное-как-открытое +**Срез:** Заметки реализации и Go-нормы — docs/architecture/03-implementation-notes.md · docs/architecture/12-go-style-notes.md +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** **D3** — изоляция канала B в failover не принуждается кодом (пара primary/fallback задаётся вызывающим); при подключении failover (Фаза 2) нужен структурный запрет Anthropic в канале B, иначе пустой ответ local-abliterated молча уйдёт в Anthropic. Все три — Фаза 1–2, не блокеры Ф0. +**Почему протухло:** Запрошенный «структурный запрет» построен и ратифицирован дважды: (1) на загрузке — `config/pipeline.go:731` отказывает конфигу, где модель провайдера без нужного `accepts_labels` резолвится под лейблом книги; (2) на вызове — `Runner.clientFor` физически не выдаёт клиента без заявленного набора лейблов, и комментарий называет это «the compile-enforced half of the routing guarantee, D39.26 point 8». Вдобавок сам объект запрета отсутствует: Anthropic убран из стека решением владельца, в `configs/models.yaml` провайдера `anthropic` и моделей claude-* нет. Пункт читается как открытая задача Фазы 2, тогда как он закрыт паком-17 (D39.25/D39.26). +**Улика — команда:** `grep -n 'may not receive content label' backend/internal/config/pipeline.go; grep -n 'refusing to send content labelled\|compile-enforced half' backend/internal/pipeline/runner.go; grep -in 'provider: anthropic\|claude-' backend/configs/models.yaml; grep -n 'provider:' backend/configs/models.yaml | awk -F': ' '{print $3}' | sort -u` +**Улика — вывод:** backend/internal/config/pipeline.go:731: contentBad("model %q (provider %s) may not receive content label(s) %v — this book declares %v...") +backend/internal/pipeline/runner.go:573:// cannot reach a provider without stating what it is sending (the compile-enforced half of the routing guarantee, D39.26 point 8). +backend/internal/pipeline/runner.go:578: return nil, fmt.Errorf("pipeline: refusing to send content labelled %v to model %q (provider %s) — it does not accept %v ... (D39.25)") +[grep -in 'provider: anthropic|claude-' configs/models.yaml → ни одной строки объявления; только комментарии 104-108/325: «Anthropic убран из стека по решению владельца (04.07)», «claude-sonnet-5 / claude-op +**Положительный контроль:** grep -n 'provider:' backend/configs/models.yaml | awk -F': ' '{print $3}' | sort -u → deepseek, gemini, kimi, local, mistral, openai, xai, zai (восемь провайдеров реально находятся тем же грепом; пустота по `anthropic` — отсутствие провайдера, не поломка команды) +**Предложенное действие:** Дописать в конец пункта: «⚠ ЗАКРЫТО паком-17 (D39.25/D39.26): маршрутизация по лейблам запрещает выдачу клиента структурно — отказ на загрузке (`config/pipeline.go:731`) и на вызове (`pipeline.clientFor`, `runner.go:573-578`); Anthropic из стека убран 04.07 (`configs/models.yaml`), объект запрета отсутствует». Тот же абзац: D2 (per-chunk skip+flag) тоже исполнен — дисп-классификатор `DispOK`/`FlagReason` + exit 2 «completed-with-flags». +**Разрушающее:** False + +--- + +## N094 · MINOR · `docs/architecture/04-unhappy-paths.md:120` · C-исполненное-как-открытое +**Срез:** Ранние арх-доки (кандидаты в архив): docs/architecture/01-decisions.md · 02-mvp-plan.md · 04-unhappy-paths.md · 06-memory-risk-registry.md +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** равномерные гейты по всем главам + **«паспорт качества главы»** в отчёте — продуктовая фича доверия. → в бэклог продукта. +**Почему протухло:** Паспорт качества главы ПОСТРОЕН и отдаётся `tmctl status`: тип `pipeline.ChapterPassport` (internal/pipeline/status.go:157-159, «the per-chapter quality passport (D12): unit counts, the worst flag reason, a pass|attention|fail verdict (exp07 chapter rule) and the chapter's spend»), поле `Chapters []ChapterPassport` в отчёте (status.go:313), CLI-дашборд описан в main.go:389. §10 отправляет его «в бэклог продукта», хотя это уже поставленный механизм. Ни один из двух ⚠-баннеров дока это не покрывает: они оговаривают колонки «Фаза/Статус» ТАБЛИЦ, а §10 — прозаические буллеты без колонок. +**Улика — команда:** `cd /home/ubuntu/projects/textmachine-main && sed -n '120p' docs/architecture/04-unhappy-paths.md | cut -c1-160 && sed -n '157,159p' backend/internal/pipeline/status.go && grep -n 'per-chapter quality passports' backend/cmd/tmctl/main.go && grep -n 'Chapters \[\]ChapterPassport' backend/internal/pipeline/status.go && grep -n 'паспорта глав' backend/README.md | cut -c1-90` +**Улика — вывод:** - **«Проклятие 300-й главы»** … + **«паспорт качества главы»** в отчёте — продуктовая фича доверия. → в бэклог продукта. +157:// ChapterPassport is the per-chapter quality passport (D12): unit counts, the worst flag +158:// reason, a pass|attention|fail verdict (exp07 chapter rule) and the chapter's spend. +159:type ChapterPassport struct { +389:// dashboard (unit counts, per-chapter quality passports, money, secondary ETA). BOTH modes exit 2 +313: Chapters []ChapterPassport `json:"chapters"` +11:| `cmd/tmctl` | CLI: `translate` / `status` (read-only N/M+паспорта глав+деньги, `--json` … +**Другие носители того же факта:** docs/architecture/02-mvp-plan.md:34 (тот же факт как ПОСТРОЕННЫЙ: «`tmctl export` … паспорт-rollup» в составе D29.1) — то есть два ранних дока уже расходятся в статусе одного механизма +**Предложенное действие:** В §10 заменить «→ в бэклог продукта» на «→ ПОСТРОЕНО: `pipeline.ChapterPassport` (status.go:157), поверхность `tmctl status --json` + rollup в `tmctl export` (D29.1)». Строку бэклога не заводить — предмет закрыт. +**Разрушающее:** False + +--- + +## N095 · MINOR · `docs/architecture/09-target-architecture.md:12` · A-дрейф +**Срез:** Целевая архитектура — docs/architecture/09-target-architecture.md +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** ⚠ **С 29–30.08 шапка ниже НЕ знает ещё двух построенных механизмов, и до их внесения в таблицу слоёв читать её как полную нельзя:** объёмный потолок оплаченной работы в движке (`--max-units`, `VolumeStop`, `backend/internal/pipeline/volume.go` — D39.170 …) и гейт схемы движка (`backend/internal/store/sqlgate_test.go` — D39.173 +**Почему протухло:** Механизмов, которых таблица не знает, уже не два: 31.08 (после последней правки дока) залендён пак D39.181/182 «закон раскрытия», добавивший в шов ДВА новых поля контракта — леджер объёмного стопа в кадре `finished` (`runevents.VolumeLedger`) и `config_drift_basis` в `status --json` (`pipeline.ConfigDriftBasis`). Точное числительное «двух» превращает предупреждение в ложную гарантию полноты остального. +**Улика — команда:** `git log -1 --format='doc last edit: %h %ad' -- docs/architecture/09-target-architecture.md; git log -1 --format='landed: %h %ad' -S 'ConfigDriftBasis' -- backend/; grep -n 'Volume \*VolumeLedger' backend/internal/runevents/runevents.go; grep -n 'ConfigDriftBasis == pipeline' backend/cmd/tmctl/render.go` +**Улика — вывод:** doc last edit: 69ef3e9 Sun Aug 30 20:20:02 2026 +0300 +landed: bb541a8 Mon Aug 31 21:03:04 2026 +0300 +206: Volume *VolumeLedger `json:"volume,omitempty"` +727: if rep.ConfigDriftBasis == pipeline.DriftBasisUnknown { +**Предложенное действие:** Снять числительное: «шапка ниже НЕ знает ряда построенных механизмов» + дописать третьим пунктом закон раскрытия движка (D39.181) и два его контрактных поля (`finished.volume`, `config_drift_basis`, лендинг `bb541a8`). Лучшее лечение — внести объёмный потолок, гейт схемы и закон раскрытия В САМУ таблицу слоёв и снять оговорку целиком. +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие верно; предпочтительна названная автором «лучшая» ветка — внести объёмный потолок, гейт схемы и закон раскрытия В САМУ таблицу слоёв и снять оговорку целиком, иначе числительное будет протухать при каждом следующем лендинге. Если правится только текст — не заменять «двух» на «трёх» (то же протухнет), а писать «ряда» + ссылку на реестр нот. + +--- + +## N096 · MINOR · `docs/architecture/09-target-architecture.md:178` · B-мёртвый-указатель +**Срез:** Целевая архитектура — docs/architecture/09-target-architecture.md +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** | 5 Качество | `H5-no-in-loop-quality-signal`, `H14-inversion-guard` (D37 §2г), `H12-cross-boundary-coherence-untested` | +**Почему протухло:** Токен `H14-inversion-guard` в ледджере, на который §3 явно ссылается заголовком раздела, не встречается ни разу: реальный идентификатор — `H14-D38.4-tail-inversion-guard`. Из восемнадцати проверенных ID таблицы §3 это ЕДИНСТВЕННЫЙ, который не грепается; остальные семнадцать резолвятся точно. +**Улика — команда:** `grep -c 'H14-inversion-guard' docs/archive/architecture/08-sync-audit-ledger.md; grep -n '^### `H14' docs/archive/architecture/08-sync-audit-ledger.md | cut -c1-110; grep -c 'H12-cross-boundary-coherence-untested' docs/archive/architecture/08-sync-audit-ledger.md` +**Улика — вывод:** 0 +459:### `H14-D38.4-tail-inversion-guard` — **WORKAROUND_NOT_ROOT** / MEDIUM · верификатор: CONF +1 +**Положительный контроль:** grep -c 'H12-cross-boundary-coherence-untested' → 1 в том же файле: греп по ледджеру работает и соседние ID из той же строки таблицы находит; ноль по H14-inversion-guard — отсутствие цели, не поломка инструмента. +**Предложенное действие:** Заменить `H14-inversion-guard` на `H14-D38.4-tail-inversion-guard` (точный заголовок ледджера, строка 459). +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие верно и минимально-разрушительно (замена одного токена). Уточнение к сопроводительной записи: писать не «цель переименована», а «ссылка была битой с рождения (оба написания пришли коммитом 225abc0)» — иначе следующая сессия пойдёт искать несуществующее переименование. + +--- + +## N097 · MINOR · `docs/architecture/10-prompt-architecture.md:8` · B-мёртвый-указатель +**Срез:** Архитектура промптов — docs/architecture/10-prompt-architecture.md +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** арх-ресета была размазана по `08-sync-audit-ledger` (дорожка L4), `09-target-architecture` (§0.1, §2 слой 2), D-логу (D30.2, D39 п.7, D39.1–39.2) и research/18–19. +**Почему протухло:** `08-sync-audit-ledger.md` вынесен в `docs/archive/architecture/` 25.07 как отработанный (санкция владельца, `docs/archive/README.md:5`), и рядом с `09-target-architecture` он больше не лежит. Док зовёт его голым именем-соседом, а колонка «Где живёт» трижды адресует его находки по идентификаторам (`L4-prompt-language-always-russian` — строка 16, `L1-*`/`H3` — строка 18, `H2` — строка 19) без пути, так что читателю не за что зацепиться. Соседний живой док уже исправлен: 09-target-architecture ссылается через `../archive/architecture/08-sync-audit-ledger.md` (строки 5 и 170). +**Улика — команда:** `find -L . -name '08-sync-audit*' -not -path '*/node_modules/*'; grep -n '08-sync-audit' docs/architecture/09-target-architecture.md; grep -rn 'L4-prompt-language-always-russian' --include='*.md' . | cut -c1-120` +**Улика — вывод:** find -L → ./docs/archive/architecture/08-sync-audit-ledger.md (единственный экземпляр; в docs/architecture/ его нет) +09-target-architecture.md:5: (`../archive/architecture/08-sync-audit-ledger.md`, 65 находок, адверсариально верифицированы; архивирован 25.07 как отработанный) +09-target-architecture.md:170: ## 3. Карта находок → слои (полный ледджер — `../archive/architecture/08-sync-audit-ledger.md`) +grep L4-prompt-language-always-russian → docs/archive/architecture/08-sync-audit-ledger.md:209 · docs/architecture/09-target-architecture.md:175 · docs/architecture/10-prompt-architecture.md:16 +**Положительный контроль:** Отрицательный вывод («в docs/architecture/ файла нет») подстрахован: find -L docs -name '08*' находит три других 08-дока (docs/experiments/08-cost-model-v2.md, docs/research/08-legal.md, docs/archive/architecture/08-sync-audit-ledger.md) — инструмент в этой области файлы видит, симлинки раскрывает, ноль по нужному пути содержателен. +**Другие носители того же факта:** docs/architecture/09-target-architecture.md:175 — цитирует те же аудит-идентификаторы (`L4-prompt-language-always-russian`, `H2`), но путь до ледджера у него верный (строки 5 и 170), поэтому носитель здоров и служит образцом правки +**Предложенное действие:** В строке 8 заменить `08-sync-audit-ledger` на `../archive/architecture/08-sync-audit-ledger.md` (архивирован 25.07 как отработанный) и один раз в §1 — при первом идентификаторе (строка 16) — дать тот же путь, чтобы `L1-*`/`H3`/`H2` резолвились одним хопом. +**Разрушающее:** False + +--- + +## N098 · MINOR · `docs/architecture/10-prompt-architecture.md:17` · A-дрейф +**Срез:** Архитектура промптов — docs/architecture/10-prompt-architecture.md +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** СЕАМ ПОСТРОЕН (пак-1, D39.2): конфиг pair-keyed `prompts: {zh-ru: …}` + `Book.LangPair()` + fail-loud на чужую пару. +**Почему протухло:** Механизм, названный здесь как построенный, СНЯТ паком-15: пар-ключевая карта `prompts:` в конфиге сегодня не просто не используется — она отвергается загрузчиком как миграционная ошибка («the pair-keyed `prompts:` map is retired»), промпт резолвится конвенцией `//.md`, а калибровка пары уехала в `configs/pairs/.yaml`. Шапка дока (строка 5) объявляет конвенционный резолв построенным, но §1 продолжает называть носителем сеама именно ретайрнутую карту, и читатель уносит форму конфига, которая сегодня уронит загрузку. `Book.LangPair()` и fail-loud — проверены, живы. +**Улика — команда:** `sed -n '938,940p' backend/internal/config/pipeline.go; grep -n 'func .*LangPair' backend/internal/config/book.go; grep -rn '^\s*prompts:' backend/configs/*.yaml` +**Улика — вывод:** pipeline.go:938-940: if len(st.LegacyPrompts) > 0 { + bad("stage %q: the pair-keyed `prompts:` map is retired — a stage's prompt is resolved by convention as //.md (pack-15), so a pair is a DIRECTORY, not a config entry. Drop the map (put the pair calibration in configs/pairs/.yaml); set `prompt_override:` only if this stage deliberately runs a variant", st.Name) +book.go:378: func (b *Book) LangPair() string { +grep '^ *prompts:' backend/configs/*.yaml → пусто (ни один боевой конфиг карту не несёт) +**Положительный контроль:** Пустой грep `^\s*prompts:` по backend/configs/*.yaml подстрахован положительным контролем в том же наборе файлов: grep -rn 'prompt_override' backend/configs/*.yaml → configs/pipeline-c1.yaml:67, configs/pipeline-c2.yaml:42, configs/pipeline-c2.yaml:43 — грep по этим yaml находит промпт-ключи, значит пустота по `prompts:` содержательна. +**Предложенное действие:** В ячейке §1 заменить «конфиг pair-keyed `prompts: {zh-ru: …}`» на «пар-каталог `prompts/<пара>/<роль>.md` (конвенционный резолв, пак-15/D39.23; карта `prompts:` в конфиге РЕТАЙРНУТА и падает громко — `internal/config/pipeline.go:939`) + калибровка пары `configs/pairs/<пара>.yaml`», сохранив «`Book.LangPair()` + fail-loud на чужую пару» — обе половины проверены и живы. +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Находка верна фактически, но действие СЛИШКОМ КРУПНОЕ, и это важно: шапка того же дока (строка 5, ⚠ СТАТУС-АПДЕЙТ 25.07) УЖЕ несёт правку дословно — «Pair-keyed промпты → конвенционный резолв `prompts/<пара>/<роль>.md` + конфиг-слой пары `configs/pairs/` (пак-15, D39.23)», — а по норме «баннер прежде содержимого» читатель её видит первой. Полный перепис ячейки создаёт ТРЕТИЙ носитель того же факта в одном доке (шапка + §1 + §4 п.2), а это ровно тот механизм, из которого рождается следующее расхождение. Минимальная правка: пометить в ячейке форму как историю пака-1 и сослаться на шапку — в стиле, который док уже применяет к §2 («⚠ Пути таблицы устарели механически…»), например «(форма пака-1; РЕТАЙРНУТА паком-15 — см. шапку)». Файл-лайн `pipeline.go:939` в док не вносить: анкер сгниёт; при нужде цитировать текст ошибки «the pair-keyed `prompts:` map is retired». + +--- + +## N099 · MINOR · `docs/architecture/10-prompt-architecture.md:18` · B-мёртвый-указатель +**Срез:** Архитектура промптов — docs/architecture/10-prompt-architecture.md +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** | D-лог D30.2 (`05:366` — «механизм реверстки — открытый вопрос») · аудит `L1-*`/`H3` · research/19 §C · exp15 Q4 | +**Почему протухло:** Строка 366 живого D-лога сегодня пустая и лежит внутри эры D39.135 (приёмка фронта S4, 15.08) — то есть на 250 строк мимо цели и в чужой эре. Цитируемый текст живёт на 05:116, в теле D30 п.2. Указатель дрейфанул от роста файла (1823 строки), автор не ошибался. +**Улика — команда:** `sed -n '366p' docs/architecture/05-decisions-log.md; grep -n 'реверстк' docs/architecture/05-decisions-log.md; grep -n '^## D30\.' docs/architecture/05-decisions-log.md | head -1; grep -n '^## D30\b' docs/architecture/05-decisions-log.md` +**Улика — вывод:** sed 366p → (пустая строка; ближайший заголовок выше — 05:365 «## D39.135 — ПРИЁМКА ФРОНТА S4 ПРИНЯТА И ЗАЛЕНДЕНА (267aa35) … (15.08)») +grep реверстк → 116: 2. **Reflow-вёрстка:** снять «Сохраняй разбивку на абзацы» из zh→ru промптов (translator.md:9, editor.md:8 — дословно подтверждено) … Механизм реверстки (в промпте редактора vs отдельный пасс) — открытый вопрос пере-прогона +grep '^## D30' → 111: ## D30. Приёмка exp12 + флип D1 (моно→билингв) … +**Положительный контроль:** Отрицательный результат («на 366 цели нет») предъявлен вместе с положительным: тот же grep по отличительному токену «реверстк» в ТОМ ЖЕ файле даёт хит на строке 116 — инструмент в этой области находит искомое, пустота на 366 не артефакт грепа. +**Предложенное действие:** Заменить `05:366` на `05:116` (тело D30, п.2). Ещё устойчивее — снять номер строки совсем и оставить «D-лог D30 п.2», как предписывает дисциплина чтения журнала (номер грепается: `grep -n '^## D30' `): подномер D30.2 собственного тела не имеет, а номер строки будет дрейфовать и дальше. +**Разрушающее:** False + +--- + +## N100 · MINOR · `docs/architecture/10-prompt-architecture.md:33` · A-дрейф +**Срез:** Архитектура промптов — docs/architecture/10-prompt-architecture.md +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** Правила, сидящие В ТЕЛЕ промтов (кандидаты на извлечение в пакет пары после exp15): русское тире-оформление диалога; «пиши живым русским»; паратаксис→гипотаксис-блок; чэнъюй-политика; few-shot-примеры (структурные, по-русски). +**Почему протухло:** Инвентарь пар-специфики в телах промтов неполон с 24.07: паком-13 в ОБА боевых промпта вписан блок «Единицы и приёмы (общие для zh→ru)» с зашитыми китайскими токенами и конверсионными таблицами (时辰 = 2 часа, 六成 = 60%, стихи — по смыслу), а 01.08 в editor.md добавлено правило авторского рефрена. Это самая явно данно-образная пар-специфика из всех перечисленных (числовые таблицы конверсии), и она уже имеет носителя-данные на детекторной стороне — langpack `configs/langpacks/zh-ru/dc-checkers.txt` — и отдельные repair-промпты `repair/dc1_time_units.md` / `repair/dc1_fractional.md`. В списке кандидатов на извлечение её нет, значит план извлечения недосчитывает свой главный лот. +**Улика — команда:** `sed -n '12,15p' backend/prompts/zh-ru/translator.md; sed -n '10p' backend/prompts/zh-ru/editor.md; git log -S'时辰' --format='%h %ad %s' --date=short -- backend/prompts/zh-ru/translator.md backend/prompts/translator.md; grep -rln '时辰' backend/configs backend/prompts` +**Улика — вывод:** translator.md:12-15: Единицы и приёмы (общие для zh→ru): +- МЕРЫ переводи в привычные читателю единицы: китайский 时辰 = 2 часа (三个时辰 ≈ шесть часов, 半个时辰 ≈ час) … Доля 成 — это десятые (六成 = 60%, 六成六 = 66%), а НЕ десятичная дробь «6,6». +- СТИХИ и названия классики передавай ПО СМЫСЛУ … +editor.md:10: - Единицы и приёмы (общие для zh→ru): МЕРЫ приводи к привычным читателю единицам (时辰 = 2 часа …); СТИХИ и названия классики — по смыслу … +git log -S时辰 → 2f91b04 2026-07-24 Land pack-13 release QA … +grep -rln 时辰 → backend/configs/langpacks/zh-ru/dc-checkers.txt, backend/prompts/zh-ru/translator.md, backend/prompts/zh-ru/editor.md, backend/prompts/zh-ru/translator-banknote.md, backend/prompts/zh-ru/rep +**Другие носители того же факта:** docs/architecture/09-target-architecture.md:73-76 — описывает содержимое тех же двух промптов («подстрочник + слабая однострочка про вёрстку» / «4 мандатами сразу — верность, стиль, глоссарий, дискурс-reflow») и блок единиц/мер тоже не знает: тот же пробел в живом арх-доке +**Предложенное действие:** Дописать в список (аддитивно, без сноса существующих пунктов): «блок «Единицы и приёмы (общие для zh→ru)» — конверсии мер 时辰/成 и правило «стихи по смыслу», сидит В ОБОИХ боевых промптах (`translator.md:12-15`, `editor.md:10`, пак-13 от 24.07); у детекторной половины этого класса носитель-данные УЖЕ есть — `configs/langpacks/zh-ru/dc-checkers.txt` + `prompts/zh-ru/repair/dc1_time_units.md`/`dc1_fractional.md`, что делает его первым кандидатом на извлечение; авторский рефрен (`editor.md:6`, 01.08)». +**Разрушающее:** False + +--- + +## N101 · MINOR · `docs/architecture/10-prompt-architecture.md:41` · C-исполненное-как-открытое +**Срез:** Архитектура промптов — docs/architecture/10-prompt-architecture.md +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** Гейты на стройку: exp15 (какие правила реально несут) + research/18 §D-3 (форма пакета). +**Почему протухло:** exp15 как гейт ПАЛ: эксперимент исполнен и залендён 25.07 в редакции REV.2 (D39.7), потрачено $12.79. §4 п.2 того же дока это уже фиксирует («exp15 ЗАВЕРШЁН») и называет новую форсинг-функцию — ja→ru-реплику; §1 (строка 17) и §3 (строка 41) остались с прежней рамкой и держат построенный гейт как несданный. Док спорит сам с собой в трёх строках. +**Улика — команда:** `sed -n '6,8p' docs/experiments/15-segmentation-empirics.md; sed -n '17p;41p;45p' docs/architecture/10-prompt-architecture.md | cut -c1-190` +**Улика — вывод:** exp15:6-8: > **СТАТУС (перевёрнут полигоном, пакет-5, 25.07.2026): ЭКСПЕРИМЕНТ ИСПОЛНЕН И ЗАЛЕНДЁН в редакции REV.2 (D39.7).** Прежний статус-блок … описывал состояние ДО прогона и завис — снят. Факт: прогон состоялся, потрачено **$12.79 из капа $15** +doc10:17: … извлечение в структурированный пакет гейтится exp15 + research/18 §D-3 («форма пакета не разрешена») +doc10:41: Гейты на стройку: exp15 (какие правила реально несут) + research/18 §D-3 (форма пакета). +doc10:45: 2. **Извлечение правил в пакет** — exp15 ЗАВЕРШЁН; **данные-часть майнера ИЗВЛЕЧЕНА (D39.16 … +**Предложенное действие:** В §3 и в ячейке §1 (строка 17) снять exp15 из списка невзятых гейтов и оставить единственный живой: «Гейт на стройку — research/18 §D-3 (форма пакета не разрешена); exp15 отработан 25.07 (D39.7), форсинг извлечения сегодня — ja→ru-реплика (§4 п.2)». Тем же касанием привести строку 33 («кандидаты на извлечение ПОСЛЕ exp15») к той же рамке. +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие верное, добавить один якорь для принимающего: §D-3 — это `docs/research/18-quality-levers.md:273` (в репо два файла `18-*`: `18-quality-levers.md` и `18-quality-levers-chatgpt.md`; §D живёт в первом, во втором такой секции нет). Правку строки 33 автор назвал сам, она подтверждена (строка существует дословно). + +--- + +## N102 · MINOR · `docs/architecture/12-go-style-notes.md:8` · C-исполненное-как-открытое +**Срез:** Заметки реализации и Go-нормы — docs/architecture/03-implementation-notes.md · docs/architecture/12-go-style-notes.md +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** **Пар-скоупнутые модули** (checkers_zh_ru и т.п.) легитимны как код за pair-гейтом, но их ДАННЫЕ (словники, руны, таблицы) подлежат выносу в langpack. +**Почему протухло:** Норма как норма жива, но её единственный пример — мёртвый указатель, и он указывает ровно в обратную сторону. `backend/internal/pipeline/checkers_zh_ru.go` (295 строк) УДАЛЁН коммитом `952469b` 24.07.2026 — в тот же день, которым датирован сам норматив; данные ушли в `configs/langpacks/zh-ru/dc-checkers.txt`, а чекеры стали пар-агностичными. То есть `checkers_zh_ru` — эталон ЗАВЕРШЁННОГО выноса, а док предъявляет его как открытый долг. Тот же класс указателя уже вычищали из кода: ресёрч 31.07 нашёл 6 живых ссылок на удалённый файл и назвал их вредными («рекламируют пар-модуль там, где код уже generic»), 02.08 их сняли («grep пуст») — а седьмая, докова, осталась. +**Улика — команда:** `cd /home/ubuntu/projects/textmachine-main && git show --stat --format='%h %ad' --date=short 952469b -- '*checkers_zh_ru*' | cat; git ls-tree -r --name-only HEAD | grep checkers_zh_ru; wc -l backend/configs/langpacks/zh-ru/dc-checkers.txt` +**Улика — вывод:** 952469b 2026-07-24 + backend/internal/pipeline/checkers_zh_ru.go | 295 ----------------------- + backend/internal/pipeline/checkers_zh_ru_test.go | 25 +- + 2 files changed, 19 insertions(+), 301 deletions(-) + +git ls-tree HEAD | grep checkers_zh_ru → backend/internal/checks/checkers_zh_ru_test.go (ТОЛЬКО тест-файл) +80 backend/configs/langpacks/zh-ru/dc-checkers.txt + +docs/archive/reports/GENERALITY_RESEARCH_2026-07-31.md:144: «6 живых указателей на checkers_zh_ru.go — файла нет с 952469b (24.07, 7 дней назад…). Вредны именно для этой инвентаризации: рекламируют пар-модуль там, где код уже generic» +docs/archive/reports/SMALLPACK_TAILS_2026-08-02.md:65: «6 указателей на checkers_zh_ru.go | с +**Положительный контроль:** git ls-tree -r --name-only HEAD | grep checkers_zh_ru → backend/internal/checks/checkers_zh_ru_test.go (греп по этому токену в дереве HEAD что-то находит; значит отсутствие `.go`-файла — факт, а не опечатка в шаблоне) +**Предложенное действие:** Заменить пример на живой: «**Пар-скоупнутые модули** легитимны как код за pair-гейтом, но их ДАННЫЕ (словники, руны, таблицы) подлежат выносу в langpack. ⚠ Прежний пример `checkers_zh_ru.go` снят: файл удалён `952469b` (24.07), детект-паттерны живут в `configs/langpacks/zh-ru/dc-checkers.txt`, чекеры пар-агностичны. Живой остаток долга — строка 79 бэклога (остаточные Cyrillic-хардкоды пар-гейтед DC-чекеров).» +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Правку делать, но без двух ошибок предложенного текста. (1) Не оформлять как «исполненное как открытое» — норма в силе; менять только пример: назвать `checkers_zh_ru.go` СНЯТЫМ образцом завершённого выноса (удалён `952469b` 24.07, данные — `configs/langpacks/zh-ru/dc-checkers.txt`, 80 строк) и, если нужен живой пример, брать пар-гейченные DC-чекеры в `internal/checks`. (2) НЕ писать «живой остаток долга — строка 79 бэклога (остаточные Cyrillic-хардкоды)»: строка 79 — про Go-константу `cjkScriptNames` (плотность письма для серий), и её кириллические компоненты в ней же помечены ИСПОЛНЕННЫМИ (D39.78); носителем чистки мёртвых указателей на checkers_zh_ru.go была строка 89 (D39.72), и она ЗАКРЫТА — то есть остатка долга по этому предмету в трекере нет вовсе, доковый пример — последний хвост. + +--- + +## N103 · MINOR · `docs/architecture/12-go-style-notes.md:16` · B-мёртвый-указатель +**Срез:** Заметки реализации и Go-нормы — docs/architecture/03-implementation-notes.md · docs/architecture/12-go-style-notes.md +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** **Русским (target-языком) ОСТАЮТСЯ и не трогаются:** wire/инъекц-промпты, которые читает модель (`glossaryBlockHeader`, маркер `⟨проверить⟩`, gender-аннотации; …) +**Почему протухло:** Идентификатора `glossaryBlockHeader` в дереве нет: константа с русским заголовком жила в `internal/pipeline/memory.go:504` и удалена тем же паком-14 (`952469b`, 24.07) — русский wire-текст перестал быть Go-идентификатором и стал СТРОКОЙ ДАННЫХ `ru\tglossary_header\t…` в `internal/lang/data/injection.txt`; в Go осталось только поле `InjectionTexts.GlossaryHeader` (`internal/lang/embedded.go:387`), которое само по себе никакого русского не несёт. Правило неперевода wire-строк в силе, но названный носитель уехал: сессия, ищущая «где нельзя трогать русский», грепает `glossaryBlockHeader` и получает ноль, а настоящий охраняемый объект — `injection.txt`. +**Улика — команда:** `cd /home/ubuntu/projects/textmachine-main && grep -rn 'glossaryBlockHeader' --include=*.go backend/ ; echo '--- was ---'; git grep -n 'glossaryBlockHeader' 952469b^ -- backend/ | head -3; echo '--- now ---'; grep -n 'glossary_header' backend/internal/lang/data/injection.txt` +**Улика — вывод:** grep -rn 'glossaryBlockHeader' --include=*.go backend/ → (пусто) +--- was --- +952469b^:backend/internal/pipeline/memory.go:501:// glossaryBlockHeader introduces the injected glossary block. … +952469b^:backend/internal/pipeline/memory.go:504:const glossaryBlockHeader = "ГЛОССАРИЙ (используй эти утверждённые переводы имён и терминов последовательно; строки с пометкой ⟨проверить⟩ — неподтверждённые кандидаты):" +--- now --- +backend/internal/lang/data/injection.txt:6:ru\tglossary_header\tГЛОССАРИЙ (используй эти утверждённые переводы имён и терминов последовательно; строки с пометкой ⟨проверить⟩ — неподтверждённые кандидаты): +**Положительный контроль:** grep -rno '[a-zA-Z]*[Gg]lossary[A-Za-z]*' --include=*.go backend/ | sort -u → RenderGlossaryBlock, GlossaryHeader, GlossaryInjection, glossaryLineTokens, … (37 идентификаторов). Инструмент в этой области находит десятки соседних имён; пустой результат по `glossaryBlockHeader` — отсутствие символа, а не промах грепа. +**Предложенное действие:** Заменить `glossaryBlockHeader` на действующие носители: «…которые читает модель (target-данные инъекции `internal/lang/data/injection.txt`: `glossary_header` / `editor_header` / `editor_unverified_header` / `unverified_marker` / `gender_*`; в Go им отвечает `lang.InjectionTexts`, `internal/lang/embedded.go:387`)». Так правило начинает указывать на файл, правка которого действительно двигает провод. +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Замену делать, но без предложенного якоря `internal/lang/embedded.go:387`: 387 — это ПОЛЕ `GlossaryHeader`, тип `InjectionTexts` объявлен на 386, и главное — file:line-якоря живых доков линтуются `docs/scripts/counts.py --lint` и гниют при каждой правке файла. Писать без номера строки: «…target-данные инъекции `internal/lang/data/injection.txt` (`glossary_header` · `editor_header` · `editor_unverified_header` · `unverified_marker` · `gender_*`); в Go им отвечает тип `lang.InjectionTexts` (`internal/lang/embedded.go`)». Оговорку про санкционированное снятие маркера (строка 134 / D39.104) сохранить — она к идентификатору не относится. + +--- + +## N104 · MINOR · `docs/architecture/12-go-style-notes.md:17` · A-дрейф +**Срез:** Заметки реализации и Go-нормы — docs/architecture/03-implementation-notes.md · docs/architecture/12-go-style-notes.md +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** ни `W0`/`W1`/`W1.5`/`W2` в идентификаторах, логах и комментариях — пишем `waveDraft`/`waveEdit` … в код и в операторский вывод они не возвращаются. +**Почему протухло:** Фактический хвост нормы («в код … не возвращаются») сегодня ложен: в НЕтестовом Go живут 8 упоминаний, все в комментариях — `cmd/tmctl/main.go:313`, `internal/config/pipeline.go:90,94,101,103,108`, `internal/config/book.go:84,85`. Норма запрещает аббревиатуры именно «в идентификаторах, логах и КОММЕНТАРИЯХ», так что это её прямое нарушение, а не серая зона. Все восемь датированы 19.07 — днём самой директивы — и с тех пор ни разу не вычищены. В идентификаторах и в операторском выводе аббревиатур действительно нет (проверено): протух только хвост про «код целиком». +**Улика — команда:** `cd /home/ubuntu/projects/textmachine-main/backend && grep -rnE '\bW0\b|\bW1\b|\bW1\.5\b|\bW2\b' --include=*.go . | grep -v _test.go; git blame -L 313,313 --date=short -- cmd/tmctl/main.go | cat` +**Улика — вывод:** cmd/tmctl/main.go:313:// pre-flight the operator runs on the mined delta before the W1.5 reseed. Prints "OK" on a clean seed. +internal/config/pipeline.go:90:// over the draft chunks (W1) and edit units (W2). It is a TRANSPORT axis … +internal/config/pipeline.go:94:// to 1: a wave-STRUCTURED but sequential run (W1 all drafts → W1.5 → W2 all units), deterministic and safe; +internal/config/pipeline.go:101:// Mining configures the W1.5 bank-mining stop (WS3 / R1): … +internal/config/pipeline.go:103:// langpack_root): the miner then runs at the W1.5 boundary over the W1 drafts … +internal/config/pipeline.go:108:// … Empty ContrastPath ⇒ W1.5 auto-continues. +internal/config/book.go:84:// … Empty ⇒ no +**Другие носители того же факта:** docs/glossary.md:39 — «Волны — waveDraft (черновик) → терминолог → waveEdit (редактура); имена только полные, аббревиатуры W0/W1 запрещены (решение владельца 19.07)». Носитель ВЕРЕН: несёт норму без ложного фактического хвоста, править его не нужно. +**Предложенное действие:** Снять недоказуемый хвост и заменить его на состояние с ценой: «… — пишем `waveDraft`/`waveEdit`, «draft wave started». В идентификаторах и операторском выводе аббревиатур нет (проверено грепом); ⚠ в КОММЕНТАРИЯХ остались 8 хвостов от 19.07 — `cmd/tmctl/main.go:313`, `internal/config/pipeline.go:90,94,101,103,108`, `internal/config/book.go:84,85`; чистка комментария поведенчески нейтральна и голден не двигает — снимать попутно при правке этих файлов.» +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Не вписывать в норматив предложенный список из восьми file:line — он неполон уже при рождении (пропущены `internal/pipeline/bankmaterialize.go:98` и комментарии `configs/pipeline-arm-mistral.yaml:3`, `configs/pipeline-arm-deepseek-pro.yaml:3`) и попадает под линт якорей `counts.py --lint`, то есть гниёт с первой же правкой этих файлов. Правильное лечение двухчастное: (1) в доке снять недоказуемый описательный хвост, оставив НОРМУ и одну датированную фразу без якорей — «⚠ в комментариях остались хвосты от 19.07; чистка поведенчески нейтральна и голден не двигает — снимать попутно»; (2) сама чистка — зона БЭКЕНДА, значит нужна строка бэклога на свип комментариев (греп вести токеном `W[012](\.5)?` с учётом подчёркиваний, иначе `snapshot_W2` снова уедет). Второй носитель `docs/glossary.md:39` находка оценила верно — он несёт норму без ложного хвоста и правки не требует. + +--- + +## N105 · MINOR · `docs/architecture/13-tech-debt-anchors.md:12` · E-второй-носитель +**Срез:** Реестр загейченных триггеров — docs/architecture/13-tech-debt-anchors.md (весь реестр, §Б-108 первым) +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** backend/internal/store/migrate.go:186 «translit_policy … field only in v1; validator = Phase 2» и :194 «ruby_reading … bridge to Polivanov dst validation, B6 Phase 2 — NOT a dst» +**Почему протухло:** Один и тот же якорь ja-препа живёт в двух живых носителях, и копии разошлись: строка 81 бэклога уже исправлена на «migrate.go:187/194», а справочник — тот самый файл, куда бэклог отсылает за якорями, — застрял на :186. Реальные позиции: 187 и 195, то есть в справочнике ошибочны ОБА числа, а в трекере — одно. Это прямой контрпример объявленной норме файла «Актуализируется лендингами паков, которые трогают эти строки»: лендинг поправил трекер и не поправил справочник, ради которого трекер и держат тонким. +**Улика — команда:** `grep -n 'translit_policy TEXT' backend/internal/store/migrate.go; grep -n 'ruby_reading TEXT' backend/internal/store/migrate.go; grep -nE '^\| 81 \|' docs/PROGRESS.md | grep -o 'migrate.go:[0-9/]*'` +**Улика — вывод:** 187: translit_policy TEXT NOT NULL DEFAULT '', -- B5 western-name-via-katakana (field only in v1; validator = Phase 2) +195: ruby_reading TEXT NOT NULL DEFAULT '', -- ruby-seeded rows: the furigana reading (bridge to Polivanov dst validation, B6 Phase 2 — NOT a dst) +--- +docs/PROGRESS.md строка 81: migrate.go:187/194 +**Другие носители того же факта:** docs/PROGRESS.md:89 (строка бэклога 81 — вторая, частично исправленная копия якоря) +**Предложенное действие:** Свести к одному носителю: в строке 81 бэклога оставить ссылку «якоря — §Срез 81/83/85 справочника», а числа держать только в справочнике (и там поправить 186→187, 194→195). Пока живут обе копии — при каждом лендинге, трогающем ja-преп, обязана обновляться ТА, куда отсылает вторая, иначе расхождение воспроизведётся. +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Диагноз верен, лекарство — нет. Автор предлагает «числа держать только в справочнике», то есть сконцентрировать голые номера в одном носителе, — а именно голые номера и есть механизм гниения: в этом же файле 14 из 18 сверенных мной якорей уехали, и якорь `memory.go:1048` из ЭТОЙ ЖЕ строки 81 уехал дважды (сегодня цель на 1077), причём автор его не заметил, хотя он лежит в тех же двух носителях. Правильное действие — не переносить копию, а сделать ОБЕ копии машинно-проверяемыми: гейт `docs/scripts/counts.py --lint` (D39.126, бэклог-строка 205) сверяет якорь по содержимому, если он записан в токен-форме, поэтому в справочнике и в строке 81 писать `` `backend/internal/store/migrate.go:187`=`translit_policy TEXT` ``, `` `…:195`=`ruby_reading TEXT` `` и `` `backend/internal/membank/memory.go:1077`=`KANA and HAN are deliberately NOT boundary-checked` ``. Тогда расхождение копий перестаёт быть вопросом дисциплины лендинга: следующий сдвиг кода покраснеет в обеих. Дедупликацию носителей (ссылка вместо чисел) делать можно, но она вторична и без токенов не спасает — сегодняшний единственный носитель тоже протух. + +--- + +## N106 · MINOR · `docs/architecture/13-tech-debt-anchors.md:24` · B-мёртвый-указатель +**Срез:** Ранние арх-доки (кандидаты в архив): docs/architecture/01-decisions.md · 02-mvp-plan.md · 04-unhappy-paths.md · 06-memory-risk-registry.md +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** 02-mvp-plan.md:34 «пере-съём exp08 v3» как открытый +**Почему протухло:** Якоря реестра техдолга в ДВА дока моего среза сдвинулись ровно на +6 строк и не были актуализированы: коммит d2cd30c (22.08) дописал ⚠-поправки в 02-mvp-plan.md (+7/−1 = +6) и 06-memory-risk-registry.md (+6). Сегодня: «пере-съём exp08 v3» — 02-mvp-plan.md:40 (на :34 стоит строка про `tmctl export`); A7 — 06:38 (на :32 стоит A1), D2 — 06:67 (на :61 пустая строка), «Отложено в v1.1/Фазу 2 … морфо-гейт глагольного рода (C3)» — 06:111 (на :105 стоит «Обязательно закрыть/учесть до заморозки схемы (шаг 4):»). +**Улика — команда:** `cd /home/ubuntu/projects/textmachine-main && git show --numstat d2cd30c -- docs/architecture/02-mvp-plan.md docs/architecture/06-memory-risk-registry.md | tail -2; for n in 34 40; do printf '02:%s -> ' $n; sed -n "${n}p" docs/architecture/02-mvp-plan.md | cut -c1-60; done; for n in 32 38 61 67 105 111; do printf '06:%s -> ' $n; sed -n "${n}p" docs/architecture/06-memory-risk-registry.md | cut -c1-55; done` +**Улика — вывод:** 7 1 docs/architecture/02-mvp-plan.md +6 0 docs/architecture/06-memory-risk-registry.md +02:34 -> - Экспорт txt/epub + отчёт — конкретизирован D29.1: `tmctl export` +02:40 -> 2. **Деньги:** телеметрия точна … мягкий sanity-якорь **~$0.7/ранобэ (до-флиповый exp08; … пере-съём exp08 v3)** +06:32 -> | A1 | **Пустой/слабый retrieval трактуется как ошибка** +06:38 -> | A7 | **Эмбеддинг-порог как тихий гейт** (Фаза 2) +06:61 -> (пустая строка) +06:67 -> | D2 | **Whole-book RAG по резюме — недоказанное расширение** +06:105 -> Обязательно закрыть/учесть до заморозки схемы (шаг 4): +06:111 -> 5. **Отложено в v1.1/Фазу 2 (не блокирует v1-схему, но заложить поля/место):** +**Другие носители того же факта:** docs/architecture/13-tech-debt-anchors.md:64 («06-memory-risk-registry.md:105» — тот же +6), docs/architecture/13-tech-debt-anchors.md:88 («06-memory-risk-registry.md:32,61,105» — тот же +6) +**Предложенное действие:** Одна арифметическая правка: во всех трёх местах 13-tech-debt-anchors.md прибавить 6 — 02-mvp-plan.md:34→:40; 06:32→:38, 06:61→:67, 06:105→:111. Урок на будущее: якоря в ⚠-баннируемые доки держать по цитате/секции, а не по номеру строки — любой новый баннер их снова сдвинет. +**Разрушающее:** False + +--- + +## N107 · MINOR · `docs/architecture/13-tech-debt-anchors.md:104` · E-второй-носитель +**Срез:** Реестр загейченных триггеров — docs/architecture/13-tech-debt-anchors.md (весь реестр, §Б-108 первым) +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** 84 (neuter отсутствует в enum memory.go:815-823 и в инъекционных текстах embedded.go:395-397; `gender: neuter` в сиде — тихий no-op без линта) +**Почему протухло:** Факт про neuter лежит в этом файле ДВАЖДЫ, и копии разошлись ВНУТРИ одного документа: тело пункта (строки 70-72) несёт пере-сверку 23.08 «ЧЕТЫРЕ пункта из пяти ПРОТУХЛИ — носитель в коде ПОСТРОЕН», а «Сводка среза» тремя абзацами ниже по-прежнему утверждает обратное — «neuter отсутствует в enum … тихий no-op без линта». Оба цитируемых сводкой якоря мертвы: `case "neuter"` живёт на memory.go:848 (не 815-823), `GenderNeuter` — на embedded.go:398 (не 395-397). Читатель, который дойдёт только до сводки (а сводка — то, что читают быстро), унесёт опровергнутый факт. +**Улика — команда:** `sed -n '848p' backend/internal/membank/memory.go; sed -n '815,823p' backend/internal/membank/memory.go | head -3; sed -n '398p' backend/internal/lang/embedded.go; sed -n '395,397p' backend/internal/lang/embedded.go` +**Улика — вывод:** 848: case "neuter", "n": +--- memory.go:815-823 (то, что там на самом деле): --- +// containsWholeWord reports whether s contains sub as a whole word ... +--- embedded.go:398: --- + GenderNeuter string // " (ср. — …)" — a neuter entity/creature (row 84); "" for a target wi +--- embedded.go:395-397: соседние поля, не GenderNeuter --- +**Другие носители того же факта:** docs/architecture/13-tech-debt-anchors.md:70-72 (тело пункта «Строка 84» — противоположная, верная редакция) +**Предложенное действие:** В «Сводке среза» (строка 104) заменить фрагмент про 84 на «84 — ЗАКРЫТА D39.69 §3 (neuter end-to-end построен; живой остаток — комментарий схемы `migrate.go:184`)» и убрать фрагмент «feed_cap: не построен (grep=0) … батчи в лексикографическом порядке src». Общее правило по месту: сводки среза не должны нести собственную фактуру — только ссылку на пункты, иначе они гниют независимо от тел, как здесь. +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие верно, но НЕПОЛНО, и в неполном виде оставит документ наполовину ложным. Автор чинит feed_cap только в СВОДКЕ, а тот же протухший факт несёт ТЕЛО пункта на строках 98-102: «feed_cap НЕ построен … кандидаты сортированы ЛЕКСИКОГРАФИЧЕСКИ по src (terminologist.go:191; terminology.go:212), а Batch сохраняет этот порядок (terminology.go:917-919)». Замером: `Batch` теперь упорядочивает батчи по УБЫВАНИЮ частоты (terminology.go:1161-1164, гвоздь TestBatchValueOrder в series_test.go:199), а ратифицированная D39.69 §1 говорит дословно «value-порядок отреза бюджета (частота-desc — **feed_cap ЗАКРЫТ этим**, из строки 86 изъят)». Сверх того путь `terminology.go` в теле мёртв как ПУТЬ: `backend/internal/pipeline/terminology.go` не существует, пакет живёт в `backend/internal/terminology/`. Поэтому: править строки 98-102 и 104 ОДНИМ касанием — тело пометить «→ ЗАКРЫТО D39.69 §1 (value-порядок частота-desc, `backend/internal/terminology/terminology.go`, гвоздь TestBatchValueOrder)», сводку свести к ссылке на пункты. И общее правило автора («сводки не должны нести собственную фактуру») верно, но реализуемо тут дешевле: строка 162 показывает работающую альтернативу — ⚠-баннер о том, что якоря сводки не актуализируются. + +--- + +## N108 · MINOR · `docs/architecture/14-api-contract/README.md:6` · B-мёртвый-указатель +**Срез:** Контракт API и его зеркала — docs/architecture/14-api-contract/README.md + docs/architecture/14-api-contract/openapi.yaml +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** СТАТУС: РАТИФИЦИРОВАН как контракт API v0 по 0.9.0 включительно (D39.169; перечень миноров и их +провенанс — ниже по файлу, здесь НЕ дублируется) +**Почему протухло:** D39.169 ратифицировала минор 0.8.0, а не 0.9.0; 0.9.0 принят и заленджен нотой D39.180 (31.08). Тело того же дока (строка 69) описывает 0.9.0 вовсе БЕЗ номера ноты — «ратифицирован №20 31.08 … заленджен №21 с паком P12», так что читатель, которому шапка дала D39.169, номера 0.9.0 не найдёт нигде. +**Улика — команда:** `grep -n '^## D39.169\|^## D39.180' docs/architecture/05-decisions-log.md | cut -c1-150; sed -n '69,71p' docs/architecture/14-api-contract/README.md | cut -c1-160` +**Улика — вывод:** 1176:## D39.169 — ПЛАТФОРМЕННЫЙ ПАК P11 ПРИНЯТ И ЗАЛЕНДЖЕН + КОНТРАКТНЫЙ МИНОР 0.8.0: отзыв сессии гасит открытый поток, застрявшие деньги видны (29.08, оркестратор №19). ✅ +1677:## D39.180 — ПАК ПЛАТФОРМЫ P12 «ДОЛГИ ПОД НОГАМИ» ПРИНЯТ И ЗАЛЕНДЖЕН вместе с контрактным минором 0.9.0 (31.08, оркестратор №21). ✅ + +> **0.9.0 (ратифицирован №20 31.08 по пингу зоны ДО стройки, заленджен №21 с паком P12) — ВТОРАЯ +> граница пересчёта `chapters_done` и непрозрачный `Book.shape_epoch`, которым она себя называет; +**Предложенное действие:** В шапке заменить «(D39.169; …)» на «(D39.180; …)» и в перечне миноров (строка 69) дописать номер ноты: «0.9.0 (D39.180, 31.08) — ратифицирован №20 по пингу зоны ДО стройки, заленджен №21 с паком P12». +**Разрушающее:** False + +--- + +## N109 · MINOR · `docs/architecture/14-api-contract/README.md:60` · B-мёртвый-указатель +**Срез:** Контракт API и его зеркала — docs/architecture/14-api-contract/README.md + docs/architecture/14-api-contract/openapi.yaml +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** `/v0`; вывод и провенанс — §2.19, отчёт сессии — `docs/CONTRACT_MINOR_REPORT.md` +**Почему протухло:** Файла по этому пути нет: отчёт вынесен в архив коммитом 31b96d1 (27.08) и лежит как `docs/archive/reports/CONTRACT_MINOR_REPORT_2026-08-27.md`. Ссылка повторяется в теле дока (строка 738) и оба раза ведёт в никуда. +**Улика — команда:** `ls docs/CONTRACT_MINOR_REPORT.md; find /home/ubuntu/projects/textmachine-main -name 'CONTRACT_MINOR_REPORT*'; git show --stat 31b96d1 | grep CONTRACT` +**Улика — вывод:** ls: cannot access 'docs/CONTRACT_MINOR_REPORT.md': No such file or directory +/home/ubuntu/projects/textmachine-main/docs/archive/reports/CONTRACT_MINOR_REPORT_2026-08-27.md + .../reports/CONTRACT_MINOR_REPORT_2026-08-27.md} | 8 ++++++++ +**Положительный контроль:** Тот же `find` по каталогу docs/ находит существующие доки (`./docs/PROGRESS.md`) и находит сам искомый отчёт по basename в архиве — значит инструмент в этой области видит, а ноль по `docs/CONTRACT_MINOR_REPORT.md` есть отсутствие файла, а не слепота поиска. +**Другие носители того же факта:** docs/architecture/14-api-contract/README.md:738 — «подробный разбор и отвергнутые альтернативы — отчёт `docs/CONTRACT_MINOR_REPORT.md`» (тот же мёртвый путь) +**Предложенное действие:** Обе ссылки (строки 60 и 738) переписать на `docs/archive/reports/CONTRACT_MINOR_REPORT_2026-08-27.md` и пометить ⚠-архивной, чтобы инструкции оттуда не исполнялись. +**Разрушающее:** False + +--- + +## N110 · MINOR · `docs/architecture/14-api-contract/README.md:990` · C-исполненное-как-открытое +**Срез:** Контракт API и его зеркала — docs/architecture/14-api-contract/README.md + docs/architecture/14-api-contract/openapi.yaml +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** | `PATCH`/`DELETE /books/{id}`, `GET /runs/{id}` | колонки есть | НЕ ПОСТРОЕНО (заведено 0.3.0) | вход P7 | +**Почему протухло:** Носитель «вход P7» израсходован — пак P7 принят D39.153, и ⚠-абзац НАД самой таблицей это уже пишет («Носитель израсходован: пак P7 принят D39.153»). Факт «НЕ ПОСТРОЕНО» верен (роутер монтирует 15 маршрутов из 20 операций канона: нет `updateBook`/`deleteBook`/`getRun`/`createExport`/`getExport`), но строки живут без действующего носителя — ровно то нарушение §Б-21, которое таблица и заведена чинить. Тем же дефектом больны ещё три строки: `resume` (981), `GET …/notes` (988) и «Счёт сделанного» (1004). +**Улика — команда:** `grep -c 'operationId:' docs/architecture/14-api-contract/openapi.yaml; sed -n '/^var contractSurface/,/^}$/p' platform/internal/httpapi/v0.go | grep -c 'method:'; sed -n '/^var contractSurface/,/^}$/p' platform/internal/httpapi/v0.go | grep -o 'path: "[^"]*"' ` +**Улика — вывод:** 20 +15 +path: "/capabilities" +path: "/books" +path: "/books/{bookId}" +path: "/books/{bookId}/chapters" +path: "/books/{bookId}/chapters/{chapterId}/units" +path: "/books/{bookId}/notes" +path: "/books/{bookId}/bank" +path: "/books/{bookId}/bank/corrections" +path: "/books/{bookId}/events" +path: "/usage" +path: "/books/{bookId}/run-options" +path: "/books/{bookId}/runs" +path: "/runs/{runId}/stop" +path: "/runs/{runId}/resume" +path: "/books" (POST) +— ни одного PATCH/DELETE /books/{bookId}, ни GET /runs/{runId}, ни /exports +**Другие носители того же факта:** docs/PROGRESS.md:3 — «канон обещает `createExport`/`getExport`, а платформа их НЕ монтирует (смонтировано 15 операций из 20; нет также `getRun`, `updateBook`, `deleteBook`)» — этот носитель ЖИВОЙ и совпадает с деревом, platform/BACKLOG.md П-17 — долг двери выдачи (по docs/PROGRESS.md:3) +**Предложенное действие:** Заменить в четырёх строках таблицы израсходованный носитель «вход P7» на живой: для `PATCH`/`DELETE /books/{id}` и `GET /runs/{id}` — строка зонного `platform/BACKLOG.md` (или новая строка единого бэклога), для `resume` — «закрыто D39.162/PD-282 fixed» (см. отдельную находку), для остальных — действующий пак/ноту. Само правило §Б-21 при этом не трогать: оно и поймало дефект. +**Разрушающее:** False + +--- + +## N111 · MINOR · `docs/architecture/14-api-contract/README.md:998` · B-мёртвый-указатель +**Срез:** Контракт API и его зеркала — docs/architecture/14-api-contract/README.md + docs/architecture/14-api-contract/openapi.yaml +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** | `Note.code` как enum спеки | карта приложения А | не enum, пока не написаны фразы | строка 148 (фразы владельца) | +**Почему протухло:** Строки 148 в едином бэклоге НЕТ — её удалил `a023e39` решением владельца 21.08. Хуже: ТОТ ЖЕ документ на строке 1670 уже это знает и пишет «⚠ Прежний указатель «строка 148» мёртв: строку удалил `a023e39` решением владельца», а действующим носителем называет строки 203/204 и делегирование фраз (D39.176 п.4, подписи владельца больше не ждём). То есть §3 спорит с Приложением А того же файла. +**Улика — команда:** `for n in 49 94 148 160 161 186 228; do printf 'строка %s: %s\n' $n "$(grep -c "^| $n |" docs/PROGRESS.md)"; done; git show --stat a023e39 | head -8` +**Улика — вывод:** строка 49: 1 +строка 94: 1 +строка 148: 0 +строка 160: 1 +строка 161: 1 +строка 186: 1 +строка 228: 1 + +commit a023e39323529de3101cb8a216471b604f69d667 +Date: Fri Aug 21 19:31:20 2026 + Record three owner decisions: cut comment water rather than length, drop the appendix row that described a world the contract abolished, and put the complain-to-owner norm into the frontend hook + docs/PROGRESS.md | 7 +++---- +**Положительный контроль:** Та же форма грепа `^| N |` по docs/PROGRESS.md находит шесть других номеров, названных этим же доком (49, 94, 160, 161, 186, 228) — инструмент строки бэклога видит; ноль даёт именно 148. +**Другие носители того же факта:** docs/architecture/14-api-contract/README.md:1670 — второй носитель того же факта, и он УЖЕ верен («Прежний указатель «строка 148» мёртв»); расходятся именно две копии внутри одного дока +**Предложенное действие:** В строке 998 заменить носитель «строка 148 (фразы владельца)» на тот, который сам док уже назвал в Приложении А п.3: «строка 203 (карта причина→код) + строка 204 (доставка словаря данными); фразы делегированы проекту, D39.176 п.4 — подписи владельца не ждём». +**Разрушающее:** False + +--- + +## N112 · MINOR · `docs/architecture/14-api-contract/README.md:1002` · B-мёртвый-указатель +**Срез:** Контракт API и его зеркала — docs/architecture/14-api-contract/README.md + docs/architecture/14-api-contract/openapi.yaml +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** заказано бэкенд-паку «тихая порча», `docs/BACKEND_SILENT_HARM_SESSION_PROMPT.md` §3.3 +**Почему протухло:** Промт вынесен в архив: живой путь — `docs/archive/prompts/BACKEND_SILENT_HARM_SESSION_PROMPT_2026-08-28.md`. Дополнительно: половина того, на что ссылка ведёт (§3.3), в этой же клетке уже объявлена ИСПОЛНЕННОЙ («пере-снято 30.08: … ВТОРОЙ ЗАКРЫТ … D39.164 §3.3»), то есть указатель ведёт в архив за работой, которая закрыта нотой. +**Улика — команда:** `ls docs/BACKEND_SILENT_HARM_SESSION_PROMPT.md; ls docs/archive/prompts/ | grep SILENT` +**Улика — вывод:** ls: cannot access 'docs/BACKEND_SILENT_HARM_SESSION_PROMPT.md': No such file or directory +BACKEND_SESSION_PROMPT_SILENT_REFUSALS.md +BACKEND_SILENT_HARM_SESSION_PROMPT_2026-08-28.md +**Положительный контроль:** Тот же `ls` в `docs/archive/prompts/` возвращает два файла с искомым токеном SILENT — каталог читается, поиск работает; ноль по `docs/BACKEND_SILENT_HARM_SESSION_PROMPT.md` есть отсутствие файла. +**Предложенное действие:** Заменить путь на `docs/archive/prompts/BACKEND_SILENT_HARM_SESSION_PROMPT_2026-08-28.md` (⚠ архив), а живым носителем оставшейся половины назвать строку бэклога 228 — она в этой же клетке уже указана и в трекере существует. +**Разрушающее:** False + +--- + +## N113 · MINOR · `docs/architecture/15-money-path.md:6` · C-исполненное-как-открытое +**Срез:** Денежный путь — docs/architecture/15-money-path.md +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** `docs/architecture/14-api-contract/openapi.yaml` лежит в «своей» зоне и при этом двигается прямо сейчас паком контракта 0.9.0. +**Почему протухло:** Пак контракта 0.9.0 ЗАЛЕНДЖЕН 31.08 коммитом 0a680a3 (D39.180), файл в дереве чист, версия в нём — 0.9.0. Настоящее время «двигается прямо сейчас» ложно, и вместе с ним протух ЕДИНСТВЕННЫЙ пример, на котором шапка держит свою норму «стабильна не зона, а чистый файл», а также обоснование в §1: «⚠ номер не ставим: файл двигает пак контракта 0.9.0». По собственному правилу дока цель теперь чиста и годна под номерную форму с токеном. +**Улика — команда:** `git log --format='%h %ad %s' --date=short -1 -- docs/architecture/14-api-contract/openapi.yaml; git status --porcelain docs/architecture/14-api-contract/openapi.yaml; grep -n '^ version:' docs/architecture/14-api-contract/openapi.yaml` +**Улика — вывод:** 0a680a3 2026-08-31 Land platform pack P12 with contract minor 0.9.0: the bank-stop bypass is gone, a chapter counts by the pass current for it, and the boundary now judges prose too +(git status --porcelain: пустой вывод — файл чист) +5: version: 0.9.0 +**Другие носители того же факта:** docs/architecture/15-money-path.md:14 — второй экземпляр того же истёкшего обоснования («⚠ номер не ставим: файл двигает пак контракта 0.9.0») +**Предложенное действие:** Перевести пример в прошедшее время: «…лежал в «своей» зоне и при этом двигался паком контракта 0.9.0 (заленджен 31.08, `0a680a3`)»; в §1 (строка 14) заменить обоснование на нейтральное («контракт переверстывается минорами — греп-форма дешевле поддержки») или поставить номер с токеном, раз файл чист. +**Разрушающее:** False + +--- + +## N114 · MINOR · `docs/architecture/15-money-path.md:14` · A-дрейф +**Срез:** Денежный путь — docs/architecture/15-money-path.md +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** Деньги видимы с первого вызова: телеметрия, потолки, явное согласие на пере-оплату (CLAUDE.md, цель 5). +**Почему протухло:** Из трёх опор канона, названных здесь, у двух в доке есть носители (телеметрия — леджер/status в §2, потолки — §2/§3), а у третьей — «явное согласие на пере-оплату» — нет ни одного: ни в §2, ни в карте §6 «Код денег движка» (там пять файлов и `pipeline/rebill.go` среди них нет). Механизм построен и ратифицирован: порог `min($0.50, 5% × ProjectedBookUSD)` с переопределением `rebill_consent_usd` (D20.2-Q2). ✅ Честно: сам пробел стоит с рождения дока (гейт заленджен 25.07, dad4b56/D39.31), то есть это НЕ чистый дрейф; но тема СДВИНУЛАСЬ 31.08 и док этого не застал: D39.182 §1(б) починил дефект, из-за которого гейт НЕ срабатывал на правке исходника на месте, а §3 той же ноты пере-определил базис порога («считается БЕЗ контура банк-ролей»). +**Улика — команда:** `grep -n 'rebillConsentFloorUSD\|rebillConsentShare\|rebill_consent_usd' backend/internal/pipeline/rebill.go backend/internal/config/book.go | head -5; sed -n '67p' docs/architecture/15-money-path.md` +**Улика — вывод:** backend/internal/pipeline/rebill.go:41: rebillConsentFloorUSD = 0.50 +backend/internal/pipeline/rebill.go:42: rebillConsentShare = 0.05 +backend/internal/pipeline/rebill.go:285:// `rebill_consent_usd` when it declares one, else the ratified `min($0.50, 5% × ProjectedBookUSD)` with +backend/internal/config/book.go:324: bad("rebill_consent_usd must be ≥ 0 (0 = the ratified default min($0.50, 5%%×projected book cost)), got %v", b.RebillConsentUSD) + +| Код денег движка | `backend/internal/store/ledger.go` · `store/store.go` · `pipeline/stagerun.go` · `pipeline/status.go` · `config/book.go` | +**Положительный контроль:** ОТРИЦАТЕЛЬНОЕ: grep -in 'rebill|пере-оплат|согласи' docs/architecture/15-money-path.md → единственный хит — сама строка 14. ПОЛОЖИТЕЛЬНЫЙ КОНТРОЛЬ: тот же греп по 'ре-билл' находит строку 23 («не двигает ни снапшот, ни ре-билл») — тема доку не чужда, значит ноль по носителю гейта — факт, а не артефакт грепа. +**Предложенное действие:** Добавить в §2 буллет: «**Согласие на пере-оплату — движковый гейт.** Прогон, который пере-покупает уже оплаченные юниты дороже порога, останавливается и требует явного согласия; порог по умолчанию ратифицирован D20.2-Q2 — `min($0.50, 5% × ProjectedBookUSD)` с полом $0.50 для книги без обработанных юнитов, книга переопределяет его `rebill_consent_usd` (`backend/internal/pipeline/rebill.go`, греп `func (r *Runner) rebillConsentThreshold(`; валидатор — `backend/internal/config/book.go`, греп `rebill_consent_usd must be`). ⚠ Порог считается БЕЗ контура банк-ролей (D39.182 §3): контур не пере-оплачивается.» И внести `pipeline/rebill.go` в строку «Код денег движка» карты §6. +**Разрушающее:** False + +--- + +## N115 · MINOR · `docs/architecture/16-events-emitter.md:3` · E-второй-носитель +**Срез:** Эмиттер · закон шва · онтология банка — docs/architecture/16-events-emitter.md · 17-seam-inbound-law.md · 18-bank-ontology.md +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** ⚠ **ЭМИТТЕР ПОСТРОЕН И ПРИНЯТ (D39.131, `9cfe080`, 14.08):** `events.jsonl` (StreamVersion 1.1) +**Почему протухло:** Движок с 31.08 (`bb541a8`) пишет `StreamVersion = "1.2"` — минор поднят под `Finished.Volume` (D39.181 п.2). 1.1 сегодня — это версия ЧИТАТЕЛЯ платформы, а не потока. Тот же факт лежит вторым носителем в `platform/docs/STACK_DECISIONS.md:419` («движок, StreamVersion 1.1») и разошёлся с кодом там же. +**Улика — команда:** `grep -n 'const StreamVersion' backend/internal/runevents/runevents.go platform/internal/ingest/events.go; git log -S 'StreamVersion = "1.2"' --format='%h %ad %s' --date=short -- backend/internal/runevents/runevents.go; grep -n 'StreamVersion' platform/docs/STACK_DECISIONS.md` +**Улика — вывод:** backend/internal/runevents/runevents.go:49:const StreamVersion = "1.2" +platform/internal/ingest/events.go:46:const StreamVersion = "1.1" +bb541a8 2026-08-31 Land the disclosure law and its eleven instances: two of the three acceptance blockers were defects inside the cure itself +platform/docs/STACK_DECISIONS.md:419:| `events.jsonl` (NDJSON эмиттера) | движок, StreamVersion 1.1 | append-only | `internal/ingest/tai +**Другие носители того же факта:** platform/docs/STACK_DECISIONS.md:419, docs/PROGRESS.md:205 (строка 243, arch-10 — та же находка удержана и не применена) +**Предложенное действие:** В обоих носителях: «StreamVersion 1.2 (движок пишет; читатель платформы объявляет 1.1 — мажор один, минор читателя отстаёт намеренно, `runevents.go:44-48`)». В `STACK_DECISIONS.md:419` — та же правка (зона платформы, пингом). +**Разрушающее:** False + +--- + +## N116 · MINOR · `docs/architecture/16-events-emitter.md:90` · C-исполненное-как-открытое +**Срез:** Эмиттер · закон шва · онтология банка — docs/architecture/16-events-emitter.md · 17-seam-inbound-law.md · 18-bank-ontology.md +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** **PD-61 (открыт; два свойства ДО постройки)** — `DEFECT_REGISTER.md:72` +**Почему протухло:** PD-60 (строка 88, «открыт») и PD-61 (строка 90, «открыт») оба стоят в регистре как `fixed(P6, дерево сессии)`. Шапка дока баннирует только «PD-113 открыт» — эти две подписи баннер НЕ покрывает, и строка 90 прямо называет пункт (а) «живым требованием к эмиттеру». +**Улика — команда:** `for p in PD-60 PD-61 PD-107 PD-113; do printf "%-7s: " $p; grep "^| $p |" platform/docs/DEFECT_REGISTER.md | awk -F'|' '{print $(NF-2)}'; done` +**Улика — вывод:** PD-60 : fixed(P6, дерево сессии) +PD-61 : fixed(P6, дерево сессии) +PD-107 : open +PD-113 : fixed(P6, дерево сессии) +**Положительный контроль:** Тот же разбор ячейки на PD-107 возвращает `open` — значит поле статуса читается, и `fixed` у PD-60/61 не артефакт парсинга. +**Предложенное действие:** Расширить ⚠-баннер шапки: «упоминания „PD-113 / PD-60 / PD-61 открыт“ в теле ниже — историческая фактура эпохи стройки; все три закрыты P6». Один правкой снимаются обе подписи. +**Разрушающее:** False + +--- + +## N117 · MINOR · `docs/architecture/16-events-emitter.md:114` · B-мёртвый-указатель +**Срез:** Эмиттер · закон шва · онтология банка — docs/architecture/16-events-emitter.md · 17-seam-inbound-law.md · 18-bank-ontology.md +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** | 3 | D39.119 п.3 · D39.122 п.2(в) · D39.123 п.2(б,в), п.3 | там же `:394`, `:416`, `:428`, `:430` | +**Почему протухло:** Тела D39.119/122/123 уехали в слайс `docs/archive/architecture/05-decisions-D39-106-123.md`, где всего 210 строк — номера 394/416/428/430 там не существуют. В ЖИВОМ журнале решений те же номера пусты (394, 416) либо принадлежат чужому телу D39.139 (428, 430). Смягчающее: строка стоит в §6, который баннирован как «ОТРАБОТАННЫЙ ХЕНДОФФ» — поэтому minor, а не major. +**Улика — команда:** `for l in 394 416 428 430; do printf "%-4s| " $l; sed -n "${l}p" docs/architecture/05-decisions-log.md | cut -c1-60; done; wc -l docs/archive/architecture/05-decisions-D39-106-123.md; grep -n '^## D39\.\(119\|122\|123\) ' docs/architecture/05-decisions-log.md docs/archive/architecture/05-decisions-D39-106-123.md | cut -c1-70` +**Улика — вывод:** 394 | +416 | +428 | (а) **Моя зона — исправлено этим лендингом:** ПТ-16/ПТ-18 держали +430 | (в) **Зона фронта — пинг-аддендум в журнал:** `FRONTEND_SESSION_PR` +210 docs/archive/architecture/05-decisions-D39-106-123.md +docs/archive/architecture/05-decisions-D39-106-123.md:165:## D39.119 — КУРС ВЛАДЕЛЬЦА 08.08 +docs/archive/architecture/05-decisions-D39-106-123.md:189:## D39.122 — ПАК «ДВИЖКОВЫЕ БЛОКЕРЫ +docs/archive/architecture/05-decisions-D39-106-123.md:201:## D39.123 — ПАК ПЛАТФОРМЫ P4 «РАННЕР» +**Положительный контроль:** Греп `^## D39\.(119|122|123) ` в живом журнале пуст, а в слайсе даёт три хита (165/189/201) — инструмент работает, ноты просто переехали. Это ровно та ловушка «пустой греп по слайсу подтвердил бы вакуумно», которую D39.156 п.5 уже разбирала на другом примере. +**Предложенное действие:** Снять line-номера совсем (D39.126: номер грепается) — «грепать `D39.119`/`D39.122`/`D39.123`; тела — слайс `docs/archive/architecture/05-decisions-D39-106-123.md`». Это же требует строка бэклога 219 («line-якоря в тела D-лога обречены съезжать»). +**Разрушающее:** False + +--- + +## N118 · MINOR · `docs/glossary.md:7` · B-мёртвый-указатель +**Срез:** Глоссарий и продуктовые требования — docs/glossary.md · docs/product-requirements.md +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** «Оркестратор №N» — канонический счётчик преемственности роли (текущий N — в шапке ORCHESTRATOR_SESSION_PROMPT). +**Почему протухло:** В файле docs/ORCHESTRATOR_SESSION_PROMPT.md (240 строк) символа «№» нет НИ РАЗУ: его шапка намеренно номер не держит — «Счётчик ролей ОДИН, номер выдаёт тот, кто пишет промт передачи (D39.112 п.6)». Текущий номер живёт в CURRENT-STATE docs/PROGRESS.md, который прямо объявляет себя единственным носителем («ОЧЕРЕДЬ №22 (единственный носитель — здесь)»). +**Улика — команда:** `grep -c '№' docs/ORCHESTRATOR_SESSION_PROMPT.md; wc -l docs/ORCHESTRATOR_SESSION_PROMPT.md; sed -n '3p' docs/ORCHESTRATOR_SESSION_PROMPT.md | cut -c1-90; grep -o 'ОЧЕРЕДЬ №22 (единственный носитель — здесь' docs/PROGRESS.md` +**Улика — вывод:** 0 +240 docs/ORCHESTRATOR_SESSION_PROMPT.md +> Счётчик ролей ОДИН, номер выдаёт тот, кто пишет промт передачи (D39.112 п.6); параллельный +ОЧЕРЕДЬ №22 (единственный носитель — здесь +**Положительный контроль:** Тот же grep по тому же шаблону на docs/PROGRESS.md даёт 57 хитов (`grep -c '№' docs/PROGRESS.md` → 57), а `grep -c 'оркестратор\|Оркестратор' docs/ORCHESTRATOR_SESSION_PROMPT.md` → 3, то есть инструмент читает именно этот файл и находит в нём текст. Ноль по «№» — свойство файла, а не опечатка команды. +**Другие носители того же факта:** docs/PROGRESS.md:3 — CURRENT-STATE, «ОЧЕРЕДЬ №22 (единственный носитель — здесь; роль передана 01.09, №21 закрыт нотой передачи D39.184)» +**Предложенное действие:** Заменить скобку на «(текущий N — в CURRENT-STATE `docs/PROGRESS.md`, единственный носитель; номер выдаёт автор промта передачи — D39.112 п.6)». +**Разрушающее:** True + +--- + +## N119 · MINOR · `docs/product-requirements.md:50` · B-мёртвый-указатель +**Срез:** Глоссарий и продуктовые требования — docs/glossary.md · docs/product-requirements.md +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** **НЕ построена дверь платформы** (`createExport`/`getExport`) — очередь №20 п.4, гейт: живой прогон п.3 +**Почему протухло:** Сам факт ЖИВ (проверено кодом, см. positive_control), но указатель «очередь №20» умер дважды: роль передана №20→№21 (D39.177, 31.08) и №21→№22 (D39.184, 01.09), и CURRENT-STATE несёт «ОЧЕРЕДЬ №22». Номер очереди — движущаяся мишень, привязка к нему протухает каждой передачей роли. +**Улика — команда:** `grep -o 'ОЧЕРЕДЬ №22.\{0,60\}' docs/PROGRESS.md | head -1; grep -m1 '| D39.184 |' docs/architecture/05-decisions-index.md | cut -c1-95` +**Улика — вывод:** ОЧЕРЕДЬ №22 (единственный носитель — здесь; роль передана 01.09, №21 закрыт нотой пере +| D39.184 | 01.09 | **Передача роли оркестратора №21 → №22.** Смена приняла пак платформы P12 +**Положительный контроль:** Отрицательная половина утверждения ПТ-44 проверена и ЖИВА: `grep -rn 'createExport\|getExport' platform/ --include=*.go` → 0 хитов в коде (только README/BACKLOG/архив). Положительный контроль на тот же инструмент: `grep -n 'listBooks\|getBook' platform/internal/httpapi/v0.go` → строки 75-76 с реально смонтированными ручками. Т.е. греп по зоне ручки находит, а createExport/getExport в ней нет. +**Другие носители того же факта:** docs/PROGRESS.md:16 — «платформенный остаток несут очередь №20 п.4 и `platform/BACKLOG.md` П-17» (тот же мёртвый номер, не в моём срезе) +**Предложенное действие:** Заменить «очередь №20 п.4» на «очередь CURRENT-STATE, п.4 «дверь выдачи» (`docs/PROGRESS.md`) + `platform/BACKLOG.md` П-17» — на носитель, а не на номер смены. +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Уточнение к находке: п.4 НЕ умер — в сегодняшней очереди CURRENT-STATE пункт (4) по-прежнему «дверь выдачи — строго ПОСЛЕ (3)», уехал только НОМЕР очереди (№20 → №22). Хуже другое, и находка этого не заметила: в docs/PROGRESS.md существует ВТОРАЯ нумерованная очередь — блок «ОТКРЫТО НА ОРКЕСТРАТОРЕ №21» (строки 36-40), — и в её №20-редакции (85282f2) п.4 был `PD-425`, а не дверь выдачи. То есть читатель, идущий по «очередь №20 п.4», может сесть не на тот список. Поэтому замена на «очередь CURRENT-STATE, п.4 «дверь выдачи»» верна и нужна именно словом «дверь выдачи», а не номером. Ссылка на `platform/BACKLOG.md` П-17 тоже ПРАВОМЕРНА, хотя её голова читается как «ИСПОЛНЕНО P7»: хвост той же строки несёт «Не вошло и отложено в P8: `updateBook`/`deleteBook`/`getRun`, экспорт (`createExport`/`getExport`)», и CURRENT-STATE сам называет П-17 носителем этого долга. + +--- + +## N120 · MINOR · `docs/product-requirements.md:57` · B-мёртвый-указатель +**Срез:** Глоссарий и продуктовые требования — docs/glossary.md · docs/product-requirements.md +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** механизм готов: `tmctl export --pairs` выравнивает пары побайтно по единицам редактуры (`internal/pipeline/export.go:89-93`) +**Почему протухло:** Якорь РОДИЛСЯ верным 02.08 (fbefe33: тогда 89-93 были ровно комментарием «the --pairs column aligned byte-for-byte to how the edit wave concatenated the members») и сгнил дрейфом: сегодня 89-93 — поля структуры TotalUnits/PendingUnits/GhostRows/ConfigDrift, а механизм уехал на 144-152. Линтер якорей его НЕ ловит: форма без `=токена` сверяется только на существование файла. +**Улика — команда:** `sed -n '89,93p' backend/internal/pipeline/export.go; git show fbefe33:backend/internal/pipeline/export.go | sed -n '89,90p'; grep -n 'aligned byte-for-byte' backend/internal/pipeline/export.go` +**Улика — вывод:** TotalUnits int `json:"total_units"` + PendingUnits int `json:"pending_units"` + GhostRows int `json:"ghost_rows,omitempty"` + // ConfigDrift is true when the CURRENT config renders a snapshot different ... +--- fbefe33 (02.08) --- + // F4: the $0 source manifest is authoritative. Under the wave model the export granularity is the EDIT + // UNIT (the editor collapses a unit's member chunks into ONE final text) ... +--- сегодня --- +147: // aligned byte-for-byte to how the edit wave concatenated the members) +**Положительный контроль:** Положительный контроль инструмента: тот же grep находит механизм в том же файле сегодня — `grep -n 'aligned byte-for-byte' backend/internal/pipeline/export.go` → `147:`; и `python3 docs/scripts/counts.py --lint` даёт «0 проблемных якорей», явно объявив «без токена — несверяемы: 721», т.е. слепота линтера подтверждена, а не предположена. +**Предложенное действие:** Заменить якорь на форму с токеном, которую линтер сверяет: (`backend/internal/pipeline/export.go`=`aligned byte-for-byte`) — либо просто снять номера строк, как это уже сделано другими коммитами по файлам живых сессий (df4b064, 11e739d). +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие верное по существу, но НЕ добавлять `backend/` к пути: короткая зонно-относительная форма `internal/pipeline/export.go` — легитимная и поддержана самим линтером (LINT_PREFIXES_RAW включает "backend/", docs/scripts/counts.py:375). Менять надо только хвост: (`internal/pipeline/export.go`=`aligned byte-for-byte`) — тогда якорь переходит из класса «несверяемых 708» в класс «сверено по содержимому 137» и следующий переезд поймает гейт, а не ревизия. + +--- + +## N121 · MINOR · `frontend/README.md:21` · A-дрейф +**Срез:** Доки замороженной зоны фронта — frontend/**/*.md (10 файлов, 5167 строк) +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** Маршрутов семь, и все снимаются (`/showcase` `/scale` `/empty` `/loading` `/error` `/offline` +`/partial`) — маршрут выбирает мир фикстур. +**Почему протухло:** Маршрутов ВОСЕМЬ: S4 завела `/intake`, и он есть в обоих замкнутых тестом списках. Приёмка S4 в том же дереве печатает «8 маршрутов, 11 сценариев». +**Улика — команда:** `sed -n '9,18p' frontend/src/api/scenarios.ts; sed -n '20,29p' frontend/scripts/shot.mjs; grep -o '8 маршрутов, 11 сценариев' frontend/docs/frontend-PROGRESS.md` +**Улика — вывод:** export const scenarios = ['showcase','scale','empty','intake','loading','error','offline','partial'] as const; +const KNOWN_ROUTES = ['/showcase','/scale','/empty','/intake','/loading','/error','/offline','/partial']; +8 маршрутов, 11 сценариев +**Предложенное действие:** «Маршрутов семь … `/partial`» → «Маршрутов восемь … `/showcase` `/scale` `/empty` `/intake` `/loading` `/error` `/offline` `/partial`». +**Разрушающее:** True + +--- + +## N122 · MINOR · `frontend/docs/BACKLOG.md:3` · B-мёртвый-указатель +**Срез:** Доки замороженной зоны фронта — frontend/**/*.md (10 файлов, 5167 строк) +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** они заходят строками единого бэклога через оркестратора (пример: строки 99–102 — пофазный прогресс · манифест глав · машиночитаемая таблица подписи · трасса) +**Почему протухло:** Строк 99–102 в живом `docs/PROGRESS.md` больше нет — все четыре закрыты и по норме журнала («закрытые строки здесь НЕ хранятся») ушли в D-лог. Шапка учит зону примером, который не резолвится. +**Улика — команда:** `for n in 99 100 101 102; do echo -n "row $n: "; grep -c "^| $n |" docs/PROGRESS.md; done; for n in 115 123 160; do echo -n "row $n: "; grep -c "^| $n |" docs/PROGRESS.md; done` +**Улика — вывод:** row 99: 0 +row 100: 0 +row 101: 0 +row 102: 0 +--- positive control --- +row 115: 1 +row 123: 1 +row 160: 1 +**Положительный контроль:** Тот же греп `^| N |` по строкам 115, 123, 160 даёт по 1 хиту — форма таблицы единого бэклога грепается, ноль по 99–102 означает отсутствие строк, а не неверный шаблон. +**Другие носители того же факта:** frontend/docs/frontend-PROGRESS.md:249-250 («единый бэклог, строки 99–100 … строка 101»), frontend/docs/STACK_DECISIONS.md:245-257 (§8 — тот же набор четырёх пунктов) +**Предложенное действие:** Заменить пример на живой (например, строка 169 — проекция банка платформой) либо снять скобку с номерами, оставив механику «запросы к движку идут строками единого бэклога через оркестратора». +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Предложенное действие («заменить пример на живой, например строку 169») ВРЕДНО и я бы его отклонил: живая строка закроется ровно так же, и шапка сгниёт снова — механизм рота встроен в замену. Норма D39.80 существует затем, чтобы исторический номер оставался цитируемым. Верное действие — минимальная приписка, снимающая ложное ожидание у читателя, идущего в живой бэклог: «(пример: строки 99–102 … — все закрыты, тела в D39.122/D39.131)». Второй названный носитель (`frontend-PROGRESS.md:249-250`) — ДРУГОЙ и настоящий дефект класса C: он говорит «**Ждёт движка**» о закрытых строках, и вот его править нужно; он же единственный лежит в файле, куда оркестратор пишет. + +--- + +## N123 · MINOR · `frontend/docs/BACKLOG.md:23` · B-мёртвый-указатель +**Срез:** Доки замороженной зоны фронта — frontend/**/*.md (10 файлов, 5167 строк) +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** Туда же несколько токенов, которые ни разу не используются (`--color-current-line`, `--color-hint`). +**Почему протухло:** Токена `--color-hint` в зоне больше нет вовсе — ни в `tokens.css`, ни в тестах, ни в коде; строка держит хвост на несуществующем токене. Вторая половина (`--color-current-line`, `--font-mono`) по-прежнему верна: оба лежат в именном списке `reserved` теста токенов. +**Улика — команда:** `grep -rn 'color-hint' frontend/src frontend/scripts; grep -rn 'color-current-line\|font-mono' frontend/src | head -6; grep -c '^ --color-' frontend/src/tokens/tokens.css` +**Улика — вывод:** (color-hint: ни одного хита в src/ и scripts/) +src/tokens/tokens.css:22: --color-current-line: #152945; +src/tokens/tokens.css:123: --font-mono: 'JetBrains Mono Variable', ui-monospace, monospace; +src/tokens/tokens.test.ts:246:const reserved = ['--color-current-line', '--font-mono']; +22 (объявленных --color-* токенов в tokens.css) +**Положительный контроль:** Тот же греп по `color-current-line` и `font-mono` в `frontend/src` даёт хиты в tokens.css и tokens.test.ts, а `grep -c '^ --color-' tokens.css` = 22 — инструмент видит токены этого файла; ноль по `color-hint` — факт. +**Другие носители того же факта:** frontend/src/tokens/tokens.test.ts:246 (живой список reserved — без --color-hint) +**Предложенное действие:** Убрать `--color-hint` из перечня Ф-20, оставив `--color-current-line` и `--font-mono` (именно они стоят в списке `reserved` теста токенов). +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Уточнение к формулировке: после снятия `--color-hint` в скобках остаётся ОДИН токен, поэтому «несколько токенов, которые ни разу не используются (`--color-current-line`, `--color-hint`)» должно стать «токен, который ни разу не используется (`--color-current-line`)» — иначе «несколько» повиснет над одним именем и получится новая мелкая неправда. `--font-mono` в этой скобке не перечисляют: он назван отдельным предложением выше. + +--- + +## N124 · MINOR · `frontend/docs/FRONTEND_SESSION_PROMPT.md:2` · B-мёртвый-указатель +**Срез:** Доки замороженной зоны фронта — frontend/**/*.md (10 файлов, 5167 строк) +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** на него +> так ссылаются `S3_SESSION_PROMPT.md` и `frontend/README.md`. +**Почему протухло:** `S3_SESSION_PROMPT.md` в зоне не существует: промты S3/S3.5/S3.7 архивированы 14.08 в `docs/archive/prompts/FRONTEND_S3*`, зонные копии сняты. Довод баннера («файл живой, потому что на него ссылается S3-промт») опирается на исчезнувший носитель. +**Улика — команда:** `find /home/ubuntu/projects/textmachine-main -name 'S3*SESSION_PROMPT*' -not -path '*/node_modules/*'; ls docs/archive/prompts/ | grep -i FRONTEND` +**Улика — вывод:** (find: пусто) +FRONTEND_S2_SESSION_PROMPT.md +FRONTEND_S35_SESSION_PROMPT_2026-08-09.md +FRONTEND_S37_SESSION_PROMPT_2026-08-10.md +FRONTEND_S3_SESSION_PROMPT_2026-08-04.md +**Положительный контроль:** find frontend/docs docs/archive/prompts -name '*SESSION_PROMPT*' → 20+ файлов, включая frontend/docs/FRONTEND_SESSION_PROMPT.md и четыре FRONTEND_S* в архиве. Инструмент в этой области находит промты; ноль по `S3*SESSION_PROMPT*` — факт, а не опечатка. +**Другие носители того же факта:** frontend/docs/frontend-PROGRESS.md:52 (`S3_SESSION_PROMPT.md`), frontend/docs/frontend-PROGRESS.md:80 (`S35_SESSION_PROMPT.md` «эта папка»), frontend/docs/frontend-PROGRESS.md:208 (`S37_SESSION_PROMPT.md`), frontend/docs/frontend-PROGRESS.md:147 (`S4_SESSION_PROMPT.md` «эта папка» — уехал в frontend/docs/archive/) +**Предложенное действие:** «ссылаются `S3_SESSION_PROMPT.md` и `frontend/README.md`» → «ссылается `frontend/README.md`; промты S2–S3.7 архивированы в `docs/archive/prompts/FRONTEND_S*`». +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие верно, но ⚠ это НЕ повод трогать сам баннер как «протухший»: баннер — механизм, и он работает; ложна ровно одна ссылка внутри него. Правка минимальная и должна СОХРАНИТЬ довод («файл живой как носитель норм»), заменив мёртвое имя на архивное: «ссылается `frontend/README.md`; промты S2–S3.7 архивированы в `docs/archive/prompts/FRONTEND_S*`, S4 — в `frontend/docs/archive/`». Список «других носителей» в находке — НЕ ошибки: `frontend-PROGRESS.md:52/80/147/208` это ХРОНИКА, ссылки на промты в момент их выдачи; переписывать хронику под сегодняшние пути запрещено нормой (историческое). Максимум — одна сноска в журнале «промты S2–S4 архивированы, пути см. `docs/archive/prompts/`». + +--- + +## N125 · MINOR · `frontend/docs/STACK_DECISIONS.md:111` · B-мёртвый-указатель +**Срез:** Доки замороженной зоны фронта — frontend/**/*.md (10 файлов, 5167 строк) +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** | Браузер | `@playwright/test 1.62.1` (точный пин) | +**Почему протухло:** Зона зависит от пакета `playwright` 1.62.1, а не от `@playwright/test`: последнего нет ни в `package.json`, ни в дереве `node_modules`. Сессия, поставившая пин буквально, добавит вторую копию браузерного рантайма. +**Улика — команда:** `grep -n 'playwright' frontend/package.json; ls -d frontend/node_modules/@playwright; node -e "console.log(require('./frontend/node_modules/playwright/package.json').version)"` +**Улика — вывод:** 35: "@axe-core/playwright": "4.12.1", +53: "playwright": "1.62.1", +ls: cannot access 'frontend/node_modules/@playwright': No such file or directory +1.62.1 +**Положительный контроль:** `ls -d frontend/node_modules/playwright` → каталог есть, версия 1.62.1; `grep -c '@playwright/test' frontend/package.json` → 0 при том, что греп по `playwright` в том же файле даёт две строки. Инструмент в этой области находит пакеты — отсутствие @playwright/test не артефакт грепа. +**Другие носители того же факта:** frontend/docs/FRONTEND_PLAN.md:122 +**Предложенное действие:** В таблице §3 и в таблице пинов `FRONTEND_PLAN.md:122` заменить `@playwright/test` на `playwright` (плюс `@axe-core/playwright 4.12.1`, который несёт axe-половину скриншот-цикла). +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие верно; добавить причину, иначе следующая сессия «починит» обратно: тест-раннер `@playwright/test` зоне не нужен ПО ПОСТРОЕНИЮ — тесты гоняет `vitest` (шестой шаг `check`), а playwright работает библиотекой из `scripts/shot.mjs`. Формулировать как «`playwright 1.62.1` (библиотека, не тест-раннер: тесты на vitest) + `@axe-core/playwright 4.12.1`». Второй носитель `FRONTEND_PLAN.md:122` назван верно, я его прочитал. + +--- + +## N126 · MINOR · `frontend/docs/STACK_DECISIONS.md:116` · E-второй-носитель +**Срез:** Доки замороженной зоны фронта — frontend/**/*.md (10 файлов, 5167 строк) +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** Нормативная поверхность — OpenAPI 3.1 в `docs/api-contract/openapi.yaml`: spectral линтует её +шестым шагом `npm run check` +**Почему протухло:** Шагов у `check` шесть, и spectral — ЧЕТВЁРТЫЙ. Тот же файл на строке 148 уже утверждает «шесть шагов», то есть носитель противоречит сам себе; вторая копия ошибки — `FRONTEND_PLAN.md:124`. +**Улика — команда:** `node -e "const p=require('./frontend/package.json');console.log(p.scripts.check.split('&&').map((s,i)=>(i+1)+': '+s.trim()).join('\n'))"` +**Улика — вывод:** 1: prettier --check . +2: eslint . --max-warnings 0 +3: stylelint "src/**/*.css" +4: npm run contract +5: tsc --noEmit +6: vitest run +**Другие носители того же факта:** frontend/docs/FRONTEND_PLAN.md:124, frontend/docs/STACK_DECISIONS.md:144-149 (там же верная редакция — «шесть шагов») +**Предложенное действие:** В обоих носителях «шестым шагом» → «четвёртым из шести шагов». ⚠ Уже названо как ФС-6 в фикс-листе приёмки S4 (frontend-PROGRESS.md:469) — строка не исполнена. +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Список носителей НЕПОЛОН: помимо `STACK_DECISIONS.md:116` и `FRONTEND_PLAN.md:124` та же ошибка стоит ТРЕТИЙ раз — `frontend/docs/BACKLOG.md:16` (Ф-14: «spectral шестым шагом `check`, openapi-typescript генерит `src/api/schema.ts`»). Правя два, оставят третий. ⚠ И ещё: во всех трёх рядом стоит «нормативная поверхность — `docs/api-contract/openapi.yaml`» — а это на время фриза НЕВЕРНО (пинг №17, дописка: «зеркало = байт-копия временно НЕ действует, нормативен только канон `docs/architecture/14-api-contract/`»). Правку «четвёртым из шести» стоит вносить одним касанием с этой оговоркой, иначе исполнитель перечитает строку и унаследует вторую ложь. + +--- + +## N127 · MINOR · `frontend/docs/frontend-PROGRESS.md:221` · C-исполненное-как-открытое +**Срез:** Доки замороженной зоны фронта — frontend/**/*.md (10 файлов, 5167 строк) +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** **S4 (загрузка книги и запуск прогона на контракте 0.2.3, + библиотека на масштабе, находки приёмки S3.7 — ждёт приёмки)**. +**Почему протухло:** Приёмка S4 состоялась 15.08 и записана В ЭТОМ ЖЕ ФАЙЛЕ строкой 465 («ПРИНЯТО и залендено `267aa35`, D39.135»). Два места одного файла говорят разное. +**Улика — команда:** `sed -n '220,221p' frontend/docs/frontend-PROGRESS.md; sed -n '465p' frontend/docs/frontend-PROGRESS.md` +**Улика — вывод:** 220: код зоны на английском; D39.129)**, **S4 (загрузка книги и запуск прогона на контракте 0.2.3, +221: библиотека на масштабе, находки приёмки S3.7 — ждёт приёмки)**. +465:### 15.08 — приёмка S4 оркестратором №17: ПРИНЯТО и залендено (`267aa35`); контракт 0.2.3 в каноне (D39.135) +**Предложенное действие:** «— ждёт приёмки» → «— ПРИНЯТ 15.08, D39.135; фикс-лист ФС-1…ФС-12 остаётся входом первого касания». +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Единственная из пятнадцати находок, чей носитель — ЗОННЫЙ ЖУРНАЛ, куда оркестратор пишет по решению владельца (CLAUDE.md, таблица ролей). Её можно исполнить сейчас, не дожидаясь разморозки, — в отличие от остальных четырнадцати. Заодно поправить в той же строке 249-250 («**Ждёт движка:** манифест глав и пофазный прогресс — строки 99–100, к S5 машиночитаемая таблица подписи — строка 101»): это тот же класс C в том же файле, строки закрыты D39.122, а машинная таблица с тех пор ещё и снесена D39.158. + +--- + +## N128 · MINOR · `platform/deploy/README.md:428` · C-исполненное-как-открытое +**Срез:** Доки зоны платформы: platform/README.md · platform/deploy/README.md · platform/docs/{ENGINEERING_STANDARDS,PLATFORM_DIRECTION,STACK_DECISIONS}.md · platform/docs/p8-review/* · platform/docs/p9/* +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** ⚠ Выбирающего путь кода ещё нет: `Supervisor.Workdir` задаёт вызывающий, а вызывающий — воркер, +**Почему протухло:** Воркер существует с пака P4: прогон спавнится в `internal/runs/spawn.go`, который задаёт `Workdir: l.Workdir` транзиентному юниту, а корень УЖЕ настройка — `TM_PLATFORM_BOOKS_DIR` (обязательный абсолютный путь), от которого каталог книги строится как `filepath.Join(s.Cfg.BooksDir, id)` и сохраняется колонкой `books.workdir`. То есть обещанное «когда он появится, корень становится настройкой» произошло. Заодно мёртв и указатель: «строка 103 единого бэклога» закрыта (D-нота «строки 103/135/165/102 закрыты»), а живой бэклог закрытых строк не хранит. Сегодня `ingest.Supervisor` вообще не имеет боевого вызывающего — он остался дев-путём, о чём этот же файл честно пишет строкой выше («Дев-путь супервизора с группой процессов остаётся дев-путём»). +**Улика — команда:** `grep -rn 'Workdir:' platform/internal/runs/spawn.go | head -2; grep -n 'dir := filepath.Join(s.Cfg.BooksDir, id)' platform/internal/books/books.go; grep -rn 'ingest.Supervisor\|Supervisor{' --include=*.go platform/ | grep -v _test | wc -l; printf 'row 103 in live backlog: %s\n' $(grep -c '^| 103 ' docs/PROGRESS.md)` +**Улика — вывод:** 168: Args: runner.TranslateArgs(l.Workdir, l.VerifyBank, s.Cfg.KeysFile, +170: Workdir: l.Workdir, +171: dir := filepath.Join(s.Cfg.BooksDir, id) +0 +row 103 in live backlog: 0 +**Положительный контроль:** `grep -c "^| 175 " docs/PROGRESS.md` → 1: шаблон грепа находит живые строки бэклога (175/202/224 = 1), значит ноль по 103 — отсутствие строки, а не отказ инструмента. +**Другие носители того же факта:** platform/docs/PLATFORM_DIRECTION.md:101 (та же закрытая строка 103 как «носитель работы движка») +**Предложенное действие:** Заменить абзац на действующий факт: корень библиотеки — настройка `TM_PLATFORM_BOOKS_DIR` (обязательный абсолютный путь), каталог книги = `BooksDir/`, воркер спавна — `internal/runs/spawn.go`; `ingest.Supervisor` остался дев-путём. Ссылку на «строку 103» снять или заменить номером D-ноты, закрывшей её. +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие принимается, с одной поправкой по существу: `TM_PLATFORM_BOOKS_DIR` не имеет дефолта — пустое значение означает «инстанс не принимает загрузок и `POST /books` вообще не монтируется» (`config.go:159-166`). Поэтому нельзя писать «её дефолт — этот каталог»: правильная формулировка — «`/srv/textmachine` это РЕКОМЕНДУЕМОЕ значение для юнита, а не дефолт кода; пусто = загрузки выключены». Иначе новая редакция заведёт ровно тот же класс дрейфа, что чинит. + +--- + +## N129 · MINOR · `platform/docs/PLATFORM_DIRECTION.md:101` · B-мёртвый-указатель +**Срез:** Доки зоны платформы: platform/README.md · platform/deploy/README.md · platform/docs/{ENGINEERING_STANDARDS,PLATFORM_DIRECTION,STACK_DECISIONS}.md · platform/docs/p8-review/* · platform/docs/p9/* +**Вердикт пере-суда:** ПОДТВЕРЖДЕНО +**Цитата (дословно из дока):** **Носитель работы движка — строка 103 единого бэклога** (словарь событий); заводится +**Почему протухло:** Указатели на строки единого бэклога, розданные по всему срезу, в шести случаях целят в строки, которых в живой таблице БОЛЬШЕ НЕТ: правило бэклога — «Закрытые строки здесь НЕ хранятся — они уходят в D-лог с номером решения» (docs/PROGRESS.md:59). Мертвы 100, 102, 103, 139, 145, 165, 174, 199, 200, 211. Часть из них цитируется как ПРОВЕНАНС (это терпимо), но три подаются как ЖИВОЙ носитель незакрытой работы и потому читаются как открытый долг: 101 («Носитель работы движка — строка 103», словарь денежных событий), 265 («Персист манифеста глав (строка 100) — несущий для латентности чтения» — read-модель построена и персистит), и deploy/README.md:430. Читатель, пошедший по номеру, находит пустоту и не может отличить «закрыто» от «потеряно». +**Улика — команда:** `for n in 100 102 103 139 145 165 174 199 200 211 175 202 224; do printf 'row %-4s: %s\n' $n $(grep -c "^| $n " docs/PROGRESS.md); done; sed -n '59p' docs/PROGRESS.md | cut -c1-70` +**Улика — вывод:** row 100 : 0 +row 102 : 0 +row 103 : 0 +row 139 : 0 +row 145 : 0 +row 165 : 0 +row 174 : 0 +row 199 : 0 +row 200 : 0 +row 211 : 0 +row 175 : 1 +row 202 : 1 +row 224 : 1 +Закрытые строки здесь НЕ хранятся — они уходят в D-лог с номером р +**Положительный контроль:** Тот же греп на 175/202/224 даёт по 1 — шаблон работает и живые строки находит; ноль по остальным означает удалённую (закрытую) строку, а не сломанный инструмент. +**Другие носители того же факта:** platform/docs/PLATFORM_DIRECTION.md:265 (строка 100), platform/deploy/README.md:430 (строка 103), :186 заголовок «строка 174», platform/docs/STACK_DECISIONS.md:246 (строка 165), :311 (строка 102), :414 (строка 199а), :348 (строка 200), :541 (строка 211), :545 (строка 145), :539/deploy:63 (строка 139) +**Предложенное действие:** Пройти по десяти номерам: где строка закрыта, дописать рядом номер закрывшей D-ноты («строка 103, закрыта D39.xxx») — тогда указатель ведёт в D-лог, как того и требует правило бэклога; где номер подан как незакрытый долг (101, 265, deploy:430) — пере-написать по факту исполнения. +**Разрушающее:** True +**⚠ Опровергатель поправил действие:** Действие верно по существу, координаты — нет: перед правкой пере-грепнуть носители, потому что шесть из семи номеров строк в other_carriers промахиваются. Действующие адреса: `platform/docs/STACK_DECISIONS.md` 267 (165), 317 (102), 357 (200), 414 (199), 547 (211), 554 (145), 560 (139); `platform/deploy/README.md` 64 (139), 179 (174), 430 (103); `platform/docs/PLATFORM_DIRECTION.md` 101 (103), 265 (100). Плюс замечание вне среза: тот же мёртвый номер несёт КОД — `backend/cmd/tmctl/main.go:72` («run `tmctl migrate` (row 174)»); правка доков его не лечит, это пинг бэкенду, а не ход оркестратора. + +--- + +## N130 · MINOR · `platform/docs/STACK_DECISIONS.md:566` · E-второй-носитель +**Срез:** Доки зоны платформы: platform/README.md · platform/deploy/README.md · platform/docs/{ENGINEERING_STANDARDS,PLATFORM_DIRECTION,STACK_DECISIONS}.md · platform/docs/p8-review/* · platform/docs/p9/* +**Вердикт пере-суда:** (не выносилось) +**Цитата (дословно из дока):** Админ-команды: `tmplatformctl grant --user --usd 5 [--note ...] [--key ...]` · +**Почему протухло:** Перечень админ-команд отстал от CLI на семь глаголов. В диспетчере `cmd/tmplatformctl/main.go` сегодня: grant · adjust · balance · logins · revoke · book add · book refresh · books · runs · run abandon · seed · exit-marker. В списке §«Как поднять локально» нет `adjust`, `book add`, `book refresh`, `books --abandoned`, `runs [--stalled]`, `run abandon`, `exit-marker`. `platform/README.md:29-34` несёт ПОЛНЫЙ и верный список, `deploy/README.md` описывает `runs`/`run abandon`/`books --abandoned` — то есть один и тот же факт разложен по трём носителям и разошёлся ровно там, куда отсылает «Как поднять локально». Оператор, поднявший стенд по этому разделу, не узнает из него о двух командах терминального вердикта, ради которых пак P8-FIX и существовал. +**Улика — команда:** `grep -n 'case "' platform/cmd/tmplatformctl/main.go | tr -d '\t'; grep -n 'args\[0\] == "exit-marker"' platform/cmd/tmplatformctl/main.go` +**Улика — вывод:** 91:case "grant": +93:case "adjust": +95:case "balance": +97:case "logins": +99:case "revoke": +101:case "book": +106:case "add": +108:case "refresh": +112:case "books": +114:case "runs": +116:case "run": +121:case "seed": +70: if args[0] == "exit-marker" { +**Другие носители того же факта:** platform/README.md:29-34 (полный и верный список), platform/deploy/README.md:277-320 (runs / runs --stalled / run abandon / books --abandoned / book refresh), platform/docs/STACK_DECISIONS.md:80-84 (§10, «ради пяти операций» — историческое обоснование P1, трогать не надо) +**Предложенное действие:** Либо дополнить перечень 566–569 недостающими семью глаголами, либо (лучше — у факта должен быть один носитель) заменить его одной строкой-ссылкой на `platform/README.md` §«Бинари» и `deploy/README.md` §«Застрявшая работа», как уже сделано с числом гейтов батареи. +**Разрушающее:** False diff --git a/docs/DOC_REVISION_REFUTED.md b/docs/DOC_REVISION_REFUTED.md new file mode 100644 index 00000000..9060e091 --- /dev/null +++ b/docs/DOC_REVISION_REFUTED.md @@ -0,0 +1,23 @@ +# СНЯТЫЕ ОПРОВЕРГАТЕЛЕМ — НЕ ЧИНИТЬ (7) + +## `docs/architecture/09-target-architecture.md:181` +**Почему находка неверна:** Опровергнуто на СВОЁМ носителе автора. Автор предъявил реестр как доказательство закрытия, но легенда реестра (product-requirements.md:5) читает 🔶 как «ЧАСТИЧНО», а не «построено»: ПТ-18 (H10) = 🔶 с прямо названным остатком «продукт-кнопки и связка с прескрином = строка 94», ПТ-19 (H9) = 🔶 с остатком «замер скорости и ETA на целой книге = строка 54». Строка 94 бэклога в PROGRESS.md:239 держит кнопки стоп/продолжить ОТКРЫТЫМИ. Значит из трёх заявленных закрытий подтверждается ОДНО — H15 (ПТ-9 = ✅; терминолог реально в коде: backend/internal/pipeline/terminologist.go, backend/internal/terminology/*, KWIC в terminology.go). Заголовок находки «три из семи закрыты» — а на нём стоит severity major — ложен в двух случаях из трёх, и предложенная правка вписала бы в ратифицированный док НОВОЕ ложное утверждение «H9/H10 закрыты», прямо спорящее с 🔶 в реестре и с открытой строкой 94. Дополнительно: blame показывает, что строка 181 — нетронутый текст 13.07, покрытый общей оговоркой строки 14 «тело ниже = замысел арх-ресета 13.07», то есть это карта находок аудита, а не живой статус-борд. + +## `docs/architecture/09-target-architecture.md:48` +**Почему находка неверна:** Опровергнуты обе несущие посылки находки, хотя её кодовый факт я подтвердил. (1) Указатель самой находки битый: «⚠-баннер §2 строк 145–150» не существует — реальный баннер §2 стоит на строках 94–99, а 145–150 это тело «Слой 4»/«Слой 5». Находка, которая опирается на образец правки по несуществующим строкам, воспроизводима принимающим как ошибка. (2) Класс D («противоречие ратифицированному») не держится: D39.7 ратифицировал «граница — НЕ рычаг НА ЭТОМ МАТЕРИАЛЕ», D39.8 — «сцен-детекция/carryover-машинерия НЕ строятся ДЛЯ КНИГ ЭТОГО КЛАССА». Обе формулировки СКОУПЛЕНЫ книгами класса zh-вебновелла, а §0.1 — ровно инвариант про ДРУГИЕ классы книг («не смеет предполагать структуру конкретной книги»). Вписать в §0.1 «ось стратегии границы замерена и не строится» значило бы обобщить класс-скоупленный замер на весь спектр — та самая ошибка, которую §0.1 называет ЗАПРЕЩЁННОЙ («книга плоская → урежем нарезку»). (3) Посылка «§0.1 не покрыт оговоркой» неточна: blame показывает строку 48 как нетронутый текст 13.07, а строка 14 несёт общую оговорку «Баннер — не переписывание, тело ниже = замысел арх-ресета 13.07», покрывающую всё тело ниже шапки, включая §0.1; сам заголовок §0.1 датирован «(поправка владельца, 2026-07-13)». ПОДТВЕРЖДАЮ только кодовую половину: в backend/internal/chunk/ нет ни выбора стратегии границы, ни фоллбек-лестницы (греп 'logical|Logical' по пакету пуст при положительном контроле grep -c greedy backend/internal/chunk/chunker.go → 2; в backend/internal/config/ нет оси Boundary при положительном контроле — оси Segmentation/DraftBudgetOut там есть, pipeline.go:113–126). + +## `docs/architecture/17-seam-inbound-law.md:74` +**Почему находка неверна:** Находка построена на ДОК-КОММЕНТАРИИ, а не на механизме. Прочёл renderDecisions целиком (httpapi/bank.go:331-357): в ней нет ни одного switch и ни одной map — она проецирует ФОРМУ (null→0, presence-флаги), а слова передаёт как есть. И передавать их как есть законно, потому что на этом направлении словари СОВПАДАЮТ побайтно: wire-действия `approve`/`decline` равны membank.ActionApprove/ActionDecline (decisions.go:61-62), а закрытый wire-словарь `termKinds` — {name,place,title,term,nickname} — это ровно тот набор, который читающая таблица termKind пропускает без перевода. Обратного отображения контракт→движок в платформе НЕТ ВООБЩЕ: греп по контрактным словам находит только константы читающей таблицы, при рабочем положительном контроле (тот же класс грепа находит движковые auto/draft/seed/ruby/mined в bank.go:113-129). Значит утверждение дока «перевод односторонний, на чтении» СЕГОДНЯ ВЕРНО, и якорь на bank.go:15-21 жив (токен на :16). Исполнение находки вписало бы в ратифицированный закон ЛОЖЬ («перевод ДВУНАПРАВЛЕННЫЙ») — ровно тот вред, ради которого пересуд и существует. + +## `docs/architecture/03-implementation-notes.md:5` +**Почему находка неверна:** Ни одно утверждение баннера сегодня не ложно — я проверил каждое исполнением, значит класс A (дрейф факта) не выполнен. Баннер — ⚠-датированная запись о том, ЧТО СДЕЛАЛ ПАК-15 25.07; вписывать в неё bookfile (30.08) и standdata (27.08) значит приписать паку-15 то, чего он не делал, то есть заменить верную историческую запись на ложную. Премисса находки («док читается как карта кода») тоже не подтвердилась: и CLAUDE.md, и backend/README.md:3 адресуют 03 за КОНТРАКТАМИ Фазы 0 «через баннер», а карта пакетов лежит в backend/README.md, который бэкенд читает ПОЗИЦИЕЙ РАНЬШЕ. Второй носитель (12-go-style-notes:22) говорит о ратификации сплита и композит-корне — тоже не инвентарь пакетов. + +## `CLAUDE.md:84` +**Почему находка неверна:** Фактическая половина находки воспроизводится (файл tracked с 9bb00d4, шесть коммитов, последний 8afc2e7 29.08) — но вывод «класс A, док утверждал верное и сгнил» неверен по трём независимым уликам. (1) ХРОНОЛОГИЯ УБИВАЕТ КЛАСС A: правило впервые записано в CLAUDE.md 01.08 (87a0124), когда файл был под git уже четыре недели (с первого коммита 04.07). Утверждение, которое НИКОГДА не было верным как факт об индексе, не может «быть верным когда-то и стать ложным сегодня»; строка — НОРМА действия («не коммить»), а не описание состояния дерева. (2) ПРОЕКТ ЯВНО ЗНАЕТ ОБА ФАКТА И ПРИМИРИЛ ИХ МАШИНОЙ: живой отслеживаемый хук frontend/scripts/githooks/pre-commit:11-13 дословно пишет «START_PROMT.MD is tracked and often modified — a bare `git commit -a` would carry it away in silence» и блокирует файл в стейдже. Tracked-статус — не противоречие правилу, а ЕГО ПРИЧИНА: именно потому, что файл tracked и почти всегда модифицирован, голый коммит унёс бы его молча. Правило не протухло — оно вооружено. (3) КОММИТ 29.08 ПРЕДПОЛАГАЕТ ПРАВИЛО ЖИВЫМ, А НЕ ОТМЕНЯЕТ ЕГО: его собственное сообщение — «on his explicit instruction and against the standing guardrail», то есть разовое переопределение владельцем стоящей нормы; D-ноты, отменяющей норму, в живом логе и реестре нет (грепнуто при позитивном контроле 142 хита). Единственное живое упоминание в D-логе (строка 694) норму ПОДТВЕРЖДАЕТ. Плюс два дефекта в самой находке: (а) её собственный указатель битый — строка 694 принадлежит D39.157 (заголовок на 680), а не «D39.158 п.7» (заголовок на 696); (б) «цена уже уплачена» подменяет факт: reset --mixed 24.08 сделан «ПО ЕГО СЛОВУ» (владельца), дерево сверено побайтно по 20 файлам — правило отработало как задумано, а не навредило. + +## `docs/glossary.md:37` +**Почему находка неверна:** Находка перевёрнута: короткая форма — не описка, а ГОСПОДСТВУЮЩАЯ конвенция проекта, а выбивается ровно тот носитель, который находка объявила «верным». Счёт по живым докам: `configs/langpacks/` без префикса — 17 вхождений (в т.ч. CLAUDE.md:70 в §Гардрейлы, 09-target-architecture.md:125, 10-prompt-architecture.md:46, 12-go-style-notes.md, 13-tech-debt-anchors.md:146), `backend/configs/langpacks/` — ОДНО, и это как раз docs/glossary.md:47. Указатель не мёртв: собственный линтер проекта резолвит зонно-относительные якоря списком LINT_PREFIXES_RAW (в нём есть "backend/" и "backend/configs/"), комментарий рядом называет их «ЗОННО-ОТНОСИТЕЛЬНЫЕ якоря… их в корпусе больше пятнадцати», и --lint даёт 0 проблем. Опора находки на CLAUDE.md §Онбординг тоже не держит: та фраза («пути от КОРНЯ репозитория») стоит в пункте 3 маршрута чтения и относится к путям ДОКОВ в онбординг-списке, тогда как тот же CLAUDE.md пятью десятками строк выше сам пишет `configs/langpacks/`. Класс B не подтверждается: цель существует, резолвится инструментом и написана по канону. + +## `platform/deploy/README.md:30` +**Почему находка неверна:** Находка исходит из посылки, что путь в доке резолвится ОТ КАТАЛОГА ФАЙЛА. В этом репозитории посылка ложна, и это доказывается самим же файлом. `platform/deploy/README.md` пишет `internal/login/dev.go` и `internal/config/config.go` — от `platform/deploy/` каталога `internal` НЕ существует (проверено: MISSING), а от корня зоны оба файла на месте. То есть весь док последовательно пишет пути ОТ КОРНЯ ЗОНЫ `platform/`, и `docs/STACK_DECISIONS.md` — ровно та же форма, а не опечатка и не копипаст. Конвенция не самодельная: ратифицированный гейт якорей `docs/scripts/counts.py` резолвит путь перебором множества префиксов, где `platform/` стоит явно (`LINT_PREFIXES_RAW`), и на всём корпусе даёт «0 проблемных якорей в 100 живых доках». Прецедент из CLAUDE.md, на который опирается находка, — про другое: там пути не резолвились НИ от корня, НИ по конвенции. Наконец, все три вхождения — бэктик-текст, а не markdown-ссылка, так что «переход по ссылке из редактора промахивается» тоже не воспроизводится: переходить не по чему. + diff --git a/docs/research/15-voice-and-state.md b/docs/research/15-voice-and-state.md index 670d8dd8..64136899 100644 --- a/docs/research/15-voice-and-state.md +++ b/docs/research/15-voice-and-state.md @@ -18,7 +18,7 @@ 1. **Клейм antipattern-дока «voice exemplars > прилагательных-дескрипторов» в универсальной форме НЕ подтверждён** — прямого сравнения для голоса персонажа не существует нигде (honest negative), а два ближайших прямых сравнения разнонаправлены. Но количественное ядро есть: **RoleLLM (Findings ACL 2024): описание роли 9.85% win-rate → +демо инлайном 30.19% → те же демо диалоговыми ходами 61.79%** *(ревью: числа arXiv-v1, судья GPT-3.5; в камере-реди ACL — 9.3→29.8→63.3, судья GPT-4, картина та же)* — exemplars главный рычаг, причём **формат важнее наличия**; **дистиллированный из реплик «voice sheet» бьёт сырой RAG-пример** (87/78% vs 39% win-rate); насыщение **~5 реплик**; **отбор по семантической близости ВРЕДИТ** (AA 69.3→36.0 — Enron, максимальное из четырёх падений; направление на всех датасетах). Итог: строить **гибрид** «1 строка регистра + 3–5 коротких разнообразных RU-реплик», не выбирать между экземплярами и дескрипторами. Наша проба на 蛊真人 подтвердила направление (§Пробы): гибрид — единогласный ранг-1 у судей двух семейств; детерминированный маркер (самоуничижение служанки 奴婢) — экземпляры/гибрид 4/4 vs база 1/4. 2. **Голос выигрывается на ДРАФТЕ, не на редактуре** (проба P3): монолингвальный grok-4.3 с узким мандатом вернул черновик **дословно** в 3 из 4 конфигураций — он не сгладил голос (страх §14 внешнего дока на этом сэмпле не воспроизвёлся), но и **не восстановил** сплющенный: voice-блок редактора не заставил его вернуть потерянное самоуничижение служанки. Инъекция голоса нужна переводчику; редактору — только констрейнты сохранения. 3. **Прайор-арт: «аналогов не найдено» стратревью — сузить, не снять.** Пер-персонажный голос в промышленном MT сериальной фикции **существует как продукт-клейм** (NAVER Webtoon «캐릭터 인식 번역» с БД речевых стилей, пресса 05.2026; Mantra — 口調-консистентность длинным контекстом; XL8 — файнтюн на серию), но **ни один механизм не раскрыт** публикацией или патентом. Детерминированный no-LLM словарный слой — массовая практика фан-стека (GalTransl 译前/译后/条件字典, LunaTranslator, SakuraLLM `src->dst #info`) — «учебник» найден; селективная инъекция по матчу — тоже НЕ уникальна (GalTransl/AiNiee). **Остаются уникальными:** спойлер-окна since/until (нигде, вкл. CAT и патентные индексы — скрининг, не FTO; ближайшее — NovelCrafter Codex Progressions, писательский тул), scene-state инъекция в MT, voice-exemplars в MT, и сама формула «disposition + окна + бюджет + детерминизм». -4. **Оси «голос персонажа» нет ни в одном бенче литературного MT** (DITING, WMT24-Literary, BlonDe — CONFIRMED), и потерю различимости голосов при MT никто не измерял — наш voice-флаггер и BWS-рука голоса будут первым замером. Измерительная база: детерминированные маркеры (ты/вы — тривиальный морфоматч; маркер-листы; NG-лексика) + **mStyleDistance** (Findings ACL 2025, 9 языков вкл. русский, MIT, 0.3B — но качество умеренное, AV ROC-AUC ~0.66 → только мягкий флаггер, не гейт). +4. **Оси «голос персонажа» нет ни в одном бенче литературного MT** (DITING, WMT24-Literary, BlonDe — CONFIRMED), и потерю различимости голосов при MT никто не измерял — наш voice-флаггер и BWS-рука голоса будут первым замером. Измерительная база: детерминированные маркеры (ты/вы — тривиальный морфоматч; маркер-листы; NG-лексика) + **mStyleDistance** (Findings ACL 2025, 9 языков вкл. русский, MIT, 0.3B — но качество умеренное, AV ROC-AUC ~0.66 → только мягкий флаггер, не гейт; ⚠ **само среднее 0.66 снято на ЧУЖИХ языках — ru-специфичных AV-чисел работа не публикует, см. §«Честные слабые места» п.4 этого файла**, испр. оркестратором №22 01.09). 5. **Scene-state: полная схема — ниша пуста** (никто не инъектил «кто где/с кем/что скрывает» в MT с замером). Доказанный числом срез один: **направленный регистр пары** — 37% контекстных ошибок En→Ru это деиксис, **67% из них — ты/вы** (Voita ACL 2019); контекст поднимает точность 50→83.5%; formality-тег управляет с 73–92% acc (CoCoA-MT/IWSLT; *ревью: огибающая, RU там только zero-shot*). Второй опорный факт — **консистентность и качество развязаны** (DelTA, ICLR 2025, после сверки первоисточника: абляция Proper-Noun-Records +3.95 пп LTCR-1 при −0.02 sCOMET; на вебновеллах Guofeng +15.5…+32.7 пп сверх контекстного бейзлайна при ~+0.24 sCOMET) → выигрыш банка/реестров виден только специализированной осью, общие метрики его не покажут. Эпистемику/ложь в схему НЕ переносить: перевод сохраняет ложь исходника бесплатно. 6. **Эпистемическое состояние читателя в переводе — ниша пуста** (spoiler-aware MT: 0 работ). Механика у нас уже есть: reveal-точки = те же спойлер-окна (`reveal_ch` + pre/post-reveal алиасы «таинственный незнакомец»→«её брат»); механический спойлер-канал zh→ru — **род русского прошедшего времени** (перевод из безродового языка принудительно раскрывает пол). Имплицитному трекингу «кто что знает» доверять нельзя (FANToM: человек 87.5% vs GPT-4+CoT 26.6%); прецедент инъектируемой модели читателя — StoRM (Findings EMNLP 2022, генерация). Переводоведение фиксирует наш риск качественно: эксплицитация — универсалия перевода — снижает саспенс. 7. **Промптинг:** согласованное ядро вендоров — структурные блоки (XML хорошо, **JSON — худший** в long-context по замеру OpenAI), позитивные формулировки вместо голых запретов, reasoning-моделям не нужен ручной CoT; «сэндвич» инструкций (повтор ключевых констрейнтов после чанка) — дёшево и вендор-подтверждено. **NO_CHANGE редактору давать обязательно** (дефолт LLM — пере-редактирование: P51/R63 на GEC; правка безошибочного текста в минус COMET), но долю NO_CHANGE не читать как сигнал — эффект «опция-артефакт» (плацебо-опции срабатывают как «Unknown»: −15.75 пп, абстенция 32.9%). **«Двухпроходность в одном вызове» — арм НЕ ставить:** выигрыш мульти-проходности даёт refinement на готовом черновике (+1.05 COMET первый проход — это наш draft→editor), план/декомпозиция незначимы или в минус, DRT — файнтюн с ~12× output-токенов; self-check в том же completion наследует галлюцинации черновика. @@ -34,7 +34,7 @@ - **SAMAS (arXiv:2602.19840, 23.02.2026) — поправка постановки: это НЕ speaker-aware метод.** «Spectrum-Guided Multi-Agent System for Style Fidelity in Literary Translation»: 81-мерный спектр из wavelet-преобразования **длин слов** детерминированно роутит текст в один из промпт-воркфлоу 6 агентов. Авторский стиль, не голоса персонажей; семантика мерена на нелитературных FLORES/WMT24, стиль — только human head-to-head без эффект-сайзов; неверный стилевой воркфлоу роняет ~5.5 XCOMET (абляция). **CONFIRMED** (препринт). Переносим только паттерн «дешёвый детерминированный сигнал → выбор промпт-варианта» — он проекту уже родной; сам метод не копировать. - **Оси «голос/регистр говорящего» нет ни в одном стандартном бенче** литературного/вебновелльного MT: DITING (6 осей, 18.7k экспертных zh→en пар), WMT24 Literary (general quality + discourse awareness), BlonDe (entity/pronoun/tense). **CONFIRMED** (peer-reviewed). Прямого замера потери различимости голосов при MT (per-character stylometry источник vs перевод) не найдено — измерено только лексическое обеднение (MTLD 96→90 en→nl при MT, 2408.17308). **CONFIRMED-absence.** → Наша voice-ось — первый замер, строить самим (см. §1.5, пилот). -- **Измерительная технология существует, но EN-центрична:** стилевые эмбеддинги StyleDistance (2410.12757) — EN-only; **разрешение через добор: mStyleDistance (Findings ACL 2025) обучен на 9 языках ВКЛЮЧАЯ русский** (~40 стилевых фич, xlm-roberta-base 0.3B, MIT на HF) — но качество умеренное (AV ROC-AUC avg 0.66; STEL-or-Content: formality 0.71, остальные оси 0.30–0.37) → пригоден как **мягкий флаггер дрейфа, не гейт**. Персонажные эмбеддинги работают на фикшене: UAR+DramaCV — SOTA атрибуции цитат на 28 английских романах (Findings EMNLP 2024). Speaker-verification-as-eval: роль-плей LLM систематически **проваливает** верификацию спикера (2405.10150) — паттерн «различимы ли реплики без атрибуции» валиден как детектор потери голоса. +- **Измерительная технология существует, но EN-центрична:** стилевые эмбеддинги StyleDistance (2410.12757) — EN-only; **разрешение через добор: mStyleDistance (Findings ACL 2025) обучен на 9 языках ВКЛЮЧАЯ русский** (~40 стилевых фич, xlm-roberta-base 0.3B, MIT на HF) — но качество умеренное (AV ROC-AUC avg 0.66 — ⚠ **среднее по ЧУЖИМ языкам, русского элемента в нём НЕТ**; STEL-or-Content: formality 0.71, остальные оси 0.30–0.37) → пригоден как **мягкий флаггер дрейфа, не гейт**. Персонажные эмбеддинги работают на фикшене: UAR+DramaCV — SOTA атрибуции цитат на 28 английских романах (Findings EMNLP 2024). Speaker-verification-as-eval: роль-плей LLM систематически **проваливает** верификацию спикера (2405.10150) — паттерн «различимы ли реплики без атрибуции» валиден как детектор потери голоса. - **Доказанные механизмы удержания голоса** (API-совместимые): retrieval **курированных** реплик персонажа помогает, retrieval сырого текста книги «barely enhances» (CoSER, ICML 2025, 771 книга; потолок Character Fidelity фронтиров ~49%); переинъекция персоны ближе к концу контекста лечит **меж-ходовой** дрейф (SPR, COLM 2024 — к нашим stateless-чанкам дрейф-лора не применима, см. §3.2). Persona drift измерен: 0.75→0.55 за 8 раундов; ошибки long-form кластеризуются в середине нарратива (40–60% длины); **люди ловят инжектированные ошибки консистентности с recall 17.1%** (авточекер 55% при precision 0.884) → холистическому чтению не доверять, нужны таргетные пробы. - Для zh есть готовая рубрика: **CharacterEval** (peer-reviewed, 1785 мультитёрн-диалогов, 77 персонажей китайских романов, 13 метрик, отдельные оси speech style / behavior) — донор осей для судья-анкеты.