textmachine/docs/DOC_REVISION_FINDINGS.md

4832 lines
580 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# РЕЕСТР НАХОДОК РЕВИЗИИ ДОКОВ — 01.09.2026, оркестратор №22
> **⚠ РЕДАКЦИЯ 2. Первая была собрана с дефектом и отозвана.** Вердикты опровергателей
> присваивались по паре `(файл, строка)`, а на одной строке сидит до ШЕСТИ находок
> (`docs/PROGRESS.md:3` — шесть), поэтому все они получали поправку ПОСЛЕДНЕЙ. Нашёл контролёр
> Fable 5.1, проверено моей командой: 10 находок стояли на неоднозначных ключах. Здесь матч идёт
> **по дословной цитате**; 82 вердикта присвоены 82 разным находкам, дублей 0.
>
> **ГЛАВНОЕ ПРАВИЛО ИСПОЛНЕНИЯ, которого в редакции 1 не было:**
> **где есть «⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ» — исполняется ОНА, а не «Предложенное действие».**
> Поправка есть у **67 находок из 130**, и часть поправок ПРОТИВОПОЛОЖНА
> исходному действию («не закрывать, а сузить» · «не переносить, а забаннерить» · «не строить —
> уже построено» · «этот носитель НЕ ТРОГАТЬ»). Исполнение по «Предложенному действию» там, где
> стоит поправка, — дефект.
>
> **ВТОРОЕ ПРАВИЛО:** не вписывать в доки НОВЫЕ голые `file:line` — они сгниют так же.
> Форма — токен-якорь `` файл:строка=`подстрока` `` либо ссылка на символ.
## Как читать вес находки
- **«Вердикт: ПОДТВЕРЖДЕНО»** — находка прошла адверсариальный пере-суд отдельным агентом.
Таких 75.
- **«Вердикт: НА ПЕРЕ-СУД НЕ ВЫНОСИЛАСЬ»** — проверена ОДНИМ агентом (правило воркфлоу: на
пере-суд шли только разрушающие, блокеры и класс F). Таких 55.
⚠ Это **наряд на перепроверку, а не истина**: перед правкой верифицировать своей командой.
Добыто воркфлоу `docs-staleness-revision-A`: 15 срезов × (ревизор + опровергатель), 30 агентов,
0 отказов, **1432 утверждения проверены исполнением**, 115 положительных контролей.
Найдено 137 → пере-суд снял 7 → **живых 130** (блокеров 6,
major 61, minor 63).
Снятые семь — `DOC_REVISION_REFUTED.md`, **их чинить нельзя**.
---
## N001 · БЛОКЕР · `docs/PROGRESS.md:176` · C-исполненное-как-открытое
**Срез:** ЕДИНЫЙ БЭКЛОГ docs/PROGRESS.md — таблица (строки 57272 файла), проверка на класс 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) и убрать её из таблицы; в теле ноты сохранить опровержение оси — пере-покупка терминолога идёт от байтов батча, а не от сдвига снапшота.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Закрыть строку 194 — верно, но в ноту закрытия обязаны войти ДВА опровержения её текста, а не одно. Кроме оси «сдвиг снапшота» (опровергнута пингом №2, `docs/PROGRESS.md:344` — пере-покупка идёт от БАЙТОВ БАТЧА) ЛОЖНА и первая половина клейма «не входят НИ в число согласия»: тест утверждает раздельно, что `committed_usd` = `SUM(checkpoints)` НЕ сдвинулся, потому что деньги банк-ролей были в нём ВСЕГДА (spend и checkpoints пишутся одной транзакцией) — слепа была только ПРОЕКЦИЯ. Плюс назвать честную границу из §9.9: в проекцию добавлено УЖЕ ПОТРАЧЕННОЕ, а не прогноз контура, то есть проекция стала НИЖНЕЙ границей.
**Разбор опровергателя:** Пытался опровергнуть тремя путями и не смог. (1) Код в РАБОЧЕМ ДЕРЕВЕ: слагаемое `r.bankRoleCommittedUSD()` стоит в `status.go:801`, посадка `bankrolemoney_test.go` называет строку 194 первой строкой файла. (2) Тест РЕАЛЬНО ЗЕЛЁНЫЙ — я его прогнал, а не поверил комментарию. (3) Лендинг ратифицирован: `bb541a8` в `git log`, нота D39.182 в живом журнале решений. Диспозиция строки («расширение проекции на банковые роли + тест») исполнена буквально — отчёт `backend/docs/MONEY_HONESTY_REPORT.md:107` подписывает «A10 строка 194 | предъявлено», §9.9 называет обе посадки. Строка при этом до сих пор стоит в таблице со статусом «скоро». Класс C подтверждён.
**Разрушающее:** True
---
## N002 · БЛОКЕР · `docs/PROGRESS.md:200` · C-исполненное-как-открытое
**Срез:** ЕДИНЫЙ БЭКЛОГ docs/PROGRESS.md — таблица (строки 57272 файла), проверка на класс 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), где она предлагается как незанятая.
**Разбор опровергателя:** Опровергнуть не удалось. Тест `TestTheProjectionSeesAnInPlaceSourceEdit` есть в рабочем дереве и ЗЕЛЁНЫЙ у меня в руках, а не по комментарию. Отчёт пака подписывает A6 «предъявлено» с двумя посадками (§9.6: ветка совпавшего снапшота теперь сверяет контент-хеш тем же предикатом, что применяет сам прогон, плюс дешёвый зонд валидности манифеста). Приёмка нашла в самой починке блокер 2 (зонд был мёртв на денежной поверхности) и он ЗАКРЫТ до лендинга — это в теле D39.182 п.(б) дословно. Второй носитель тоже верен: строки 238 и 239 действительно предлагаются как «свободная работа зоны» на `docs/PROGRESS.md:282`, то есть протухли ДВА места, а не одно.
**Разрушающее:** True
---
## N003 · БЛОКЕР · `docs/PROGRESS.md:201` · C-исполненное-как-открытое
**Срез:** ЕДИНЫЙ БЭКЛОГ docs/PROGRESS.md — таблица (строки 57272 файла), проверка на класс 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.
**Разбор опровергателя:** Опроверг только ОДНУ деталь формулировки находки и она не меняет вердикта: правило не «перенесено», а сведено в общее определение `orphanStageRows` (`driftbasis.go:83`), которое зовут ОБЕ поверхности; при этом символ `exportConfigDrift`, названный в самой строке бэклога, жив — то есть мёртвого указателя тут нет. По существу же строка исполнена: `status.go:740` зовёт правило прямо в блоке `checkWave` и комментарием называет строку 239 по номеру, посадки есть (`driftbasis_test.go` + §9.7 отчёта: 4 посадки плюс две в `cmd/tmctl`), тесты у меня зелёные. D39.182 в примечаниях сам называет строку 239 «болезнью, воспроизведённой в лекарстве от неё» и говорит, что она сведена к общей предпосылке `driftCheckable` — то есть лендинг про эту строку знает, а таблица нет.
**Разрушающее:** True
---
## N004 · БЛОКЕР · `docs/architecture/15-money-path.md:31` · D-противоречие-ратифицированному
**Срез:** Денежный путь — docs/architecture/15-money-path.md
**Вердикт:** ПОДТВЕРЖДЕНО
**Цитата из дока (дословно):**
> реальная цена главы замерена по движковым леджерам — средняя **$0.0115$0.0190**, максимум одной главы $0.0375, то есть константа завышена в **1.62.6×** … и «купить 10 глав» отдаёт движку $0.30, покупающий ~1626 глав
**Почему протухло:** Числа взяты из 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 главы, а не 1626, и ⛔ «константу по сегодняшним числам не калибровать» защищает ставку НИЖЕ себестоимости. ‘Исторический’ баннер над провенансом не спасает: вывод про ручку «главы» стоит в НАСТОЯЩЕМ времени как живое описание дефекта 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.62.6×, и «купить 10 глав» = $0.30 покупает ~3 главы, а не 1626. Направление лечения (цена от объёма исходника + объёмный потолок в движке) не меняется, но ЗНАК дефекта PD-410 обратен записанному: ручка не пере-продаёт, а НЕДО-продаёт.» Добавить D39.179 в карту §6 и отпинговать зоне платформы PD-410.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Находка верна, предложенный текст — нет, он ставит под удар ровно то, чем прошлая смена уже обожглась. Три правки. (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 — ИЮЛЬСКИЕ (сама нота, ⛔г: прогоны 1011.07, редактор другой моделью). D39.179 п.1 ратифицировал ×4.47 к ценам с 16.08 16:00 UTC ⇒ та же средняя сегодня $0.051$0.085, тот же максимум $0.168. Константа $0.03 НЕ завышена — она ниже даже пере-считанной средней, и «купить 10 глав» = $0.30 покупает единицы глав, а не 1626.» Замер холодного прогона добавить ОДНОЙ строкой как иллюстрацию с оговоркой 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.01150.0190 против константы продажи $0.03)») — чинить в одной правке. Строка реестра `docs/architecture/05-decisions-index.md:229` их тоже повторяет, но она зеркало ноты: там достаточно указателя на D39.179. И уточнить формулировку про PD-410: строку регистра я прочёл — она несёт только (а)(б)(в)(г) и НЕ содержит ни «1626 глав», ни «завышена в 1.62.6×», поэтому «знак дефекта PD-410 обратен записанному» — перебор; перевёрнуто СЛЕДСТВИЕ в D39.165 §1 и его копия в доке. Пинг зоне платформы послать стоит, но как новую информацию («бета сегодня НЕДО-берёт»), а не как претензию к ряду. ⛔(г) при этом остаётся в силе: правка описывает знак, калибровку по-прежнему держит строка бэклога 202.
**Разбор опровергателя:** Пытался опровергнуть тремя путями, ни один не сработал. (1) Сначала проверил, не сложены ли деньги дважды: `checkpoints.cost_usd` — по одной строке на запрос (PK `request_hash`), сумма чекпойнтов сходится с `request_log` и с `spend.committed_usd` = 0.436109664 копейка в копейку. Замер автора воспроизводится. (2) Затем проверил, не куплен ли вывод хвостом брака: исключив оплаченные НЕуспешные вызовы, полная цепь главы всё равно $0.071563 и $0.078336 — 2.42.6× константы $0.03, знак тот же. (3) Затем — самый важный ход: вывод НЕ зависит от тонкого замера автора вовсе, он следует из одной ратифицированной арифметики. D39.165 ⛔(г) сам объявляет свои прогоны июльскими (1011.07, «редактор был другой моделью»), а D39.179 п.1 ратифицировал множитель ×4.47 к ценам, вступившим 16.08 16:00 UTC. $0.01150.0190 × 4.47 = $0.051$0.085; названный доком АБСОЛЮТНЫЙ максимум $0.0375 × 4.47 = $0.168. Обе цифры выше $0.03 ⇒ «завышена в 1.62.6×» и «$0.30 покупает ~1626 глав» перевёрнуты уже чистой ратификацией, без единого нового замера. (4) Независимое подтверждение из ЗАЛЕНДЖЕННОГО носителя: движок сам печатает `projected_book_usd = 1.1377277707` на полной цепи тех же десяти глав (docs/archive/reports/COLDRUN_V16_REPORT_2026-08-31.md:70/294) — ≈$0.114/глава. Утверждение дока стоит в НАСТОЯЩЕМ времени как живое описание дефекта PD-410, и баннер «провенанс ниже — ИСТОРИЯ» его не спасает: он снимает обоснование ставки, но не переворачивает знак следствия, который читатель уносит с собой.
**Разрушающее:** False
---
## 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 because no sweep will ever come for it")
```
**Другие носители того же факта:** 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.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие в целом верно (дописать вторую ветвь в §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` сегодня чист, но зона под паками движется).
**Разбор опровергателя:** Воспроизвёл своей командой, не поверив автору. (1) Ветвление стоит в `AbandonRun` (platform/internal/pgstore/runs.go:566-569): `if finished != nil { verdict = AbandonedSettlement; return abandonSettlement(...) }` — ДО чтения попытки и ДО отказа `if unit != "" || baseline != nil` (строка ~592). Значит для прогона с `finished_at is not null` названный доком отказ по юниту/базовой линии физически недостижим. (2) `giveTheHoldBack` в `abandonSettlement` НЕ передаётся ни на одном из двух вызовов (569, 587), а сама функция (732) закрывает резервацию и возвращает холд `closeReservation(..., "released", now)` в СВОЕЙ транзакции — то есть `--release-hold` в этой ветви игнорируется, деньги всегда закрываются inline. Флаг это и объявляет: `"...; ignored for a PHASE=settling run"` (platform/cmd/tmplatformctl/runs.go:307). (3) Единственный сдерживатель — порог `const abandonAfter = 5` (runs.go:730) с отказом `ErrSettlementNotStuck`, и комментарий над функцией прямо пишет, почему: «this command REFUNDS THE WHOLE HOLD», а замер приёмки назван числом ($1.234567 потрачено, эта команда за тик раньше списала бы $0.000000). Значит к abandon в этой ветви допускается именно попытка, которая ДОШЛА до движка и потратила. (4) Положительный контроль на пустоту: `grep -c settling` даёт 4 в platform/deploy/README.md и 0 в 15-money-path.md — инструмент в этой области ищет, а в доке слова нет. (5) Датировка автора точна: абзац вошёл 22.08 (0e31a8b), settling-ветвь и её описание — 29.08 (e548e5a) и только в зонный README. Абзац дока с тех пор не правился по смыслу.
**Разрушающее:** False
---
## 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(а)» снять.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие принимается как есть. Единственное уточнение к формулировке замены: писателем является не «глагол bank-apply» вообще, а функция `writeDecisionFiles` (`backend/internal/pipeline/bankdecisions.go:450`), и порядок переименований в ней НЕ произвольный (rejects первым — аргумент в шапке функции); если таблица описывает атомарность, честнее написать «атомарно ПОФАЙЛОВО (stage+rename+syncDir), но пара файлов НЕ транзакционна — половинное состояние сходится на пере-посылке», иначе колонка «атомарно» пообещает больше, чем код даёт.
**Разбор опровергателя:** Все четыре под-утверждения находки воспроизвелись моими командами на рабочем дереве. (1) Ключ РЕТИРОВАН: поле `MinedDelta` помечено `yaml:"-"`, путь выводится в `book.go:199` из `BookID+MinedDeltaSuffix`, а `RetiredMinedDelta yaml:"mined_delta"` существует ровно затем, чтобы объявленный ключ ВАЛИЛ конфиг (`bad(...)` в 263265, с прямой ссылкой на D39.156 п.3). Значит цена ошибки, названная находкой, реальна: вписанный в деплой-шаблон `mined_delta:` уронит загрузку каждой книги. (2) Писатель ЕСТЬ — `writeDecisionFiles` в `pipeline/bankdecisions.go` стейджит оба файла и вызывается глаголом `bank-apply`, который стоит в диспетчере `main.go:185/:249`. (3) Атомарность не «—»: `stageFileAtomic` + commit(rename) + `syncDir` на каталоге книги. (4) Развилка не «ждёт ратификации» — D39.156 п.3 (23.08) закрыла её растворением, а индекс D39.166 пишет «строки 199/215 закрыты». Положительный контроль грепа бэклога состоятелен: тот же шаблон даёт 1 на живых 175/202/224 и на надгробии 96, и 0 на 199/231. Находка верна и предложенное действие верно.
**Разрушающее:** True
---
## 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.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие верное по сути, но опись носителей в находке НЕТОЧНА и предложенная форма сама сгниёт. (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 и протухают быстрее всего.
**Разбор опровергателя:** Воспроизвёл своей командой полностью. Из «трёх» read-out файлов один не существует, а уцелевшая человеческая `.bank-stop.txt` пишется голым `os.WriteFile` — то есть ровно БЕЗ той гарантии, ради которой README и написал «их читают ПОКА идёт прогон». Автор был прав в 0e69bc1 и сгнил на d1eb8a9. Класс A, major — подтверждаю.
**Разрушающее:** True
---
## N009 · MAJOR · `backend/README.md:82` · D-противоречие-ратифицированному
**Срез:** Доки зоны бэкенда: backend/README.md + backend/docs/*.md (9 файлов)
**Вердикт:** ПОДТВЕРЖДЕНО
**Цитата из дока (дословно):**
> Пути дефолтятся ОТ `$HOME` и от корня репозитория (не от `/home/ubuntu`), так что клон под любым пользователем и по любому пути находит то, что есть
**Почему протухло:** От `$HOME` НЕ дефолтится НИЧЕГО. `internal/standdata` резолвит корпус ТОЛЬКО от корня репозитория (`<repo>/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
// <repo>/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 (<repo>/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/<book>/…»
**Предложенное действие (автора находки):** Строку 82 переписать: «Пути дефолтятся ОТ КОРНЯ РЕПОЗИТОРИЯ, найденного по маркеру `backend/go.mod` (`internal/standdata`); корпус — `<репозиторий>/books` (D39.157 п.2). $HOME не читается вовсе.» В строках 47/79/80 заменить `/home/ubuntu/books` и `~/books` на `<репозиторий>/books`. Отдельной строкой — backup.go:28. ⚠ Строка бэклога 217 покрывает только СКРИПТЫ полигона, носителя для этого README-дрейфа в трекере нет.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие верное; уточняю две вещи. (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` — код бэкенд-зоны, не док: это ПИНГ бэкенд-сессии, а не правка оркестратора.
**Разбор опровергателя:** Воспроизведено. `$HOME` в дефолтах не читается вовсе и это прямо ратифицировано D39.157 п.2 плюс записано в шапке пакета. Классический «родилось верным — сгнило дрейфом», и носителя в трекере под него больше нет (218 закрыта). Класс D/A, major — подтверждаю.
**Разрушающее:** True
---
## 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`).»
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие верное по существу, но предложенная замена воспроизводит ту же болезнь — снова номера строк (`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.
**Разбор опровергателя:** Воспроизведено дважды — и грепом по предмету, и положительным контролем в соседнем файле того же пакета. Долг погашен, код по обоим якорям говорит README прямо в лицо, а сами якоря вдобавок уехали (457/474 → 494). Класс C + B, major — подтверждаю.
**Разрушающее:** True
---
## 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.»
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие верное, но одну деталь предложенной редакции надо снять: «строка 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/, назови файл явно — иначе следующая сессия снова не найдёт, откуда взялась диспозиция.
**Разбор опровергателя:** Воспроизведено с собственным положительным контролем (189 строк / 3 из 3 соседних номеров). Указатель мёртв, и мёртв он под шапкой, которая явно обещает, что номер резолвится в таблицу. Класс B, major — подтверждаю.
**Разрушающее:** True
---
## 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, строки 118 (шапка ⟶ ТЕКУЩЕЕ СОСТОЯНИЕ, ~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 — иначе разморозка фронта будет спланирована на неверное число перегенераций.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Само действие верно, но один из перечисленных «носителей» я снимаю: docs/README.md:29 (строка «Контракт») говорит «минор 0.8.0 ПРИНЯТ и заленджен 29.08 (D39.169) вместе с паком P11» — это ИСТОРИЧЕСКИ ВЕРНОЕ утверждение о контрактном паке контрактной роли, а не заявление «канон сегодня 0.8.0»; 0.9.0 приехал платформенным паком и назван в своей строке (README:26). Правку туда вносить не надо — максимум добавить ссылку «далее 0.9.0 — см. строку Платформа». Править надо ровно три места: docs/PROGRESS.md:3, docs/PROGRESS.md:10, frontend/docs/frontend-PROGRESS.md:9.
**Разбор опровергателя:** Опровергнуть не удалось: канон в рабочем дереве — 0.9.0, зеркало — 0.2.3, и все семь промежуточных миноров подтверждены историей самого файла (не выведены арифметикой из двух чисел). Счёт операций 20/16 воспроизведён. Носители-копии (PROGRESS:10, frontend-PROGRESS:9) существуют дословно. Разрушительность действия проверена: правка не сносит текста, а меняет два числа — риск низкий.
**Разрушающее:** True
---
## N014 · MAJOR · `docs/PROGRESS.md:3` · A-дрейф
**Срез:** ГОЛОВА журнала: CURRENT-STATE и очередь — docs/PROGRESS.md, строки 118 (шапка ⟶ ТЕКУЩЕЕ СОСТОЯНИЕ, ~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-предпочтение).
**Разбор опровергателя:** Опровергнуть не удалось, и находка оказалась даже сильнее заявленного: «73%» противоречит не только двум знаменателям отчёта, но и ДОСЛОВНОМУ слову владельца в шапке того же отчёта («половину захода»), а адверсариальный проход отчёта отдельной строкой уже принял находку «81% без знаменателя». Прямой пересчёт из таблицы 4.2 даёт 49.27% — 73% не получается ни при каком знаменателе отчёта. Число формулирует предмет следующей ПЛАТНОЙ пробы, поэтому severity major оправдан.
**Разрушающее:** True
---
## N015 · MAJOR · `docs/PROGRESS.md:3` · E-второй-носитель
**Срез:** ГОЛОВА журнала: CURRENT-STATE и очередь — docs/PROGRESS.md, строки 118 (шапка ⟶ ТЕКУЩЕЕ СОСТОЯНИЕ, ~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:245246 — реестровые строки D39.181/D39.182 со столбцом «ЖИВОЕ» (применение закона к десяти экземплярам — фаза 2; строка бэклога 246), docs/PROGRESS.md:302 — упоминание D39.181 в зонной записи бэкенда, ниже шапки
**Предложенное действие (автора находки):** Дописать в шапку одну фразу вида: «31.08 в зоне бэкенда лёг пак «деньги и честность выдачи» (D39.182) и РАТИФИЦИРОВАН закон раскрытия движка (D39.181); живой остаток — фаза 2 (применение к десяти экземплярам) и строка бэклога 246». Заодно продлить провенанс-перечень строки 16 до 248: строки 244248 заведены 31.08 и в перечне обрываются на 243 (сами счётчики 189/88/125/59/0 при этом сходятся с `counts.py`).
**Разрушающее:** False
---
## N016 · MAJOR · `docs/PROGRESS.md:80` · C-исполненное-как-открытое
**Срез:** ЕДИНЫЙ БЭКЛОГ docs/PROGRESS.md — таблица (строки 57272 файла), проверка на класс 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). Живым остатком оставить только то, что действительно не измерено, — цена ДОВЕДЁННОЙ до конца книги.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие верно и строку НЕ закрывать — только переписать. Живым остатком оставить ровно то, что находка и называет: цена ДОВЕДЁННОЙ до конца книги (прогон отдал 3 юнита из 14). При переписывании привести в согласие и колонку гейта: сегодня она говорит «первый прогон добора идеала», а CURRENT-STATE числит строку 16 среди снятых холодным прогоном — оставить одну версию, иначе класс E воспроизведётся внутри одного файла.
**Разбор опровергателя:** Историческая часть цитаты («на coldrun-a/b ноль редакторских вызовов») ВЕРНА и я её не оспариваю — протух ВЫВОД «⇒ строка жива» в прежнем объёме. 31.08 отработал coldrun-v16, и в его собственной БД четыре оплаченных редакторских чекпойнта на $0.214865 — почти половина захода. То есть «полная цена холодного старта с редактурой» перешла из «не измерена» в «измерена частично». Отдельно подтверждаю вторую половину находки: июльские числа строки ($0.031639 / $0.045095) по ратифицированной D39.179 п.1 множатся на 4.47 в сегодняшних деньгах, и строка об этом молчит, то есть вводит читателя в смету, ошибочную вчетверо. Третий носитель, который находка не назвала: CURRENT-STATE (`docs/PROGRESS.md:3`) числит строку 16 в списке «гейт строк 16/154/157/160/198/202/216», снятом холодным прогоном, а колонка гейта самой строки говорит «первый прогон добора идеала — после решения по 46» — то есть шапка и таблица расходятся и в этом месте.
**Разрушающее:** True
---
## N017 · MAJOR · `docs/PROGRESS.md:143` · A-дрейф
**Срез:** ЕДИНЫЙ БЭКЛОГ docs/PROGRESS.md — таблица (строки 57272 файла), проверка на класс 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 и замер НЕ дало — назначить настоящее предусловие (публикация размеров юнитов в знаках в манифесте/сайдкаре) отдельным носителем, либо признать замер невозможным и решать по косвенной мере (входные токены черновых вызовов, 15571863 на чанк). Тем же касанием снять крест-накрест подпись отчёта §9 (154↔157).
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие верно, но одна деталь его формулировки опасна: «назначить настоящее предусловие … отдельным носителем» надо делать НЕ молча — публикация размеров юнитов в знаках это новое поле контракта манифеста (`manifest_version`), то есть решение уровня ноты, а не строки бэклога. Плюс уточнение по косвенной мере: 15571863 — это входные токены на ЧАНК, а юнит по этому же прогону несёт от 1 до 2 чанков (медиана 1), поэтому пере-считывать в цену юнита надо через `chunk_count`, а не приравнивать юнит чанку.
**Разбор опровергателя:** Положительный контроль внутри самой команды: python печатает НЕПУСТЫЕ списки ключей книги, главы и юнита — манифест читается, поля видны, значит отсутствие символьных размеров это факт, а не пустой хит. Гейт назначен на событие, которое произошло 31.08, и событие ответа не дало. Крест-накрест подтверждаю СВОИМ прочтением: предмет строки 154 — «цена в размере ЮНИТОВ не замерена», и раздел отчёта про размеры юнитов подписан «Строка 157»; предмет строки 157 — `family_max_members`/`families_held`, и раздел с числами семейств подписан «Строка 154». Промт §5.7 обе строки гейтит одним прогоном и подписей не путает — путаница родилась в отчёте.
**Разрушающее:** True
---
## N018 · MAJOR · `docs/PROGRESS.md:157` · A-дрейф
**Срез:** ЕДИНЫЙ БЭКЛОГ docs/PROGRESS.md — таблица (строки 57272 файла), проверка на класс 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 (`<project_db>.bank-stop.json` в таблице каналов: «⚠ канал существует и…»), backend/docs/pipeline.puml:72 («.bank-stop.json — машине (строка 101): ПОЛНАЯ таблица»), frontend/docs/frontend-PROGRESS.md:499 («сайдкар `<db>.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`»), и тем же касанием пройти прочих живых носителей, где файл всё ещё числится существующим каналом.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие верно; список носителей уточняю по исполнению. `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`. Два из них — чужие зоны: правка пингом, не касанием.
**Разбор опровергателя:** Это самый крепкий из десяти. Отрицательный результат снабжён живым положительным контролем: тем же грепом в тех же файлах находятся `.bank-stop.txt` и `.bank.json`, значит пустота у `bank-stop.json` — факт. `git log -S` показывает и рождение символа (`0e69bc1`), и его смерть (`d1eb8a9`, входная дверь шва D39.158) — то есть это не опечатка и не мой промах с симлинком: каталог прогона я читал через `ls -L`, и там лежит только `.bank-stop.txt`. Зона платформы независимо пишет ровно то же самое и прямо предупреждает чужие доки. Строка 169 обещает фронту машинную таблицу для экрана S5, которой нет.
**Разрушающее:** True
---
## N019 · MAJOR · `docs/PROGRESS.md:182` · D-противоречие-ратифицированному
**Срез:** ЕДИНЫЙ БЭКЛОГ docs/PROGRESS.md — таблица (строки 57272 файла), проверка на класс C «исполненное как открытое»
**Вердикт:** ПОДТВЕРЖДЕНО
**Цитата из дока (дословно):**
> Цена ≈ $0.05 на десятиглавую книгу.
**Почему протухло:** Дважды протухла. (1) Цифра «≈$0.05» СНЯТА ратифицированной нотой D39.179 п.1 как протухшая: пере-пин DeepSeek 15.08 даёт множитель 4.47, а замер 31.08 на ровно такой десятиглавой книге дал $0.436110 при собственной проекции движка $1.1377 — то есть строка обещает платформе смету, ошибочную примерно в 923 раза. (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).
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие верно; добавить два уточнения, чтобы новая цифра не протухла так же. Первое: $0.436110 — цена НЕПОЛНОГО захода (доставлено 3 юнита из 14), поэтому в строку надо писать не её одну, а связку «куплено $0.436110 при собственной проекции движка `projected_book_usd = 1.1377277707` и полном оттоке кассы ≈$1.44 по §4 отчёта» — иначе платформа снова возьмёт в смету число меньше правды. Второе: указатель на ключи ставить греп-указателем (`TM_PLATFORM_ENGINE_KEYS_PATH` в `platform/internal/config/config.go`), без номера строки — файл в живой зоне платформы.
**Разбор опровергателя:** Опровергнуть не вышло по обеим половинам. (1) D39.179 п.1 — ЖИВАЯ ратифицированная нота, и она снимает «≈$0.05» ИМЕННО как протухшую цифру, с механизмом (пере-пин 15.08, множитель 4.47). Строка бэклога спорит с ратифицированным — класс D по норме приоритета «журнал решений бьёт всё». (2) Гейт «холодный прогон движка» действительно ПАЛ: `git log` держит `244f808` (лендинг отчёта холодного прогона), а сумма по БД прогона — $0.436110 на 50 чекпойнтах, то есть на порядок выше обещанных строкой $0.05. Расхождение с собственной шапкой файла тоже реально: CURRENT-STATE (`docs/PROGRESS.md:3`) пишет «гейт СНЯТ холодным прогоном», таблица на строке 182 — «скоро (гейт: холодный прогон движка)». Живой блокер подтверждён зонным журналом: ключи провайдера.
**Разрушающее:** True
---
## N020 · MAJOR · `docs/PROGRESS.md:196` · C-исполненное-как-открытое
**Срез:** ЕДИНЫЙ БЭКЛОГ docs/PROGRESS.md — таблица (строки 57272 файла), проверка на класс 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`) — при закрытии не «чинить».
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
НЕ закрывать и НЕ убирать строку 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`) не трогать.
**Разбор опровергателя:** ЦИТАТА протухла — это подтверждаю: `store.AnnouncedOnceKeys()` построен (`outbox.go:51`), ось доставки в `volume.go:490,539` выведена из анонс-леджера, отчёт подписывает A11 «предъявлено», 4 посадки. НО предложенное ДЕЙСТВИЕ находки неверно, и это ловушка, в которую находка попала: сам пак §4.5 ЯВНО объявляет, что строка 232 несёт ДВЕ половины и вторую он НЕ закрывает. Убрать строку из таблицы — значит потерять живой открытый долг (учёт слота гранта у юнита, прерванного между волнами; замер строки — 4 купленных юнита на 2 главы), который автор лекарства сам назвал незакрытым.
**Разрушающее:** True
---
## N021 · MAJOR · `docs/PROGRESS.md:202` · C-исполненное-как-открытое
**Срез:** ЕДИНЫЙ БЭКЛОГ docs/PROGRESS.md — таблица (строки 57272 файла), проверка на класс 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).
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие в целом верно, но его ФОРМУЛИРОВКА протухла на сутки: закрывать «при лендинге/приёмке 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`-заглушке».
**Разбор опровергателя:** Проверял именно на пустой положительный контроль — он НЕ пустой: греп по `BankStop` в `platform/` даёт шесть живых символов и три теста, значит отсутствие объявления `LiftBankStop` — факт, а не опечатка. Гард снят, колонка `bank_released` снесена миграцией 00029, защита переехала в ветку `awaiting_bank` реконсилера, которая закрывает прогон вместо рестарта и называет строку 240 по номеру в двух местах (`:612`, `:971`). Зонный журнал подписывает §3.7/строку 240 как «да». Лендинг ратифицирован D39.180. Строка в таблице по-прежнему «скоро».
**Разрушающее:** True
---
## 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/openapi.yaml:10
**Предложенное действие (автора находки):** Заменить «— байт-зеркало, сверять при лендинге» на факт: «зонная копия `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.Messages { w(m.Role, m.Content, strconv.FormatBool(m.CacheBoundary)) }
```
**Предложенное действие (автора находки):** Сноской по образцу уже стоящей в §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 Request) string {
stagerun.go:59: return nil, fmt.Errorf("pipeline: job %s/ch%d/%s was started under snapshot %.12s ... --resnapshot ...")
```
**Положительный контроль:**
```
Тот же 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 сам предупреждает «версия здесь живёт хуже всего».
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие автора верно по сути, но перечень носителей уточняю: docs/README.md:29 НЕ ложна — это корректное историческое утверждение о D39.169, а README:26 (строка «Платформа») уже несёт 0.9.0; ей нужно дописать 0.9.0 в ряд «Контракт», а не переписывать. Обязательны к правке ТРИ носителя: 09-target-architecture.md:12, product-requirements.md:56 (её же собственный «урок ячейки» требует править версию первой), PROGRESS.md:10.
**Разбор опровергателя:** Воспроизвёл своей командой, попыток опровергнуть не нашёл. ОБА носителя, названные самой строкой, показывают 0.9.0; D39.180 (31.08) ратифицирует и лендит минор 0.9.0 вместе с паком P12. Blame: строка 12 написана 30.08 живой правкой шапки (e1e48ac), то есть это НЕ 13.07-текст под общим баннером «тело ниже = замысел 13.07» (он стоит в конце строки 14 и покрывает только тело НИЖЕ шапки) — это прескриптивное утверждение сегодняшнего дня, и оно ложно. Класс E тоже подтверждён: product-requirements.md:56 пишет «он придёт с лендингом пака P12, до него канон 0.8.0», PROGRESS.md:10 — «канон на 0.8.0 и уйдёт на 0.9.0 с лендингом P12»; P12 залендён 31.08, обе копии протухли тем же событием.
**Разрушающее:** 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 несут ту же протухшую формулировку.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие верное по существу, две поправки. (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-спеке и в телах экспериментов). То есть буллет сносится целиком, а не правится хвостом.
**Разбор опровергателя:** Опровергнуть не удалось — находка воспроизводится тремя независимыми носителями. (1) Ратификация: D39.13 дословно называет src→dst-блок редактора «закрытием D30.1» — и в реестре, и в теле архивного слайса; тело прочитано целиком, реопена нет. (2) Код: форма src→dst не просто написана, а зафолжена в снапшот константой RenderFormatVersion (подстрока editor-src2dst), доккоммент прямо говорит «resolves the D30.1-open question». (3) Потребитель есть — проверил положительным контролем, которого у автора не было: RenderEditorConstraintBlock подключён боевой проводкой pipeline/chunkrun.go:102 как рендерер роли редактора, плюс четыре тест-файла пинуют формат. Вопрос, который §4 п.5 держит открытым, ратифицирован 19.07 — через три дня после рождения дока. Оба со-носителя тоже проверены и протухли: 09-target-architecture.md:144-145 и :77 существуют дословно.
**Разрушающее:** True
---
## 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).»
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие верное. Добавить в тот же обход одну проверку, которую автор не сделал: 09-target-architecture.md:138 несёт «⚠ Арм «переводчик отдаёт структуру» никогда не тестировался», что тем же D39.9 выглядит протухшим — но осторожно: D39.9 мерил ВЕРНОСТЬ черновика, а не структуру, так что это ЛИД для отдельной сверки, а не готовая правка. Ссылку на Q4c лучше дать якорем секции (`19-editor-contract-q4b.md` §2.10 «Границы»), а не только номером строки.
**Разбор опровергателя:** Воспроизводится. Q4a и Q4c по research/19:730/732 — это ровно оси «структура у переводчика» и «где живёт reflow», то есть п.5 действительно про них. Q4a измерен и ратифицирован D39.9 (реестр: «закрыт»), топология ролей решена эксп-21 — тело D39.117 прочитано, п.3 дословно принимает форму «механический пре-гейт ПЕРЕД редактором» и подтверждает full-regen. Не гонялся только Q4c, и это подтверждено телом эксп-19 §2.10. Решающая улика — сам док: §1 (строка 18, испр. 22.08) уже несёт «Q4a ИЗМЕРЕН (D39.9)» и «Не гонялся только Q4c», то есть п.5 в §4 остался непоправленным тем же касанием и прямо противоречит §1 через 34 строки. Класс C верен.
**Разрушающее:** True
---
## 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 reference
```
**Другие носители того же факта:** 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 (список команд).
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Находка верна, но предложенное действие бьёт мимо в трёх местах.
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 — НЕ находка, флагать не надо.
**Разбор опровергателя:** Опровергнуть находку не вышло — она воспроизводится четырьмя независимыми уликами.
1) Отрицательное утверждение находки я проверял С ПОЛОЖИТЕЛЬНЫМ КОНТРОЛЕМ в обе стороны: греп по backend/ находит (func main → main.go:25), греп по бэклогу находит строку 93 и не находит 83. Пустого-грепа-по-опечатке здесь нет.
2) Факт строки 52 сегодня ЛОЖЕН по существу: `case "backup"` (main.go:243), `PRAGMA integrity_check` и `VACUUM INTO` (store/backup.go:18,31,56,82), пре-флайт в двух точках платного пути (main.go:221,236). Утверждение «0 вхождений во всём backend/» опровергается кодом.
3) Якорь main.go:73-83 — классический дрейф, а не ошибка автора: `git show 036dc78:…` показывает, что в момент написания там действительно стоял dispatch-switch (case "translate" … case "seed-lint"), а сегодня это словарь exit-кодов. Это ровно тот паттерн «родился верным, сгнил дрейфом», о котором предупреждал заказ.
4) Проверил главную линию защиты — «это баннированное историческое, а значит легитимно». Не проходит: баннер файла (строка 3, D39.101) сам обещает механизм «закрытия помечены стрелками «→» ПО МЕСТУ». Стрелка проставлена в заголовке среза (строка 16: «→ ИСПОЛНЕНО D39.82»), а в сводке строки 52 — нет. То есть док противоречит САМ СЕБЕ через 36 строк, и обещанный баннером маркер в этом месте отсутствует. Это не «ощущение устарелости», а невыполненный контракт собственного баннера.
5) Проверил «не дефект ли это ратифицированного контракта»: наоборот — реестр D39.82 (05-decisions-index.md:146, тело в слайсе docs/archive/architecture/05-decisions-D39-59-90.md) прямо ратифицирует «tmctl backup + pre-flight (F4) … строки 89/53/83 закрыты», и строка 83 из бэклога PROGRESS физически снята. Ратифицированное на стороне находки.
6) Проверил, не читал ли автор устаревшую копию: все цитаты сверены по рабочему дереву (git status по docs/architecture/13-*.md чист; изменён только docs/research/15-voice-and-state.md, к предмету отношения не имеет).
Неточности находки, на вердикт не влияющие: (а) второй носитель F4 в реестре памяти — строка 84, а не 88 (баннированность автор определил верно, флагать её не стал — правильно); (б) формулировка «единственный не-баннированный носитель» неточна: не-баннированный носитель есть ещё и в строке 16 того же файла, но он КОРРЕКТЕН — что усиливает класс E (копии разошлись внутри одного дока), а не ослабляет.
**Разрушающее:** True
---
## 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` оставить.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие в целом верно и его якоря я проверил (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), и починка одной половины оставит документ противоречащим самому себе.
**Разбор опровергателя:** Опровергнуть не удалось: обе половины цитаты ложны замером. (1) `gender: neuter` не no-op — `genderConstraintNote` отдаёт `tx.GenderNeuter` (memory.go:848-849), а `ComputeVersionScopedIn` складывает scoped-тег `neuter-directive-v1` (memory.go:461-462); (2) линт есть — `GenderVocabViolations` вызывается из `SeedLint` (memseed.go:533), т.е. ровно та «seed-lint-отбраковка неизвестных значений рода», которую абзац предписывает ПОСТРОИТЬ. Это прямое противоречие ратифицированному D39.69 §3, который называет строку 84 ЗАКРЫТОЙ, — и строки 84 в единственном трекере (таблица «Бэклог» docs/PROGRESS.md) уже нет. Абзац не защищён ⚠-баннером и в отличие от соседей (строки 60, 78, 82 — «→ Закрыто D39.69 … (Исторически: …)») не помечен как исторический: он читается как живое предписание к действию. Тело пункта выше (строка 70) это уже признаёт — не обновлён ровно абзац «Фикс/скоуп». Находка верна, класс D верен, severity major верен.
**Разрушающее:** True
---
## 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`), которые греп находит без номера, и поставить в шапку файла предупреждение «номера строк — снимок лендинга, сверяйся грепом токена».
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Находка верна, ПРЕДПИСАНИЕ НЕВЕРНО в главном: «завести $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) — известный дефект гейта: хук судит ЗАКОММИЧЕННЫЙ док против РАБОЧЕГО кода и врёт в обе стороны.
**Разбор опровергателя:** Опровергнуть не удалось — наоборот, мой независимый прогон дал ХУДШУЮ картину, чем у автора (14/18 против его 29/59). Все семь поимённо названных им якорей воспроизведены, включая особый случай: якорь KANA/HAN правился один раз (1048→1071, о чём файл сам пишет на строке 134) и уехал СНОВА на 1077 — обе редакции мертвы одновременно, причём голая 1048 всё ещё живёт на строках 12, 14 и 52 этого же файла и в бэклог-строке 81. `touchesPkg` предъявлен с корректным положительным контролем (15 функций в файле, найдена замена `reaches`/`fromPkg`) — пустой греп не от опечатки. Класс B верен, severity major верна: файл существует РОВНО чтобы быть носителем file:line, и две живые строки бэклога (docs/PROGRESS.md:255-256, строки 121/122) отсылают за якорями именно сюда. Заявленный механизм актуализации (строка 4) демонстративно не работает.
**Разрушающее:** True
---
## 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 exhausted account.
--- реестр ---
PD-282 ... | fixed(акт 5) | (комментарий кода на reconcile.go:1575: «It used to answer 202 with the run unchanged ... (PD-282)»)
```
**Положительный контроль:**
```
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`.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Предложенное действие верно по сути; добавить четыре вещи. (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.
**Разбор опровергателя:** Опровергнуть не удалось ни по одному из трёх каналов. Указатель мёртв: вердиктов `exhausted` в коде нет, они разведены на `ceilingSpent`/`creditUnavailable` — и это не пустой греп, положительный контроль в том же файле даёт три хита на то же слово. Поведение сменилось: 409 `run_not_resumable` · `cause: ceiling_reached` идёт из `Resume` через `fail()` (v0.go:807/810) при статусе 409 из problem.go:120. Носитель дефекта закрыт: PD-282 = fixed(акт 5). Проверил и обратную гипотезу «автор читал устаревшую копию» — нет, он читал ОДНОВРЕМЕННУЮ: лендинг кода и лендинг дока разделяют 10 секунд, доковая половина писалась под код ДО фикса и не была пере-снята. Проверил и вторую половину той же строки — «Пауза: платформа уже отвечает 409 ceiling_reached (ветка case "paused" в Resume) — совпало»: она ВЕРНА до сих пор (reconcile.go:1537 `case "paused":` → :1544 `ErrCeilingReached`), сносить её нельзя.
**Разрушающее:** True
---
## 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 («шаги 12 — конфигурация»).
**Улика — команда:**
```
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: «шаги 12 — конфигурация» на «шаги 12 построены P7».
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие в целом верное, но с тремя правками. (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 «шаги 12 — конфигурация» править обязательно и в ОДНОМ коммите с абзацем 1637 — иначе останется третий расходящийся носитель внутри одного файла (994 говорит «шаги 12 закрыты D39.153»).
**Разбор опровергателя:** Опровергнуть не удалось: сжатие ПОСТРОЕНО в приложении, в Go, и это видно из тела conditional.go (writeJSON буферизует, ставит Vary: Accept-Encoding, при acceptsGzip и размере ≥1 КиБ жмёт и ставит Content-Encoding: gzip). Хронология подтверждает диагноз «родилось верным, сгнило лендингом», и положительный контроль на историческом коммите чист. Уточнение, которое находка проглядела и которое НАДО передать принимающему: дословная команда, вписанная в док (`grep -rn "gzip|Content-Encoding"`, БЕЗ -E), даёт 0 и СЕГОДНЯ — basic grep читает `|` буквально. То есть в доке лежит команда с пустым положительным контролем: её ноль неотличим от отсутствия факта. Это усиливает находку, а не ослабляет: вписанная в док «улика» никогда ничего не доказывала. Отдельно: клауза «Go stdlib не сжимает» сама по себе ВЕРНА (net/http не жмёт автоматически) — ложен вывод «в репозитории сжатия нет».
**Разрушающее:** True
---
## 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 съехала: реальные производители лежат на 240270 строк ниже названных, а на названных номерах стоят комментарии и скобки. Это провенанс-карта словаря ошибок — единственное место, откуда следующая сессия узнаёт, где в платформе рождается каждый код, и сегодня она ведёт мимо. Линтер якорей (`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 errors.Is(err, pgstore.ErrRunInFlight): [док: v0.go:548]
801: case errors.Is(err, runs.ErrBookNotReady): [док: v0.go:550]
805: case errors.Is(err, runs.ErrNotStoppable): [док: v0.go:554]
820: case errors.Is(err, runs.ErrCeilingOutOfBounds) [док: v0.go:558]
828: case errors.Is(err, runner.ErrCeilingNotWired) [док: v0.go:562]
server.go: CodeForbidden на 108-109 [док: server.go:99 = комментарий], catch-all CodeNotFound на 144 [док: server.go:134 = комментарий], CodeUnauthenticated в server.go ОТСУТСТВУЕТ вовсе — он объявлен в problem.go:30/111 [док: server.go:109-110]
```
**Положительный контроль:**
```
Тем же методом (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`.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Текст правки верен, обоснование и процедура — нет; передать принимающему три поправки. (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` не встречаются ни разу. Значит «клиент учится по этой прозе неправде» сегодня неверно — клиент вообще не видит этой прозы; вред от протухшей фразы чисто канонический (человек-читатель нормы), и заявлять его надо так.
**Разбор опровергателя:** Опровергнуть не удалось: третье значение и существует, и доезжает до клиента. Проверил самое уязвимое место находки — «а точно ли `ceiling_chapters = 0` это пере-проход, а не какой-то вырожденный прогон»: да, единственное место, где ноль ставится в потолок, — ветка `in.RePass` в runs.go, и она же берёт холд на всю книгу. Проверил обратную гипотезу «автор читал устаревшую копию» — нет: правка `re_pass` и фраза лендились в один день, фраза первой. Класс E назван корректно: README:883 несёт ровно ту же протухшую пару. Но носителей БОЛЬШЕ, чем два, и это меняет объём работы, а не вердикт.
**Разрушающее:** True
---
## 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` разошлись с кодом обеих зон» — находка удержана аудитом 3031.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` (токен уникален и переживёт сдвиг лучше номера).
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Находка верна, предложенный ЯКОРЬ неверен и провалит гейт. 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` гейт считает внешней ссылкой и не проверяет, поэтому починка без пути оставит якорь ровно так же беззащитным. Правка одного якоря в ратифицированном доке — обслуживание, не пере-ратификация.
**Разбор опровергателя:** Воспроизвёл СВОЕЙ командой полностью. 4f56160 — действительно тот коммит, что создал 17-seam-inbound-law.md (110 добавленных строк), и на нём mining.go:221 был `if err := r.seedGlossary(ctx)` — тот самый пере-сид. В рабочем дереве 221 — это `if !stopping {`, ПЕРВОЕ из двух одинаковых условий файла (второе на 233), и оно живёт в ветке стопа --verify-bank, то есть в другом механизме; пере-сид уехал на 243. Сам ФАКТ дока («движок пере-читает сид посреди прогона») остался верным — сгнил только указатель, ровно класс B. Проверил и почему гейт это не поймал: линт даёт 0 проблем, потому что якорь без токена несверяем (708 таких), а форма `mining.go:221` без корневого префикса вообще классифицируется как внешняя ссылка (REPO_TOPS={backend,docs,eval,frontend,platform}, counts.py:384) и не судится никогда.
**Разрушающее:** True
---
## 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 ЗАКРЫТА) роняет неизвестный ключ ГРОМКО — именно поэтому пиннинг бинаря остаётся единственной защитой от чтения нового файла старым кодом». Правка ратифицированного носителя.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Находка верна, атрибуция в предложенной формулировке неточна. Строгость КОДА приехала лендингом 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 при живых соседях — этот номер надо пере-судить тем же заходом, иначе абзац починят наполовину.
**Разбор опровергателя:** Воспроизвёл и усилил. Загрузчик сида строгий: decodeStrict ставит dec.KnownFields(true) (decode.go:61), а единственный вход в него — seed.DecodeFile, который и зовёт memseed.go:51, то есть боевой путь glossary_seed. Отрицательный вывод «нестрогого загрузчика больше нет» подпёрт положительным контролем в обе стороны: греп yaml.Unmarshal по backend/ в НЕ-тестовом коде пуст (тот же греп находит строку-упоминание в комментарии, значит инструмент работает), а счётчик строк бэклога даёт единицы у соседей 209/210/213/214/216 и ноль у 212. Сверх авторской улики нашёл ПРЯМУЮ ратификацию: шапка журнала решений, эррата 27.08-д (D39.159 п.7), дословно «212 ЗАКРЫТА и снята с таблицы: голый yaml.Unmarshal заменён строгим seed.DecodeFile». Довод п.5 закона опирается на дефект, которого нет.
**Разрушающее:** True
---
## 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) вопреки собственному правилу
```
**Положительный контроль:**
```
Соседи 195198 и 201203 возвращают по единице — таблица бэклога читается грепом, и ноль у 199 не артефакт формы.
```
**Другие носители того же факта:** platform/docs/platform-PROGRESS.md:2750 (исторический фикс-лист приёмки: «Труба доставки решений банка в движок — единый бэклог, строка **199(а)**» — тоже описывает дверь как предстоящую)
**Предложенное действие (автора находки):** Привести строку к форме соседей: «~~приём правок банка движковым глаголом~~ **ПОСТРОЕН** 27.08 (`tmctl bank-apply`, D39.158); строка **199(а)** ЗАКРЫТА растворением (D39.156 п.3) и снята с бэклога». Заодно перечитать строку 103 таблицы («приоритет ПАДАЕТ после двери 199(а)») — падение уже произошло.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие верное, но чинить надо ОБЕ строки одним заходом, а не одну: строка 103 той же таблицы («`tmctl init` (форма В) … приоритет ПАДАЕТ после двери 199(а) — та забирает её главный довод») стоит в будущем времени о двери, которая построена, — падение уже произошло, и её надо переписать в прошедшее. Атрибуцию брать точную: дверь — D39.158 (лендинг d1eb8a9), развилка 199(а) растворена D39.156 п.3, сама строка снята с бэклога сверткой D39.166 п.3. ⚠ ВТОРОЙ НОСИТЕЛЬ из находки трогать НЕ НАДО: platform/docs/platform-PROGRESS.md:2750 сидит внутри раздела «### Фикс-лист приёмки — завести строками СВОЕГО бэклога» (:2738) — это ХРОНИКА чужой зоны, запись о том, что было верно на своей дате, а не живой носитель факта; правка чужого журнала задним числом стирает след, а не чинит его.
**Разбор опровергателя:** Подтверждено четырьмя независимыми уликами. (1) Глагол существует: `bank-apply` в списке команд tmctl (:185) и в диспетчере (:249). (2) Собственная шапка дока на строке 3 объявляет эту дверь ПОСТРОЕННОЙ. (3) Строка 199 снята с бэклога — счётчик даёт ноль при единицах у всех соседей 195-198 и 201-203 (положительный контроль на форме таблицы), а D39.166 п.3 фиксирует «Бэклог держал ДВЕ ЗАКРЫТЫЕ строки (199, 215)»; развилку 199(а) закрыл ещё D39.156 п.3 «РАСТВОРЕНИЕМ». (4) Решающее: я поднял диффом коммит 2f97a3a от 30.08 («two seam doors were still listed as forthcoming») — он в ЭТОЙ ЖЕ таблице пере-написал строки 211 и 213 в форму «~~…~~ ПОСТРОЕН», а строку 199(а) прошёл мимо. Значит это не осознанное решение прежней сессии, а пропуск актуализации: соседи получили зачёркивание, эта строка осталась в прошедшем времени.
**Разрушающее:** True
---
## 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** закрывается, комментарий платформы получает строку в её регистр, а проводка полей через шов
становится ВОЗМОЖНОЙ»
```
**Положительный контроль:**
```
Соседние номера 228230 и 232234 дают по единице — счётчик строк работает; ноль у 231 совпадает с явной запиской PROGRESS.md:285 и с телом D39.170.
```
**Другие носители того же факта:** docs/PROGRESS.md:285 (несёт ВЕРНУЮ версию факта — «строк 231 и 236 в таблице БОЛЬШЕ НЕТ»; расхождение именно между ним и доком 17)
**Предложенное действие (автора находки):** Снять обе ссылки на 231: в строке 100 оставить только 124(в); в строке 101 заменить хвост на «⚠ испр.: строка 231 ЗАКРЫТА D39.170 (29.08) — смета пере-прохода доезжает до покупателя; движковая оценка $/глава (строка 166) остаётся открытой САМА ПО СЕБЕ, без родни». Правка ратифицированного носителя.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие в целом верное, но формулировку для строки 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) остаётся открытой сама по себе, без родни». Правка ратифицированного носителя ⇒ нотой оркестратора.
**Разбор опровергателя:** Подтверждено, причём тремя носителями сразу и с рабочим положительным контролем (соседи 228-230 и 232-234 дают по единице, 231 — ноль). Тело D39.170 говорит прямо: «Слепое окно закрыто… строка бэклога 231 закрывается». Сам PROGRESS.md:285 несёт верную версию явной запиской «строк 231 и 236 в таблице БОЛЬШЕ НЕТ — 231 съел пак „деньги“». Сверх авторской улики я проверил ЖИВОЙ КОД чужой зоны: platform/internal/ingest/resync.go:41-47 своим комментарием подтверждает «That blind window is CLOSED» и «`status` will price a re-pass BEFORE it is bought». Значит и ссылка на 231 как на живую родню (:100), и категорическое «смету пере-прохода до покупки показать нечем» (:101) сегодня ложны. При этом соседние номера в тех же двух строках — 124 и 166 — живы (по единице), так что правка адресная, а не снос строк.
**Разрушающее:** True
---
## 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)»; блок помечен как снимок, но пункты 13 уже исполнены
**Предложенное действие (автора находки):** Заменить хвост строки на «**P12** «долги под ногами» — семь открытых major + снятие обхода `--verify-bank`, ПРИНЯТ и ЗАЛЕНДЖЕН 31.08, D39.180, вместе с минором 0.9.0».
**Разбор опровергателя:** Тело D39.180 (не только строка реестра) читается как «ПРИНЯТ И ЗАЛЕНДЖЕН… Обязательство D39.158 (снятие обхода `--verify-bank`) закрыто целиком», коммит лендинга существует и содержит ровно это. Глоссарий последний раз тронут 30.08 (d7ccc50) — за сутки до лендинга, то есть классическое протухание, а не ошибка автора. Побочный клейм находки про «major 2 против 7» тоже подтверждён: CURRENT-STATE считает открытых рядов регистра 97 при major 2. Глоссарий лежит в онбординг-пути, так что свежая сессия получает ложное «на приёмке».
**Разрушающее:** True
---
## N051 · MAJOR · `docs/glossary.md:22` · E-второй-носитель
**Срез:** Глоссарий и продуктовые требования — docs/glossary.md · docs/product-requirements.md
**Вердикт:** ПОДТВЕРЖДЕНО
**Цитата из дока (дословно):**
> - **V0V5** — секции идей владельца в `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**...)» — ВЕРНЫЙ носитель
**Предложенное действие (автора находки):** Заменить на «**V0V6** — секции идей владельца в `START_PROMT.MD` (живой бриф; разобраны в `product-requirements.md`; V6 от 18.08 — ПТ-37..ПТ-43)». Впредь диапазон в глоссарии двигать тем же коммитом, что разбирает новую V-секцию.
**Разбор опровергателя:** Опровергнуть не удалось: секция V6 физически есть в живом брифе (START_PROMT.MD:97, файл 102 строки, то есть V6 — последняя и не обрезана), а парный носитель — шапка docs/product-requirements.md:3 — уже пишет «идеи V0**V6**» и несёт разбор строками ПТ-37…ПТ-43 под заголовком §80 «идеи V6». Значит две копии одного факта разошлись, и занижена ровно та секция, которая предлагает переделку архитектуры пайплайна. Класс E назван верно.
**Разрушающее:** 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 1315.08. Два других живых носителя того же числа несут ⚠-оговорку про множитель ×4.24.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/ранобэ (⚠ число ДО пере-пина цен 1315.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: «⚠ число ДО пере-пина цен 1315.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 (они не в моём срезе).
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие в целом верное, но одну его половину надо адресовать точнее: фразы «отстаёт на СЕМЬ миноров» в самой ПТ-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 (сама ячейка предупреждает, что номер двигается).
**Разбор опровергателя:** Пытался опровергнуть — не смог. Нормативная спека В ДЕРЕВЕ несёт version: 0.9.0, и внесена она коммитом 0a680a3 от 31.08, чьё сообщение прямо называет лендинг P12 вместе с минором; D39.180 (реестр, строка 244) это ратифицирует. Значит условие «он придёт с лендингом пака P12, до него канон 0.8.0» наступило и обе фразы ячейки про 0.8.0 сегодня ложны. Носители из other_carriers проверены и тоже протухли: docs/architecture/09-target-architecture.md:12 «Канон контракта — 0.8.0»; docs/architecture/14-api-contract/README.md:8-9 «0.2.3 при каноне 0.8.0»; docs/PROGRESS.md:3 «зеркало отстало на ШЕСТЬ миноров (0.2.3 против канона 0.8.0)» — при каноне 0.9.0 миноров действительно семь (0.30.9).
**Разрушающее:** True
---
## 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 её не размораживает, разморозка отдельным словом владельца)».
**Разбор опровергателя:** Тело D39.147 п.1 прочитано целиком и говорит буквально то, что заявлено: условие разморозки амендировано, лендинг P7 зону больше не размораживает. P7 залёнджен 20.08 (D39.153), то есть названный в ПТ-35 гейт прошёл и ничего не открыл — остаток по-прежнему недостижим, но адресован пройденной вехе. Дополнительно я проверил происхождение: «после P7» впервые появилось 15.08 (33b4aa6), за двое суток ДО D39.147, — то есть якорь родился верным при D39.136 п.2 и сгнил амендментом. Парный носитель в том же доке (ПТ-20) и docs/glossary.md:11 факт про D39.147 несут правильно, так что док спорит сам с собой.
**Разрушающее:** True
---
## N055 · MAJOR · `frontend/README.md:7` · A-дрейф
**Срез:** Доки замороженной зоны фронта — frontend/**/*.md (10 файлов, 5167 строк)
**Вердикт:** ПОДТВЕРЖДЕНО
**Цитата из дока (дословно):**
> Продуктовых экранов
ещё нет — они на S4S7 (`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; S5S7 — впереди, зона ЗАМОРОЖЕНА (D39.136 п.2 + D39.147)».
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Формулировка предложенной замены верна, но названный «второй носитель» — ОШИБКА: строка Ф-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 — поправить первым касанием»).
**Разбор опровергателя:** Опровергнуть не удалось: экраны не «фикстуры», а продуктовые компоненты на слое данных (`useQuery(libraryQuery())`, `bookQuery`, `chaptersQuery`, `bankQuery` внутри `Showcase.tsx`), S4 залендена коммитом `267aa35` и принята D39.135 15.08. Перечисление пройденных этапов в шапке обрывается на S3.7 при том, что S4 в том же дереве. Утверждение «продуктовых экранов ещё нет» ложно.
**Разрушающее:** True
---
## 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`.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Содержание правки верно; уточнить якорь: тело флага занимает `mining.go:202-207` (строка 202 — заголовок коммента с ссылкой на D39.144), а не «201». ⚠ Не переписывать Ф-44 в «строка закрыта»: пинг №17 (`frontend-PROGRESS.md:508`) ратифицирует не закрытие, а ПЕРЕЧТЕНИЕ — «строки Ф-44/Ф-45/Ф-46 при разморозке читать через эту модель: действие подписи = один ОК + опциональные точечные правки». Хвост «экран действия строить при разморозке» остаётся живым. Исполнитель — фронт-сессия (зона заморожена).
**Разбор опровергателя:** Опровергнуть не удалось ни по одной из трёх половин. (1) Гейт полноты снят D39.144 — это прямое слово владельца, ратифицированное. (2) Канон 0.9.0 в таблице resume отвечает `202` на `awaiting_bank` «with ANY state of decisions» — `409` там ТОЛЬКО про потолок/кредит/нестоповые состояния. (3) Якорь `mining.go:201` уехал буквально: строка 201 пуста, тело стоит на 202-207 и цитирует D39.144 против той самой модели, которую утверждает Ф-44. Второй носитель Ф-14 (`BACKLOG.md:16`, «стоп снимается ТОЛЬКО полным набором (mining.go:200-202)») назван верно — я его прочитал.
**Разрушающее:** True
---
## 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`, всё-или-ничего, превью перед сохранением, эффект — на СЛЕДУЮЩЕМ прогоне».
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Текст замены верен, но ОСТАЛЬНЫЕ НОСИТЕЛИ ПРАВИТЬ НЕЛЬЗЯ, и это важнее самой правки: (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`, всё-или-ничего, превью).
**Разбор опровергателя:** Положительный контроль автора воспроизведён и усилен: `decline` в каноне 17 вхождений, `bank/corrections` — живой путь со схемой `BankCorrection`, а `promote`/`BankDecision`/`bank/decisions` — ровно ноль. Это не опечатка грепа. Зеркало 0.2.3 в том же дереве несёт `/books/{bookId}/bank/decisions` — значит утверждение Ф-45 родилось верным и убито ломающим 0.5.0 (README:75). Класс B назван правильно.
**Разрушающее:** True
---
## 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`.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие верно, но формулировка «опрос СНОСИТСЯ» слишком категорична для строки бэклога: `intakePollMs` обслуживает ДВА чтения (`queries.ts:171` библиотека и `:178` карточка книги), и снос — не одна строка, а переезд обоих на подписку плюс `useIntakeEnd.ts`. Записывать так: «Ф-56 ОТВЕЧЕНА каноном (Б-6а, `GET /books/{bookId}/events`); опрос остаётся легитимным лечением до синка зеркала; при разморозке — переезд обоих чтений (`queries.ts:171,178`) и `useIntakeEnd.ts` на поток». Исполнитель — фронт-сессия.
**Разбор опровергателя:** Отрицательный вывод предъявлен с положительным контролем и он воспроизводится: в каноне три пути `/runs/{runId}*`, и `events` среди них нет; вместо него `/books/{bookId}/events`, чьё описание называет ровно ту причину, которую строка Ф-56 держит как нерешённую («a book is received and cut into chapters before any run exists»). В зеркале 0.2.3 путь на прогоне ещё стоит — строка родилась верной. Диспозиция «оркестратор → контракт» исполнена.
**Разрушающее:** True
---
## 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`» — продуктовой развилки больше нет.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Находка верна, но список носителей НЕПОЛОН — есть ТРЕТИЙ, и он в коде: докблок `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`, но в диспозицию его назвать надо, иначе при разморозке поправят строку бэклога и оставят ложь в коде. Исполнитель — фронт-сессия первого касания.
**Разбор опровергателя:** Опровергнуть не удалось. Ручка есть в каноне 0.9.0 с ОБЯЗАТЕЛЬНЫМ `language_pairs`; в зеркале 0.2.3 её нет вовсе — то есть Ф-57 родилась верной и стала ложной с батчем. Пинг №17 п.2 в том же дереве это уже зафиксировал; носитель не поправлен. Класс C верен: продуктовая развилка («обещать пары списком или отвечать отказом») контрактом РЕШЕНА в пользу списка со статусом.
**Разрушающее:** True
---
## 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).
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие верно по существу. Одно уточнение: «жанр из формы снять (Б-23)» — это НЕ хвост Ф-62, а отдельное слово владельца (D39.136 / пинг №17 п.4: «жанр из продукта УХОДИТ совсем»), и вешать его хвостом на строку про переименование значит спрятать самостоятельное решение внутри чужой строки — завести отдельной зонной строкой при разморозке. Исполнитель — фронт-сессия (зона заморожена, D39.147).
**Разбор опровергателя:** Обе половины утверждения сегодня ложны и обе были верны на 0.2.3 (в зеркале у `/books/{bookId}` действительно только `get`). Продуктовый вопрос, который строка держит открытым («нужна ли правка метаданных книги и в какой форме»), отвечен владельцем 16.08 и ратифицирован — сам зонный журнал это уже записал строкой 498, носитель не поправлен.
**Разрушающее:** True
---
## 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)».
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Формулировка замены верна по существу, но: (а) третий носитель `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.
**Разбор опровергателя:** Подтверждено И замером, И ратификацией. Пункт РОДИЛСЯ верным (зеркало 0.2.3 в том же дереве до сих пор несёт `required: [draft, edit]`) и сгнил дрейфом канона: 0.9.0 кладёт на провод одну пару в ГЛАВАХ + открытую строку `stage`; `phases`/`draft_done`/`edit_done` в каноне ноль вхождений при том, что схема `Progress` грепается по имени — инструмент в этой области видит. D39.138 ратифицирует именно это. Носителей действительно три, и все три названы правильно (я сверил построчно: PLAN:692, STACK:249, PROMPT:437).
**Разрушающее:** True
---
## 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) — носитель не поправлен.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие «заменить §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` + движковый экспорт банка `<db>.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 «при разморозке обновить все три». Единственное, что здесь принадлежит оркестратору сегодня, — ⚠-баннер/запись в зонном журнале.
**Разбор опровергателя:** Раздел действительно протух, и это не мнение, а уже РАТИФИЦИРОВАННЫЙ вывод: аддендум к пингу №17 (frontend-PROGRESS.md:504 п.2) дословно говорит «`STACK_DECISIONS.md` §8 протух целиком: все четыре пункта ПОСТРОЕНЫ». Мой независимый замер: п.1 (пофазный прогресс) = `PhaseProgress` row 99; п.2 (манифест) = row 100, писатель `manifest.go:401`; п.3 = row 101; п.4 (трасса) = row 102, `TM_TRACE_ID` принимается средой (`main.go:152`). Все пять строк отсутствуют в живом бэклоге при положительном контроле на четырёх живых номерах, и закрыты нотами D39.122 / D39.131.
**Разрушающее:** True
---
## 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 <book.yaml>` делает пред-миграционный бэкап и переводит 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 (архив, под баннером — легитимно)
**Предложенное действие (автора находки):** Снести абзац 251253 целиком (его содержимое опровергнуто абзацем 215217 того же файла) и заменить на: «Проверка исполнением ИСПОЛНЕНА паком P7: v14→v15 прогнан на стенде, exit 13 + `schema_mismatch found=14 expected=15` наблюдён живьём». Абзац 255258 пере-формулировать: форма ошибки известна, НЕ построен только автомат самолечения (PD-201, живой остаток).
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Не «заменить абзац 251253 на новую формулировку про P7» — это завело бы ВТОРОЙ носитель того же факта в одном файле, ровно тот класс, из-за которого он и разошёлся. Абзац 251253 снести и на его месте оставить одну отсылку вверх («проверка исполнением — см. врезку P7 выше»), а сам факт держать в единственном месте (строки 187189). Абзац 255258 не сносить: его живой остаток — автомат самолечения — по регистру PD-201 действительно open; пере-написать только зачин «движковый `migrate` ПРИДЁТ с машиноразличимой ошибкой» на «форма ошибки финализирована (D39.134, `schema_mismatch found=N expected=M`); не построен автомат».
**Разбор опровергателя:** Опровергнуть не удалось: файл противоречит сам себе внутри одного раздела. Строки 187195 того же файла говорят «Проверено исполнением на стенде (P7)» и «УСЛОВИЕ ВЫПОЛНЕНО — правка 29.08, глагол существует и заленджен», а строки 251253 через 60 строк утверждают обратное — «движковой команды ещё нет». Дерево на стороне первой редакции: `backend/cmd/tmctl/migrate.go` лежит, стоит в списке команд `main.go:184` и в `case` на 245, и у отказа есть свой код 13. Регистр PD-201 независимо подтверждает прогон на стенде. Первый абзац раздела (182) при этом фиксирует, что ПРЕЖНЯЯ цитата ошибки в этом же доке была мёртвой — то есть чистку 29.08 делали, но до хвоста не дошли.
**Разрушающее:** True
---
## 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`». Баннер 106116 свернуть в исторический (с датой) и поправить счёт 41→40; строку 145 «НЕ ВЗЯТ» пометить как историю P7. П-19 в BACKLOG.md — закрыть/пере-скоупить (зона платформы, не мой ход).
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие верно, но формулировку замены поправить, чтобы она не спорила с ратифицированной нотой: писать «40 запросов, вынесенных в ЧЕТЫРЕ файла `pgstore/queries/`» (D39.172 говорит «сорок запросов пяти файлов pgstore» — это счёт по исходным Go-файлам, и переписывать его в «четырёх файлов» нельзя, номер бьёт док). Строку 145 «НЕ ВЗЯТ» и блок 136147 не сносить: это записанный ДОВОД отказа P7, улика решения; пометить датой как историю пака. П-19 в `platform/BACKLOG.md` — зона платформы, не ход оркестратора: пинг, а не правка.
**Разбор опровергателя:** Опровергнуть не вышло — всё воспроизвелось. sqlc взят и заленджен: `platform/sqlc.yaml`, четыре файла запросов, генерённые `*.sql.go`, пин `SQLC_VERSION := 1.31.1` и `sqlc-check` пререквизитом `check` в Makefile, ратифицировано D39.172. Клетка 159 при этом объявляет инструмент неисполненным, а верхний баннер (106116, «ЧИТАТЬ ПРЕЖДЕ ВСЕГО») — работой, ждущей отдельной сессии. Побочное утверждение находки о счёте тоже подтвердилось точно: 15+6+13+6 = 40 в четырёх файлах, `observe.go` не конвертирован (нет ни `observe.sql`, ни `observe.sql.go`), тогда как баннер держит «пять файлов … idempotency(7) · sessions(5) · observe(1) = 41 запрос». Важная оговорка для исполнителя: сама D39.172 пишет «Сорок запросов ПЯТИ файлов `pgstore`» — то есть ратифицированная нота считает по исходным Go-файлам, а не по `.sql`; путать эти два счёта нельзя.
**Разрушающее:** True
---
## 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` (на этом хосте несёт) — без родителя лист контроллеров не получит.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие верно, но одного вставленного уровня мало: рецепт обязан лечить ОБА уровня, иначе он останется ложно-успокаивающим на свежей машине. Правильная форма — `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`, и это второй разошедшийся носитель одного факта.
**Разбор опровергателя:** Проверено исполнением на живом хосте: путь из рецепта ОТСУТСТВУЕТ, а путь через `tm.slice` СУЩЕСТВУЕТ — это правило именования systemd (срез `X-Y.slice` живёт внутри `X.slice`), а не особенность машины. Положительный контроль сильный: тот же `find` находит настоящие вложенные срезы (`user.slice/user-1000.slice`, `system.slice/system-*.slice`), то есть инструмент видит cgroup-дерево; отсутствует именно рецептный путь. Больше того, замер поймал ровно ту болезнь, ради которой рецепт писан: `tm-runs.slice/cgroup.subtree_control` СЕЙЧАС пуст, а родительский `tm.slice` несёт `memory pids` — то есть исполнить рецепт нужно, и в нынешней записи он даст ENOENT. Имя среза в коде подтверждено (`runner.go:54`). Второй носитель тоже реален и назван неточно: `p8-review/README.md:23` говорит про опустевший контрол у `tm.slice`, тогда как чинить надо у `tm-runs.slice` — два дока называют разные уровни.
**Разрушающее:** True
---
## 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` — внутренняя терсная обёртка)».
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие в целом верно, но формулировка неточна: `ingest` — не «терсная обёртка», а точка входа по расширению (она разветвляется на `ingestTXT`/`ingestEPUB`), и главное — она ТЕСТОВАЯ: вне пакета недостижима, вызывается только из `internal/chunk/ingest_test.go`. Точнее: «`IngestEncoded``Document` (единственный экспорт пакета; неэкспортированная `ingest` — вход по умолчанию для тестов пакета)».
**Разбор опровергателя:** Воспроизведено с положительным контролем. Указатель на несуществующий экспорт, родившийся ошибкой в момент написания. Класс B — подтверждаю, но severity автор завысил: это minor и он сам так его и пометил; на поведение не влияет, стоит одного пустого грепа читателя.
**Разрушающее:** True
---
## 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; если её всё же двигать, сначала править ноту.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Находка верна, ДЕЙСТВИЕ — нет, в двух местах. (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 не трогать вовсе.
**Разбор опровергателя:** Самый опасный класс проверил жёстче прочих: git grep по всему трекаемому дереву (а не по --include=*.md/*.go, как у автора) даёт тот же результат — живых читателей у четырёх записок нет. Факт «протухла» подтверждаю: шапки указывают в несуществующие файлы, паки приняты D39.158.
**Разрушающее:** True
---
## N075 · MINOR · `docs/PROGRESS.md:3` · B-мёртвый-указатель
**Срез:** ГОЛОВА журнала: CURRENT-STATE и очередь — docs/PROGRESS.md, строки 118 (шапка ⟶ ТЕКУЩЕЕ СОСТОЯНИЕ, ~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/`».
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Исполнять только по ОДНОМУ файлу. Действие: либо перенести docs/BACKEND_COLDRUN_V16_SESSION_PROMPT.md в docs/archive/prompts/BACKEND_COLDRUN_V16_SESSION_PROMPT_2026-08-31.md (дата — по отчёту COLDRUN_V16_REPORT_2026-08-31.md) и оставить текст шапки как есть, либо оставить файл на месте и написать «промт отработан, лежит в `docs/`». Про BACKEND_MONEY_HONESTY_SESSION_PROMPT.md НИЧЕГО не делать — он уже в docs/archive/prompts/ (bb541a8, 31.08); попытка «вынести его в архив» ничего не найдёт. Также сверить строку промта в docs/README.md, если она называет путь: перенос сделает её мёртвым указателем.
**Разбор опровергателя:** Ядро находки воспроизведено: указатель шапки «в archive/prompts/» ведёт в пустоту, файл лежит в docs/, и положительный контроль доказывает, что каталог архива грепается и содержит однокоренные имена (спутать V16 с двумя другими COLDRUN-промтами действительно легко). Но ВТОРАЯ половина находки опровергнута деревом: BACKEND_MONEY_HONESTY_SESSION_PROMPT.md в корне docs/ отсутствует с 31.08 (коммит bb541a8) — автор находки читал устаревшее состояние, ровно ловушка «истина — рабочее дерево».
**Разрушающее:** True
---
## N076 · MINOR · `docs/PROGRESS.md:3` · A-дрейф
**Срез:** ГОЛОВА журнала: CURRENT-STATE и очередь — docs/PROGRESS.md, строки 118 (шапка ⟶ ТЕКУЩЕЕ СОСТОЯНИЕ, ~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 как «развязки нет, но линия не запушена».
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Первую половину предложенного действия принять: заменить улику на «origin/main — ПРЕДОК main, расхождения нет; не запушено 48 коммитов (замер 01.09, `git rev-list --left-right --count main...origin/main`)». ВТОРУЮ половину — «либо снять пункт с листа владельца» — НЕ исполнять: противоречия между строками 3 и 11 нет. Строка 3 говорит «расхождения (развилки) нет», строка 11 держит на владельце ДЕЙСТВИЕ (force-push его рукой, не пуллить), и замер это действие подтверждает как всё ещё нужное — 48 неопубликованных коммитов. Снятие пункта с листа владельца потеряло бы живое обязательство.
**Разбор опровергателя:** Опровергнуть не удалось, и я закрыл единственную лазейку, о которой автор не подумал: «origin/main» мог быть протухшим tracking-ref'ом — сходил ls-remote (сетевое чтение, $0), живой удалённый main тоже 8977001. Улика шапки «на одном коммите» сегодня ложна: расхождение 48/0. Несущий вывод («развязки нет») выжил — origin/main остаётся предком main.
**Разрушающее:** True
---
## N077 · MINOR · `docs/PROGRESS.md:3` · A-дрейф
**Срез:** ГОЛОВА журнала: CURRENT-STATE и очередь — docs/PROGRESS.md, строки 118 (шапка ⟶ ТЕКУЩЕЕ СОСТОЯНИЕ, ~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 его не взял»), чтобы число не гнило при каждом лендинге.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие верно, и лучше исполнить именно вторую, критериальную его половину: заменить ручной счёт на «ни один пак платформы после P7 этот долг не взял (проверка: `grep -rn 'createExport\|getExport' platform/internal/ --include=*.go` — пусто)», чтобы число не гнило при каждом следующем лендинге. Если счёт всё-таки оставлять — «шесть паков (P8-FIX · P8-REVIEW · P9 · P10 · P11 · P12), плюс непронумерованный `sqlc`».
**Разбор опровергателя:** Опровергнуть не удалось. «Пять» было верно ровно до 31.08; лендинг P12 объявлен В ТОЙ ЖЕ строке шапки, а счётчик рядом не пересчитан — классический дрейф от ручного счёта. Заодно я проверил то, что автор находки принял на веру: указатель П-17 жив и несёт названный текст, а вывод «ни один его не взял» подтверждён кодом при непустом положительном контроле.
**Разрушающее:** True
---
## N078 · MINOR · `docs/PROGRESS.md:15` · C-исполненное-как-открытое
**Срез:** ГОЛОВА журнала: CURRENT-STATE и очередь — docs/PROGRESS.md, строки 118 (шапка ⟶ ТЕКУЩЕЕ СОСТОЯНИЕ, ~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/ранобэ (⚠ число ДО пере-пина цен 1315.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 — таблица (строки 57272 файла), проверка на класс 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` на длинной книге, с новым гейтом вместо отработавшего.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие верно, но сужать надо аккуратнее, чем написано: `families_held=0` замерен на ДЕСЯТИГЛАВНОЙ книге, а строка требует пере-подтверждения на ДЛИННОЙ — значит вывод «ослаблять гард не нужно» пишется с явной оговоркой «на 10 главах», иначе он сам протухнет на первой длинной книге. Тем же касанием починить указатель гейта «§6 отчёта»: в отчёте холодного прогона §6 — это «Две ловушки `build`», числа семейств в §9; ставить греп-указатель (`families_held` по отчёту), а не номер секции.
**Разбор опровергателя:** Числа я взял НЕ из отчёта, а из собственного stderr прогона на диске (через симлинк `books/`, с `-h` по логам) — `families=11 families_refused=0 families_held=0`, и эмиттер этих полей найден в коде (`terminologist.go:479`), то есть имена не выдуманы. Гейт «первый холодный прогон» пал, ответ получен и он отрицательный: ослаблять гард не от чего. Дополнительно нашёл то, чего находка не назвала: указатель «§6 отчёта» в колонке гейта при чтении через отчёт холодного прогона МЁРТВ — §6 там «Две ловушки `build`», а числа семейств лежат в §9. Крест-накрест подписи (§9 зовёт эти числа «Строкой 154») подтверждаю.
**Разрушающее:** True
---
## N080 · MINOR · `docs/PROGRESS.md:183` · B-мёртвый-указатель
**Срез:** ЕДИНЫЙ БЭКЛОГ docs/PROGRESS.md — таблица (строки 57272 файла), проверка на класс C «исполненное как открытое»
**Вердикт:** НА ПЕРЕ-СУД НЕ ВЫНОСИЛАСЬ — проверить своей командой перед правкой
**Цитата из дока (дословно):**
> `unspecified` ратифицирован в каноне (`openapi.yaml:1784-1787`)
**Почему протухло:** Оба line-якоря строки 203 уехали после контрактного минора 0.9.0 (P12, `0a680a3`, 31.08). `openapi.yaml:1784-1787` сегодня показывает поля `id`/`units_done` схемы прогресса; ратификация `unspecified` живёт на 19031906. Соседний якорь `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 — таблица (строки 57272 файла), проверка на класс 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 — таблица (строки 57272 файла), проверка на класс 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 (строки 8997) исполняется при ЛЮБОМ непустом индексе — единственный ранний выход хука это `[ -z "$staged" ] && exit 0` на строке 37. Сам хук объясняет отмену гейта на строках 4247. Следствие уже живёт строкой бэклога 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/` — ждёт слова владельца по развилке», чтобы холодная сессия не приняла его за свой.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Переезд ДЕЛАТЬ — оговорка «если развилка не закрыта, не двигать» снята замером: закрыта. Одним коммитом 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/). Если оркестратор хочет живой указатель — правка допустима, но она отдельное решение, а не часть переезда.
**Разбор опровергателя:** Воспроизвёл своими командами и подтверждаю с усилением. Файл лежит в docs/, в archive/prompts его нет; позитивный контроль честный — каталог грепается (в нём два ДРУГИХ coldrun-промта), и переезды реально случаются (у money-промта --follow даёт три коммита, включая лендинг-переезд, у V16 — ровно один, коммит выдачи 8e475fa). Находка при этом НЕДОСЧИТАЛА носителей и потому недооценила severity: docs/PROGRESS.md:3 — CURRENT-STATE, объявленный ЕДИНСТВЕННЫМ носителем состояния — прямо утверждает «промт `BACKEND_COLDRUN_V16_SESSION_PROMPT.md` отработан, в `archive/prompts/`». Это не норма, а УКАЗАТЕЛЬ, и он сегодня ложен: класс B поверх класса A, и врёт он ровно тому читателю, который по нему пойдёт. Зато КАВЕАТ находки опровергнут: развилка «довести / остановиться» НЕ висит на владельце — она закрыта его словом «не доводить» (05-decisions-log.md:1786, нота передачи D39.184; то же в PROGRESS.md:3 п.2), потрачено $0.436110 из $0.80. Заодно вскрылось, что стала протухшей и сама строка README:25, на которую находка опиралась как на основание осторожности: «отчёт ждёт лендинга» ложно — отчёт заленджен 244f808 (31.08 21:21, предок HEAD) и лежит в docs/archive/reports/COLDRUN_V16_REPORT_2026-08-31.md.
**Разрушающее:** True
---
## N085 · MINOR · `docs/STACK.md:7` · B-мёртвый-указатель
**Срез:** Корневые онбординг-доки: CLAUDE.md · docs/README.md · docs/STACK.md
**Вердикт:** НА ПЕРЕ-СУД НЕ ВЫНОСИЛАСЬ — проверить своей командой перед правкой
**Цитата из дока (дословно):**
> `configs/pipeline-c1.yaml:36-60`
**Почему протухло:** Блок стадии draft лежит на строках 6084 (`stages:` — 59, `- name: draft` — 60, следующая стадия `- name: edit` — 85). На заявленных 3660 сегодня блок `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` лежит на строках 142159 (`escalation:` — 142, `chains:` — 143, `default:` — 153, `adult:` — 154, `budget_usd: 0` — 159). На заявленных 118135 сегодня СОВСЕМ ДРУГИЕ гейты — `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)» (цитируется 06-memory-risk-registry.md F1). Т.е. git находит существующие короткие хэши той же длины.
```
**Другие носители того же факта:** 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) для проектных данных; отдельный файл БД на книгу для банка памяти.
**Почему протухло:** Отдельного файла БД под банк памяти НЕТ и не было построено: у книги ровно ОДНА база `<book_id>.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 «отдельный файл БД на книгу для банка памяти» на «ОДИН файл `<book_id>.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` — можно добавить туда же.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Правка нужна, но НЕ той подстановкой, что предложена: находка в одной строке отображает 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 добавить полсловом «(исходные хэши в отчёте были указаны ошибочно; в истории их нет)», чтобы следующий ревизор не искал переписанную историю.
**Разбор опровергателя:** Оба хэша мертвы — воспроизвёл своей командой в ОБОИХ чекаутах, при работающем положительном контроле на трёх реальных коротких хэшах и при полном сканировании 1032 объектов. Уточнение к находке: они не «сгнили», а были ЛОЖНЫ ПРИ РОЖДЕНИИ — автор писал строку внутри 214e005, когда 7954325 и e861541 уже несли сегодняшние хэши, а совпадение author/committer-дат по всей цепи 04.07 исключает переписывание истории. Второй носитель, который находка не назвала (archive/PROGRESS-2026-07-04-10.md:164-165), одновременно РАЗРЕШАЕТ заявленное «самопротиворечие»: там прямо написано, что dc6ea8e — это КАРКАС, а a32ec2e — волна 1, то есть строка 212 не противоречит подзаголовку «Волна 1 (коммит a32ec2e)», она лишь неаккуратно ставит каркас в перечень «двух волн».
**Разрушающее:** True
---
## 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. Все три — Фаза 12, не блокеры Ф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-opus-4-8 удалены вместе с провайдером anthropic»]
provider: deepseek | gemini | kimi | local | mistral | openai | xai | zai
```
**Положительный контроль:**
```
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
**Вердикт:** ПОДТВЕРЖДЕНО
**Цитата из дока (дословно):**
> ⚠ **С 2930.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`). Лучшее лечение — внести объёмный потолок, гейт схемы и закон раскрытия В САМУ таблицу слоёв и снять оговорку целиком.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие верно; предпочтительна названная автором «лучшая» ветка — внести объёмный потолок, гейт схемы и закон раскрытия В САМУ таблицу слоёв и снять оговорку целиком, иначе числительное будет протухать при каждом следующем лендинге. Если правится только текст — не заменять «двух» на «трёх» (то же протухнет), а писать «ряда» + ссылку на реестр нот.
**Разбор опровергателя:** Опровергнуть не удалось. Док в последний раз правился 30.08 20:20; закон раскрытия (D39.181) и его применение (D39.182) залендены 31.08 21:03 коммитом bb541a8, оба новых поля живут в коде (runevents.go:206, export.go:100). Положительный контроль на пустой греп: 'volume.go|sqlgate' в доке даёт ОДИН хит (строка 12) — инструмент в этом файле такие токены находит; ноль по 'config_drift_basis|VolumeLedger' — отсутствие факта, а не опечатка. Точное числительное «двух» стало ложной гарантией полноты: непокрытых механизмов теперь минимум три. Замечу отдельно: доп-греп показал, что док не знает НИ ОДНОЙ ноты из D39.176184 (0 упоминаний), так что дефект шире названного.
**Разрушающее:** 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).
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие верно и минимально-разрушительно (замена одного токена). Уточнение к сопроводительной записи: писать не «цель переименована», а «ссылка была битой с рождения (оба написания пришли коммитом 225abc0)» — иначе следующая сессия пойдёт искать несуществующее переименование.
**Разбор опровергателя:** Опровергнуть не удалось, и положительный контроль здесь исключительно сильный: 27 из 28 ID таблицы §3 грепаются в ледджере ровно по одному разу, ноль даёт ТОЛЬКО `H14-inversion-guard` — значит инструмент в этом файле работает, и ноль есть отсутствие цели. Реальный заголовок — `H14-D38.4-tail-inversion-guard` (строка 459). Токен `H14-inversion-guard` не встречается больше нигде в docs/ backend/ platform/ eval/ frontend/ CLAUDE.md. Уточнение к формулировке автора: указатель не «уехал» — `git log -S` показывает, что ОБА написания появились одним коммитом 225abc0, то есть ссылка РОДИЛАСЬ битой 13.07 и такой прожила. На класс B это не влияет, но точность цели правки повышает: чинится опечатка автора, а не последствие переименования.
**Разрушающее:** True
---
## 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.139.2) и research/1819.
**Почему протухло:** `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»), промпт резолвится конвенцией `<prompts root>/<pair>/<role>.md`, а калибровка пары уехала в `configs/pairs/<pair>.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 <prompts root>/<pair>/<role>.md (pack-15), so a pair is a DIRECTORY, not a config entry. Drop the map (put the pair calibration in configs/pairs/<pair>.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 на чужую пару» — обе половины проверены и живы.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Находка верна фактически, но действие СЛИШКОМ КРУПНОЕ, и это важно: шапка того же дока (строка 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».
**Разбор опровергателя:** Опровергнуть не удалось. Ключ `prompts:` жив в схеме ровно затем, чтобы быть ОТВЕРГНУТЫМ при загрузке (комментарий pipeline.go:194 + ветка 938-939), ни один боевой конфиг его не несёт, а положительный контроль тем же грепом по тем же файлам находит `prompt_override` — значит пустота не артефакт инструмента. Резолв сегодня конвенционный (`promptConventionPath`), калибровка пары — `configs/pairs/<пара>.yaml` (три файла на диске). Обе половины, которые автор объявил живыми, действительно живы: `Book.LangPair()` (book.go:378) и fail-loud на чужую пару (pipeline.go:955, «never silently substitute another pair's conventions»). То есть цитата описывает форму конфига, которая сегодня роняет загрузку.
**Разрушающее:** True
---
## 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/repair/dc1_time_units.md, backend/prompts/zh-ru/repair/dc1_fractional.md
```
**Другие носители того же факта:** 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») к той же рамке.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие верное, добавить один якорь для принимающего: §D-3 — это `docs/research/18-quality-levers.md:273` (в репо два файла `18-*`: `18-quality-levers.md` и `18-quality-levers-chatgpt.md`; §D живёт в первом, во втором такой секции нет). Правку строки 33 автор назвал сам, она подтверждена (строка существует дословно).
**Разбор опровергателя:** Воспроизводится, и вторым независимым носителем: exp15 объявлен исполненным не только своей шапкой, но и живым 09-target-architecture.md:26 («трек B исполнен, программа ЗАВЕРШЕНА, D39.7/D39.10») и реестром D39.7. Док спорит сам с собой: строки 33 и 41 держат exp15 несданным гейтом, строка 45 в том же доке уже пишет «exp15 ЗАВЕРШЁН». Отдельно проверил то, чего автор не проверял и что могло бы убить его же действие: указатель research/18 §D-3 ЖИВОЙ — это пункт 3 §D по строке 273, дословно «Размер/форма пакета конвенций… не решён», — так что оставление его единственным гейтом не заводит мёртвую ссылку. Класс C верен, severity minor справедлив.
**Разрушающее:** True
---
## 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 | сделано (D39.78): grep пуст.»
```
**Положительный контроль:**
```
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-чекеров).»
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Правку делать, но без двух ошибок предложенного текста. (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), и она ЗАКРЫТА — то есть остатка долга по этому предмету в трекере нет вовсе, доковый пример — последний хвост.
**Разбор опровергателя:** Факт воспроизведён: прод-модуль, названный единственным примером нормы, удалён 24.07, а сама норма написана тем же днём на 19.5 часов раньше — классический «родилось верным, сгнило дрейфом», а не впечатление. Смягчение, которое находка не заметила: токен `checkers_zh_ru` (без `.go`) в дереве всё ещё РЕЗОЛВИТСЯ — в `internal/checks/checkers_zh_ru_test.go`, то есть греп даёт не ноль, и указатель «полумёртвый», а не стерильно мёртвый. Класс тоже назван неточно: сама НОРМА («данные пар-модулей — в langpack») жива и ничего не объявляет открытым — протух только иллюстративный пример, это B, а не C.
**Разрушающее:** True
---
## 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`)». Так правило начинает указывать на файл, правка которого действительно двигает провод.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Замену делать, но без предложенного якоря `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) сохранить — она к идентификатору не относится.
**Разбор опровергателя:** Отрицательный вывод получен с честным положительным контролем (51 соседний идентификатор), поэтому пустой греп — отсутствие символа, а не промах шаблона; историю подтвердил обеими сторонами коммита 952469b. Указатель мёртв дважды: он не резолвится ни в дереве, ни в подсказке «где нельзя трогать русский» — настоящий охраняемый объект переехал в ДАННЫЕ (injection.txt), правка которых двигает `pack.Version()`/снапшот. Два других примера в той же скобке (`⟨проверить⟩`, gender-аннотации) живы, так что протух ровно один из трёх — минорность находки подтверждаю.
**Разрушающее:** True
---
## 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 pack (the miner is inert / W1.5 auto-continues);
internal/config/book.go:85:// set ⇒ the runner loads the pack in W0, failing LOUD …
git blame → 13c4375e (Claude (backend session) 2026-07-19 313)
```
**Другие носители того же факта:** 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`; чистка комментария поведенчески нейтральна и голден не двигает — снимать попутно при правке этих файлов.»
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Не вписывать в норматив предложенный список из восьми 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` находка оценила верно — он несёт норму без ложного хвоста и правки не требует.
**Разбор опровергателя:** Факт воспроизведён моей командой: аббревиатуры волн живут в НЕтестовых комментариях, а норма запрещает их именно «в идентификаторах, логах и комментариях», поэтому хвост «в код … не возвращаются» как ОПИСАНИЕ сегодня ложен (и был ложен уже в момент переноса нормы в док 21.08). Половина про операторский вывод и идентификаторы, наоборот, подтверждается — там чисто. Отмечаю, что перечень находки НЕПОЛОН: её регекс `\bW2\b` не ловит `snapshot_W2` (подчёркивание — словесный символ), и девятый нетестовый комментарий плюс два конфига остались за кадром.
**Разрушающее:** True
---
## 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-преп, обязана обновляться ТА, куда отсылает вторая, иначе расхождение воспроизведётся.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Диагноз верен, лекарство — нет. Автор предлагает «числа держать только в справочнике», то есть сконцентрировать голые номера в одном носителе, — а именно голые номера и есть механизм гниения: в этом же файле 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` ``. Тогда расхождение копий перестаёт быть вопросом дисциплины лендинга: следующий сдвиг кода покраснеет в обеих. Дедупликацию носителей (ссылка вместо чисел) делать можно, но она вторична и без токенов не спасает — сегодняшний единственный носитель тоже протух.
**Разбор опровергателя:** Опровергнуть не удалось. Оба числа справочника ошибочны (186→187, 194→195), в трекере одно из двух уже поправлено (187 ✓, 194 ✗) — то есть копии одного факта в двух ЖИВЫХ носителях действительно разошлись, класс E верен. Проверил и обратную гипотезу «автор читал устаревшую копию»: сверка сделана по рабочему дереву на HEAD 162ab9b, дерево по docs/ чистое (единственная незакоммиченная правка — docs/research/15-voice-and-state.md, к делу не относится). Заявленный автором вывод «прямой контрпример норме файла» подтверждается: лендинг, поправивший трекер, справочник не тронул. Severity minor согласна — обе цели существуют, ошибка в 1-2 строки, читатель дойдёт грепом.
**Разрушающее:** True
---
## 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». Общее правило по месту: сводки среза не должны нести собственную фактуру — только ссылку на пункты, иначе они гниют независимо от тел, как здесь.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие верно, но НЕПОЛНО, и в неполном виде оставит документ наполовину ложным. Автор чинит 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 показывает работающую альтернативу — ⚠-баннер о том, что якоря сводки не актуализируются.
**Разбор опровергателя:** Опровергнуть не удалось. Оба якоря сводки мертвы (815-823 — дедуп-цикл, 395-397 — соседние поля), а фактическое утверждение «тихий no-op без линта» опровергнуто тем же замером, что и находка №1. Класс E назван верно и подтверждён буквально: один факт лежит в ОДНОМ файле дважды (тело пункта, строки 68-72, и сводка, строка 104), и копии разошлись до прямой противоположности — тело говорит «носитель ПОСТРОЕН», сводка говорит «отсутствует». Ключевая проверка на анти-оверфлаг пройдена: ⚠-баннера над этой сводкой нет, тогда как соседняя сводка на строке 162 такой баннер несёт — значит здесь это не легитимное баннированное историческое, а незакрытая гниль. Severity minor занижена относительно риска (сводку читают быстро, и именно она уносится читателем), но как формальная оценка приемлема.
**Разрушающее:** True
---
## 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)».
**Разбор опровергателя:** Проверил положительным контролем, что инструмент видит именно этот файл: grep по 'оркестратор' даёт 4 хита, файл читается. При этом символа '№' в нём НОЛЬ, а шапка прямо объявляет обратную норму — номер не хранится здесь, его выдаёт автор промта передачи (D39.112 п.6). Историческая проверка добивает: до 22.08 (1ccd5e4) '№' в файле было 3, с 23.08 — ноль, то есть указатель именно СГНИЛ. Текущий номер живёт в CURRENT-STATE docs/PROGRESS.md, который объявляет себя единственным носителем («ОЧЕРЕДЬ №22 (единственный носитель — здесь)»).
**Разрушающее:** 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» — на носитель, а не на номер смены.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Уточнение к находке: п.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 носителем этого долга.
**Разбор опровергателя:** Отрицательная половина клейма ПТ-44 подтверждена с положительным контролем: тот же греп по той же зоне находит смонтированные ручки listBooks/getBook (v0.go:75-76), а createExport/getExport в platform/**.go нет вовсе. Указатель же действительно протух — CURRENT-STATE несёт «ОЧЕРЕДЬ №22» (роль передана дважды: D39.177 №20→№21, D39.184 №21→№22). Но находка преувеличивает степень смерти, и это надо сказать принимающему.
**Разрушающее:** True
---
## 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).
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие верное по существу, но НЕ добавлять `backend/` к пути: короткая зонно-относительная форма `internal/pipeline/export.go` — легитимная и поддержана самим линтером (LINT_PREFIXES_RAW включает "backend/", docs/scripts/counts.py:375). Менять надо только хвост: (`internal/pipeline/export.go`=`aligned byte-for-byte`) — тогда якорь переходит из класса «несверяемых 708» в класс «сверено по содержимому 137» и следующий переезд поймает гейт, а не ревизия.
**Разбор опровергателя:** Воспроизвёл сам: в дереве 89-93 — поля структуры отчёта экспорта (TotalUnits/PendingUnits/GhostRows/ConfigDrift), а утверждаемый механизм («--pairs column aligned byte-for-byte to how the edit wave concatenated the members») уехал в комментарий, начинающийся строкой 144, с ключевой фразой на 147. В fbefe33 (02.08) тот же комментарий стоял ровно на 89-93 — якорь родился верным и сгнил дрейфом. Слепота линтера подтверждена ИСПОЛНЕНИЕМ, а не предположением: --lint даёт «0 проблемных якорей» при «без токена — несверяемы: 708» (у автора было 721 — число живое, к сути не относится), а код линтера прямо говорит «НЕ проверяется: якорь без токена (только существование файла и длина)».
**Разрушающее:** True
---
## 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`».
**Разбор опровергателя:** Три независимых носителя в рабочем дереве (генератор маршрутов, список скриншотера, запись приёмки) дают восемь; `/intake` завела S4. Расхождение двух списков падает тестом `src/routes.test.ts` — значит списки заведомо синхронны между собой, и отстала именно проза README. Опровергнуть нечем.
**Разрушающее:** True
---
## N122 · MINOR · `frontend/docs/BACKLOG.md:3` · B-мёртвый-указатель
**Срез:** Доки замороженной зоны фронта — frontend/**/*.md (10 файлов, 5167 строк)
**Вердикт:** ПОДТВЕРЖДЕНО
**Цитата из дока (дословно):**
> они заходят строками единого бэклога через оркестратора (пример: строки 99102 — пофазный прогресс · манифест глав · машиночитаемая таблица подписи · трасса)
**Почему протухло:** Строк 99102 в живом `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 хиту — форма таблицы единого бэклога грепается, ноль по 99102 означает отсутствие строк, а не неверный шаблон.
```
**Другие носители того же факта:** frontend/docs/frontend-PROGRESS.md:249-250 («единый бэклог, строки 99100 … строка 101»), frontend/docs/STACK_DECISIONS.md:245-257 (§8 — тот же набор четырёх пунктов)
**Предложенное действие (автора находки):** Заменить пример на живой (например, строка 169 — проекция банка платформой) либо снять скобку с номерами, оставив механику «запросы к движку идут строками единого бэклога через оркестратора».
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Предложенное действие («заменить пример на живой, например строку 169») ВРЕДНО и я бы его отклонил: живая строка закроется ровно так же, и шапка сгниёт снова — механизм рота встроен в замену. Норма D39.80 существует затем, чтобы исторический номер оставался цитируемым. Верное действие — минимальная приписка, снимающая ложное ожидание у читателя, идущего в живой бэклог: «(пример: строки 99102 … — все закрыты, тела в D39.122/D39.131)». Второй названный носитель (`frontend-PROGRESS.md:249-250`) — ДРУГОЙ и настоящий дефект класса C: он говорит «**Ждёт движка**» о закрытых строках, и вот его править нужно; он же единственный лежит в файле, куда оркестратор пишет.
**Разбор опровергателя:** ФАКТ подтверждён с положительным контролем: строк 99102 в живом бэклоге нет. Но КЛАСС находки («B — мёртвый указатель») опровергается тем же чтением, которое факт доказывает: `docs/PROGRESS.md:59` прямо нормирует, что ID закрытой строки стабилен НАВСЕГДА и на него ССЫЛАЮТСЯ доки и промты (D39.80), а тело уезжает в D-лог (здесь — D39.122 и D39.131). Указатель разрешается в один хоп через реестр и по построению не «уехал». Плюс сама фраза учит МЕХАНИЗМУ («запросы к движку заходят строками единого бэклога через оркестратора»), и как пример прошлого она историчеcки ИСТИННА: эти четыре строки именно так и зашли.
**Разрушающее:** True
---
## 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` теста токенов).
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Уточнение к формулировке: после снятия `--color-hint` в скобках остаётся ОДИН токен, поэтому «несколько токенов, которые ни разу не используются (`--color-current-line`, `--color-hint`)» должно стать «токен, который ни разу не используется (`--color-current-line`)» — иначе «несколько» повиснет над одним именем и получится новая мелкая неправда. `--font-mono` в этой скобке не перечисляют: он назван отдельным предложением выше.
**Разбор опровергателя:** Опровергнуть не удалось: `--color-hint` не объявлен, не используется и не зарезервирован — он исчез при том самом фиксе, который архивная запись описывает («Исправлено: … именной список зарезервированных под Ф-20»), а живой список `reserved` содержит ровно два имени. Остальная фактура Ф-20 (мёртвый шрифт в бандле, `--font-mono` и `--color-current-line` без единого `var(...)`) проверена и ВЕРНА — находка правильно не трогает её.
**Разрушающее:** True
---
## 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`; промты S2S3.7 архивированы в `docs/archive/prompts/FRONTEND_S*`».
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие верно, но ⚠ это НЕ повод трогать сам баннер как «протухший»: баннер — механизм, и он работает; ложна ровно одна ссылка внутри него. Правка минимальная и должна СОХРАНИТЬ довод («файл живой как носитель норм»), заменив мёртвое имя на архивное: «ссылается `frontend/README.md`; промты S2S3.7 архивированы в `docs/archive/prompts/FRONTEND_S*`, S4 — в `frontend/docs/archive/`». Список «других носителей» в находке — НЕ ошибки: `frontend-PROGRESS.md:52/80/147/208` это ХРОНИКА, ссылки на промты в момент их выдачи; переписывать хронику под сегодняшние пути запрещено нормой (историческое). Максимум — одна сноска в журнале «промты S2S4 архивированы, пути см. `docs/archive/prompts/`».
**Разбор опровергателя:** Отрицательный вывод получен НЕ при пустом положительном контроле: тот же самый find без сужения даёт 121 файл, включая сам разбираемый промт и четыре архивных FRONTEND_S*. Значит ноль по `S3*SESSION_PROMPT*` — факт. Вторая половина довода баннера (`frontend/README.md`) жива и проверена — README:48 ссылается. То есть баннер не рушится, у него отваливается одна из двух опор.
**Разрушающее:** True
---
## 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-половину скриншот-цикла).
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие верно; добавить причину, иначе следующая сессия «починит» обратно: тест-раннер `@playwright/test` зоне не нужен ПО ПОСТРОЕНИЮ — тесты гоняет `vitest` (шестой шаг `check`), а playwright работает библиотекой из `scripts/shot.mjs`. Формулировать как «`playwright 1.62.1` (библиотека, не тест-раннер: тесты на vitest) + `@axe-core/playwright 4.12.1`». Второй носитель `FRONTEND_PLAN.md:122` назван верно, я его прочитал.
**Разбор опровергателя:** Отрицательный вывод предъявлен с рабочим положительным контролем: тот же греп по 'playwright' в том же файле даёт две строки, значит ноль по `@playwright/test` — факт, а не промах шаблона. Зона зависит от библиотеки `playwright` (скриншот-цикл в `scripts/shot.mjs`) и от `@axe-core/playwright`; тест-раннер `@playwright/test` не установлен и не импортируется. Пин в таблице называет несуществующий в зоне пакет.
**Разрушающее:** True
---
## 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) — строка не исполнена.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Список носителей НЕПОЛОН: помимо `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/`»). Правку «четвёртым из шести» стоит вносить одним касанием с этой оговоркой, иначе исполнитель перечитает строку и унаследует вторую ложь.
**Разбор опровергателя:** Шагов шесть, spectral — четвёртый (внутри `npm run contract`). Тот же файл на 146-147 несёт верную редакцию, т.е. носитель спорит сам с собой. ФС-6 фикс-листа приёмки S4 называет ровно это и не исполнен.
**Разрушающее:** True
---
## 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 остаётся входом первого касания».
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Единственная из пятнадцати находок, чей носитель — ЗОННЫЙ ЖУРНАЛ, куда оркестратор пишет по решению владельца (CLAUDE.md, таблица ролей). Её можно исполнить сейчас, не дожидаясь разморозки, — в отличие от остальных четырнадцати. Заодно поправить в той же строке 249-250 («**Ждёт движка:** манифест глав и пофазный прогресс — строки 99100, к S5 машиночитаемая таблица подписи — строка 101»): это тот же класс C в том же файле, строки закрыты D39.122, а машинная таблица с тех пор ещё и снесена D39.158.
**Разбор опровергателя:** Проверил главное подозрение — не стоит ли строка 221 под ⚠-баннером «историческое» (тогда находка была бы анти-оверфлагом). Не стоит: баннер в этом файле начинается СТРОКОЙ НИЖЕ (222) и покрывает абзац про замок 04.08. Пункт «Пройдено» — носитель текущего состояния зоны, и он говорит «ждёт приёмки» о том, что принято 15.08 и залендено `267aa35`. Два места одного файла противоречат друг другу.
**Разрушающее:** True
---
## 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/<book_id>`, воркер спавна — `internal/runs/spawn.go`; `ingest.Supervisor` остался дев-путём. Ссылку на «строку 103» снять или заменить номером D-ноты, закрывшей её.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие принимается, с одной поправкой по существу: `TM_PLATFORM_BOOKS_DIR` не имеет дефолта — пустое значение означает «инстанс не принимает загрузок и `POST /books` вообще не монтируется» (`config.go:159-166`). Поэтому нельзя писать «её дефолт — этот каталог»: правильная формулировка — «`/srv/textmachine` это РЕКОМЕНДУЕМОЕ значение для юнита, а не дефолт кода; пусто = загрузки выключены». Иначе новая редакция заведёт ровно тот же класс дрейфа, что чинит.
**Разбор опровергателя:** Воспроизведено полностью. Воркер спавна СУЩЕСТВУЕТ (`platform/internal/runs/spawn.go` задаёт `Workdir: l.Workdir` юниту и зовёт `Status` по нему), корень библиотеки УЖЕ настройка — `TM_PLATFORM_BOOKS_DIR` (`config.go:495`, обязательно абсолютный, `config.go:504`), а каталог книги строится как `filepath.Join(s.Cfg.BooksDir, id)` (`books.go:171`). То есть обещанное абзацем «когда он появится, корень становится настройкой» уже произошло. Отрицательное утверждение про `ingest.Supervisor` я проверил с положительным контролем: тот же греп даёт 13 вхождений всего (инструмент ищет и находит), из них вне тестов — только собственное определение пакета `supervisor.go`, ни одного боевого вызывающего. Указатель «строка 103» мёртв при работающем контроле (175 → 1).
**Разрушающее:** True
---
## 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) — пере-написать по факту исполнения.
### ⚠ ПОПРАВКА ОПРОВЕРГАТЕЛЯ — ИСПОЛНЯЕТСЯ ОНА
Действие верно по существу, координаты — нет: перед правкой пере-грепнуть носители, потому что шесть из семи номеров строк в 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)»); правка доков его не лечит, это пинг бэкенду, а не ход оркестратора.
**Разбор опровергателя:** Факт подтверждён с двойным контролем. Все десять номеров отсутствуют в живой таблице и по строгому, и по свободному шаблону, тогда как контрольные живые строки (175/202/224) и надгробия переехавших (96/97) находятся тем же грепом — инструмент работает. Второй контроль: номера не «выселены» в `13-tech-debt-anchors.md` (там 10 секций Б-*, ни одна не про эти строки), то есть след ведёт только в D-лог, как и предписывает правило `docs/PROGRESS.md:59`. Подтверждается и то, что три из них поданы как ЖИВОЙ незакрытый долг: 265 называет персист манифеста глав «несущим» для латентности, а таблица `chapters` построена миграцией `00002_readmodel.sql:91`. НО: список other_carriers в находке содержит НЕВЕРНЫЕ номера строк — в дереве это 267/317/357/547/554/560 (а не 246/311/348/541/545/539) и deploy 64/179 (а не 63/186). Сам факт это не отменяет, исполнителю нельзя править по этим координатам вслепую.
**Разрушающее:** True
---
## 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 <id> --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, трогать не надо)
**Предложенное действие (автора находки):** Либо дополнить перечень 566569 недостающими семью глаголами, либо (лучше — у факта должен быть один носитель) заменить его одной строкой-ссылкой на `platform/README.md` §«Бинари» и `deploy/README.md` §«Застрявшая работа», как уже сделано с числом гейтов батареи.
**Разрушающее:** False