textmachine/docs/DOC_REVISION_FINDINGS.md

484 KiB
Raw Blame History

РЕЕСТР НАХОДОК РЕВИЗИИ ДОКОВ — 01.09.2026, оркестратор №22

Источник: воркфлоу docs-staleness-revision-A, 15 срезов, 30 агентов, 1432 клейма проверены исполнением, 115 положительных контролей. Найдено 137, адверсариальный пере-суд снял 7. Живых находок: 130 — блокеров 6, major 61, minor 63. Колонка ВЕРДИКТ: ПОДТВЕРЖДЕНО = прошло адверсариальный пере-суд; (пусто) = не выносилось на пере-суд (не разрушающее и не блокер) — такая находка проверена ОДНИМ агентом и на доверии не исполняется.


N001 · БЛОКЕР · docs/PROGRESS.md:176 · C-исполненное-как-открытое

Срез: ЕДИНЫЙ БЭКЛОГ docs/PROGRESS.md — таблица (строки 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) и убрать её из таблицы; в теле ноты сохранить опровержение оси — пере-покупка терминолога идёт от байтов батча, а не от сдвига снапшота. Разрушающее: True ⚠ Опровергатель поправил действие: Закрыть строку 194 — верно, но в ноту закрытия обязаны войти ДВА опровержения её текста, а не одно. Кроме оси «сдвиг снапшота» (опровергнута пингом №2, docs/PROGRESS.md:344 — пере-покупка идёт от БАЙТОВ БАТЧА) ЛОЖНА и первая половина клейма «не входят НИ в число согласия»: тест утверждает раздельно, что committed_usd = SUM(checkpoints) НЕ сдвинулся, потому что деньги банк-ролей были в нём ВСЕГДА (spend и checkpoints пишутся одной транзакцией) — слепа была только ПРОЕКЦИЯ. Плюс назвать честную границу из §9.9: в проекцию добавлено УЖЕ ПОТРАЧЕННОЕ, а не прогноз контура, то есть проекция стала НИЖНЕЙ границей.


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), где она предлагается как незанятая. Разрушающее: 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. Разрушающее: 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. Разрушающее: False ⚠ Опровергатель поправил действие: Находка верна, предложенный текст — нет, он ставит под удар ровно то, чем прошлая смена уже обожглась. Три правки. (1) НЕ объявлять $0.093$0.151 ценой главы: это n=2 главы из десяти, прогон остановлен снапшот-гардом (редакторская волна 3/14), и сам отчёт запрещает такое употребление дословно — «Чего замер НЕ должен делать: калибровать ставку платформы. Она гейчена строкой 202 и требует ЗАВЕРШЁННОЙ цепи» (COLDRUN_V16_REPORT_2026-08-31.md:642). Прогон вдобавок нёс объявленные девиации (черновик шёл БАНКНОТНЫМ промптом вместо конвенционного translator.md; escalation.budget_usd поднят 0.015 → 0.08), спред цены на юнит ×7.6, а причина, по которой редактор съел 73% захода, НЕ УСТАНОВЛЕНА. (2) Несущим доводом сделать РАТИФИЦИРОВАННУЮ арифметику, которая ни от чего этого не зависит: «Числа D39.165 §1 — ИЮЛЬСКИЕ (сама нота, г: прогоны 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.


N005 · БЛОКЕР · docs/architecture/15-money-path.md:35 · A-дрейф

Срез: Денежный путь — docs/architecture/15-money-path.md Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): Команда ОТКАЗЫВАЕТ, если попытка ещё называет юнит или несёт базовую линию траты — над живым движком прогон не закрывают. Холд возвращается ЦЕЛИКОМ в обоих случаях (к abandon допускается только попытка, не дошедшая до движка, значит она ничего не потратила), а флаг решает лишь КОГДА Почему протухло: Абзац описывает ТОЛЬКО живую ветвь run abandon. С пака P11 (PD-385, коммит e548e5a от 29.08) есть вторая ветвь — PHASE=settling: к ней отказ «юнит/базовая линия» НЕ применяется (попытка уже кончилась), она принимает попытку, которая ДОШЛА до движка и потратила, возвращает холд ЦЕЛИКОМ (единственная защита — порог abandonAfter неудач расчёта, отказ ErrSettlementNotStuck), а флаг --release-hold там ИГНОРИРУЕТСЯ: деньги всегда закрываются inline. Все три утверждения абзаца в settling-ветви ложны, и холд возвращается целиком за работу, которую движок реально оплатил. Абзац вошёл в док 22.08 (0e31a8b) и был верен; аудит указателей 31.08 его смысла не касался. Улика — команда: sed -n '292,300p;307p' /home/ubuntu/projects/textmachine-main/platform/cmd/tmplatformctl/runs.go Улика — вывод: // ⚠ THE PARAGRAPH ABOVE IS THE LIVE HALF ONLY, since PD-385. On a run whose PHASE is settling — // finished, with a settlement that has failed and will not close — there is no sweep left to hand // the money to, so the command closes it inline whatever the flag says, and the refusal above does // not apply because the attempt has already ended. What DOES apply there is a different refusal: a // settlement that has not failed even once is turned away, because this command returns the hold // WHOLE and that one is about to be settled correctly for what it really spent. ... release := fs.Bool("release-hold", false, "... ; ignored for a PHASE=settling run, whose money is always closed inline Другие носители того же факта: platform/deploy/README.md:288-291 — тот же абзац дословно, НО там он починен отдельной секцией «Строка PHASE = settling — та же команда, другой исход.» (deploy/README.md:311-318), которой в 15-money-path нет, platform/docs/DEFECT_REGISTER.md:536 (PD-385) и :60 (PD-418) — регистровые носители ветвления Предложенное действие: Дописать в §3 п.7 вторую ветвь: «⚠ С P11 (PD-385) ветвей ДВЕ, и абзац выше — только ЖИВАЯ. У прогона с runs.finished_at is not null (PHASE=settling) отказ по юниту/базовой линии не действует, к abandon допускается попытка, которая ДОШЛА до движка и потратила, холд всё равно возвращается ЦЕЛИКОМ (сдерживает только порог abandonAfter неудач расчёта — отказ ErrSettlementNotStuck), а --release-hold игнорируется: деньги закрываются в той же транзакции (platform/internal/pgstore/runs.go, греп func abandonSettlement().» Живой образец формулировки — секция «Строка PHASE = settling» в platform/deploy/README.md. Разрушающее: False ⚠ Опровергатель поправил действие: Действие в целом верно (дописать вторую ветвь в §3 п.7, образец — секция «Строка PHASE = settling» в platform/deploy/README.md), но добавить в него ДВЕ оговорки, без которых новый абзац сам станет протухшим: (а) ветвление идёт по runs.finished_at, а НЕ по наличию осиротевшей попытки — это ратифицированная оговорка PD-418, дословно стоящая в platform/deploy/README.md рядом с образцом; (б) ЖИВОЙ прогон с намертво заблокированной расплатой уходит в ЖИВУЮ ветвь и отказом по юниту закрыт до сих пор — PD-424 в platform/docs/DEFECT_REGISTER.md стоит open именно на этой половине («терминальная РУЧКА»), и абзац, обещавший бы лечение settling-ветвью на живом прогоне, соврал бы. Плюс мелочь фактуры: холд возвращается по КАЖДОЙ осиротевшей попытке прогона, а не по одной (цикл for _, o := range orphans). Указатели — греп-формой, как и предложено (platform/internal/pgstore/runs.go сегодня чист, но зона под паками движется).


N006 · БЛОКЕР · platform/docs/STACK_DECISIONS.md:414 · D-противоречие-ратифицированному

Срез: Доки зоны платформы: platform/README.md · platform/deploy/README.md · platform/docs/{ENGINEERING_STANDARDS,PLATFORM_DIRECTION,STACK_DECISIONS}.md · platform/docs/p8-review/* · platform/docs/p9/* Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): | mined_delta (путь из book.yaml) | писателя в движке НЕТ — только читатель loadMinedDelta Почему протухло: Строка (и парная ей 415 про mined_rejects) неверна в четырёх местах сразу. (1) mined_delta БОЛЬШЕ НЕ «путь из book.yaml»: ключ РЕТИРОВАН — RetiredMinedDelta string yaml:"mined_delta"`` ловит его, чтобы конфиг эры объявляемых путей ПАДАЛ, а сам путь выводится из book_id (b.MinedDelta = filepath.Join(dir, b.BookID+MinedDeltaSuffix)). (2) «писателя в движке НЕТ» — ложь: писатель есть, это глагол bank-apply (stageFileAtomic(book.MinedDelta, res.DeltaBytes)), тот самый СЕДЬМОЙ канал, который дописан в эту же таблицу 31.08 четырьмя строками ниже. (3) Колонка «Атомарность: —» неверна: запись стейджится атомарно + syncDir. (4) «развилка ждёт ратификации» — развилка ЗАКРЫТА ратифицированной D39.156 п.3 (23.08, слово владельца): «Развилка 199(а) закрывается РАСТВОРЕНИЕМ… движок дефолтит mined_delta/mined_rejects по конвенции каталога… писателем становится движковый $0-глагол ⇒ ключей никто не объявляет». Строка 199 единого бэклога закрыта 28.08 (D39.166), её преемник 231 съеден паком «деньги». Цена ошибки не теоретическая: этот же документ на 424 предупреждает, что расширение набора ключей платформой — ратификация, а декодер движка строгий; сессия, прочитавшая «путь из book.yaml», может вписать mined_delta: в деплой-шаблон, и тогда НЕ ЗАГРУЗИТСЯ КАЖДАЯ новая книга. Улика — команда: grep -n 'RetiredMinedDelta string' backend/internal/config/book.go; grep -n 'stageFileAtomic(book.MinedDelta' backend/internal/pipeline/bankdecisions.go; grep -n 'b.MinedDelta = filepath.Join' backend/internal/config/book.go; sed -n '670p' docs/architecture/05-decisions-log.md | cut -c1-260; for n in 199 175; do printf 'row %s in live backlog: %s\n' $n $(grep -c "^| $n " docs/PROGRESS.md); done Улика — вывод: 115: RetiredMinedDelta string yaml:"mined_delta" 455: if deltaStage, err = stageFileAtomic(book.MinedDelta, res.DeltaBytes); err != nil { 199: b.MinedDelta = filepath.Join(dir, b.BookID+MinedDeltaSuffix) 3. Развилка 199(а) закрывается РАСТВОРЕНИЕМ, а не выбором. Ни один из четырёх вариантов зоны: движок дефолтит mined_delta/mined_rejects по конвенции каталога … писателем становится движковый $0-глагол ⇒ ключей никто не объявляет (это тело D39.156, ратифицировано 23.08) row 199 in live backlog: 0 row 175 in live backlog: 1 Положительный контроль: grep -c "^| 175 " docs/PROGRESS.md → 1 (шаблон грепа строк бэклога РАБОТАЕТ и находит живые строки 175/202/224), при этом для 199 он даёт 0; правило удаления закрытых строк — docs/PROGRESS.md:59 «Закрытые строки здесь НЕ хранятся — они уходят в D-лог с номером решения» Другие носители того же факта: platform/docs/STACK_DECISIONS.md:415 (парная строка mined_rejects, « тот же разрыв»), platform/docs/STACK_DECISIONS.md:424 (хвост строки book.yaml: «Прямо относится к развилке 199(а)»), platform/docs/p9/door-live-probe.md:157 (тот же факт — но ВЕРНО: «правка термина через контрактную дверь доезжает до движковых файлов решений»), docs/architecture/17-seam-inbound-law.md:97 Предложенное действие: Пере-написать обе строки таблицы: путь ВЫВОДИТСЯ из book_id (ключи mined_delta/mined_rejects в book.yaml ретированы и валят конфиг); писатель — движковый глагол bank-apply; атомарность — стейдж+rename+syncDir; колонка «Согласовано?» → с ссылкой на D39.156 п.3 вместо « разрыв… ждёт ратификации». Хвост на 424 «Прямо относится к развилке 199(а)» снять. Разрушающее: True ⚠ Опровергатель поправил действие: Действие принимается как есть. Единственное уточнение к формулировке замены: писателем является не «глагол bank-apply» вообще, а функция writeDecisionFiles (backend/internal/pipeline/bankdecisions.go:450), и порядок переименований в ней НЕ произвольный (rejects первым — аргумент в шапке функции); если таблица описывает атомарность, честнее написать «атомарно ПОФАЙЛОВО (stage+rename+syncDir), но пара файлов НЕ транзакционна — половинное состояние сходится на пере-посылке», иначе колонка «атомарно» пообещает больше, чем код даёт.


N007 · MAJOR · backend/README.md:11 · A-дрейф

Срез: Доки зоны бэкенда: backend/README.md + backend/docs/*.md (9 файлов) Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): | cmd/tmctl | CLI: translate / status … / redrive … / manifest … / migrateПочему протухло: Перечень команд читается исчерпывающим и отстал на ТРИ верба из одиннадцати: backup, seed-lint, bank-apply. bank-apply — это ратифицированная ВХОДНАЯ ДВЕРЬ ШВА (D39.158), то есть единственный способ платформы вернуть движку решения владельца по банку; сессия, читающая карту пакетов, о её существовании не узнаёт. Тем же дефектом страдает «Карта пакетов»: из 18 пакетов internal/ в ней нет bookfile, runevents, standdata — причём internal/bookfile завёл ТОТ ЖЕ коммит 8adcb86, которым строка 11 последний раз переписывалась. Улика — команда: cd backend && sed -n '183,186p' cmd/tmctl/main.go; ls internal/; for t in bookfile runevents standdata backup seed-lint bank-apply; do printf '%-12s ' $t; grep -q -- "$t" README.md && echo IN_README || echo 'NOT IN README'; done Улика — вывод: var dispatchCommands = []string{ "translate", "report", "status", "export", "build", "redrive", "manifest", "backup", "migrate", "seed-lint", "bank-apply", }

archguard bookfile checks chunk config lang ledger llm membank miner obs pipeline runevents seed standdata store terminology text

bookfile NOT IN README runevents NOT IN README standdata NOT IN README backup NOT IN README seed-lint NOT IN README bank-apply NOT IN README Предложенное действие: Дописать в ячейку cmd/tmctl: bank-apply (входная дверь шва — приём документа решений владельца по банку, exit 14 «отклонено» / 15 «запись не завершилась», D39.156/D39.158) · backup (VACUUM INTO + integrity_check, он же pre-flight боевого прогона, строка 83) · seed-lint. В карту пакетов добавить строки internal/bookfile (EPUB 3/txt писатель), internal/runevents (кадры журнала прогона + словарь исходов), internal/standdata (резолв корпусных путей от корня репо). Разрушающее: False


N008 · MAJOR · backend/README.md:24 · A-дрейф

Срез: Доки зоны бэкенда: backend/README.md + backend/docs/*.md (9 файлов) Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): машинная стоп-таблица в mining.go (101); все три пишутся через artifact.go (write-then-rename — их читают ПОКА идёт прогон) Почему протухло: Машинной стоп-таблицы (.bank-stop.json) в движке НЕТ — её снёс пак «входная дверь шва» (d1eb8a9, ратифицирован D39.158); уцелевшая .bank-stop.txt пишется ГОЛЫМ os.WriteFile (mining.go:198), то есть НЕ через artifact.go и БЕЗ write-then-rename. Реальные четыре клиента writeFileAtomic — bankexport, manifest, карта подписи и авто-банк. Строка родилась верной в 0e69bc1 (09.08, коммит, который машинную таблицу и завёл) и сгнила 27.08. Улика — команда: cd backend && grep -rn 'writeFileAtomic(' internal/pipeline/*.go | grep -v _test | grep -v 'func writeFileAtomic'; grep -n 'bankStopTablePath()' internal/pipeline/mining.go | head -3; git log --oneline --all -S'TableJSONPath' | cat Улика — вывод: internal/pipeline/bankexport.go:152: if err := writeFileAtomic(r.bankExportPath(), ... internal/pipeline/manifest.go:401: if err := writeFileAtomic(r.manifestPath(), ... internal/pipeline/mining.go:191: if err := writeFileAtomic(r.signatureMapPath(), mapBytes); err != nil { internal/pipeline/mining.go:722: if err := writeFileAtomic(r.autoBankPath(), []byte(body)); err != nil { 198: if werr := os.WriteFile(r.bankStopTablePath(), []byte(renderBankStopTable(rows)), 0o644); werr != nil { 371: return r.Book.ProjectDB + ".bank-stop.txt" --- git -S TableJSONPath --- d1eb8a9 Land the seam inbound door: ... (removal) 0e69bc1 Land the contract-blockers pack: ... machine bank-stop table (introduction) Другие носители того же факта: backend/internal/pipeline/artifact.go:11 — та же протухшая опись клиентов: «the machine bank-stop table (row 101)» перечислена, а карта подписи (mining.go:191) и авто-банк (mining.go:722) — нет Предложенное действие: Заменить хвост ячейки на: «manifest.go (структура глав/чанков, строка 100), bankexport.go (весь банк тремя статусами, 125), карта подписи и авто-банк в mining.go — все четыре через artifact.go (write-then-rename); человеческая стоп-таблица .bank-stop.txt пишется обычным os.WriteFile (машинная .bank-stop.json снесена D39.158)». Заодно поправить шапку artifact.go. Разрушающее: True ⚠ Опровергатель поправил действие: Действие верное по сути, но опись носителей в находке НЕТОЧНА и предложенная форма сама сгниёт. (1) artifact.go:11-13 авто-банк УЖЕ перечисляет («and the engine's auto-bank (row 231 …)») — из четырёх реальных клиентов в его шапке не хватает ТОЛЬКО карты подписи (mining.go:191), а лишняя — машинная таблица (row 101). (2) Находка пропустила третий носитель: backend/docs/pipeline.puml:72 («.bank-stop.json — машине (строка 101): ПОЛНАЯ таблица») — он вне .md-среза, но правится тем же ходом, и docs/README.md:34 объявляет puml домом диаграмм рядом с кодом. (3) Номера строк бэклога 100/101/125/231 в этой ячейке ВСЕ закрыты (грепом по ^| N | в docs/PROGRESS.md ноль при 189 живых строках) — это исторические ссылки-заказчики, не указатели в трекер; менять их не нужно, но и добавлять новых номеров в замену не надо. (4) В самом тексте вместо якорей-строк называй символы (bankExportPath · manifestPath · signatureMapPath · autoBankPath): именно line-якоря в этом README и протухают быстрее всего.


N009 · MAJOR · backend/README.md:82 · D-противоречие-ратифицированному

Срез: Доки зоны бэкенда: backend/README.md + backend/docs/*.md (9 файлов) Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): Пути дефолтятся ОТ $HOME и от корня репозитория (не от /home/ubuntu), так что клон под любым пользователем и по любому пути находит то, что есть Почему протухло: От $HOME НЕ дефолтится НИЧЕГО. internal/standdata резолвит корпус ТОЛЬКО от корня репозитория (<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 // /books (D39.157 п.2, moved there on 24.08; the previous address $HOME/books is dead). // Both are derived from the repository root found by MARKER, never counted in ".." and never taken from // $HOME: a default that reads a home directory is wrong for every machine but the one it was written on const standRoot = "books" // StandFile resolves parts against the stand corpus root (/books).

5:2. Корень книг — <репозиторий>/books, отдельный git. Каталог перенесён владельцем внутрь дерева

lrwxrwxrwx /home/ubuntu/books -> /home/ubuntu/projects/textmachine/books Другие носители того же факта: backend/README.md:47 — «Книга-стенд: /home/ubuntu/books/gu-zhenren/», backend/README.md:79 — «~/books/gu-zhenren/{rerun/records.json, guzhenren-seed-v2.yaml}», backend/README.md:80 — «~/books/gu-zhenren/labels/», backend/cmd/tmctl/backup.go:28 — «the stand book lands under ~/books//…» Предложенное действие: Строку 82 переписать: «Пути дефолтятся ОТ КОРНЯ РЕПОЗИТОРИЯ, найденного по маркеру backend/go.mod (internal/standdata); корпус — <репозиторий>/books (D39.157 п.2). $HOME не читается вовсе.» В строках 47/79/80 заменить /home/ubuntu/books и ~/books на <репозиторий>/books. Отдельной строкой — backup.go:28. ⚠ Строка бэклога 217 покрывает только СКРИПТЫ полигона, носителя для этого README-дрейфа в трекере нет. Разрушающее: True ⚠ Опровергатель поправил действие: Действие верное; уточняю две вещи. (1) Половина фразы («и от корня репозитория», «не от /home/ubuntu») ВЕРНА — переписывать надо не всю строку, а снять только «ОТ $HOME и»; предложенная редакция это делает, но не должна выглядеть как опровержение всей строки. (2) По строкам 47/79/80: замена на <репозиторий>/books правильна, но проверь, что имелось в виду — на ЭТОЙ машине оба адреса резолвятся (/home/ubuntu/books/home/ubuntu/projects/textmachine/books, и books/ внутри самого чекаута — тоже симлинк туда же), так что «сломано» тут не наблюдаемо и правка чисто анти-дрейфовая: пиши её как «адрес по D39.157 п.2», а не как «путь не работает» — иначе следующая сессия проверит ls и решит, что ты ошибся. (3) backend/cmd/tmctl/backup.go:28 — код бэкенд-зоны, не док: это ПИНГ бэкенд-сессии, а не правка оркестратора.


N010 · MAJOR · backend/README.md:99 · C-исполненное-как-открытое

Срез: Доки зоны бэкенда: backend/README.md + backend/docs/*.md (9 файлов) Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): Остаточные Cyrillic-хардкоды пар-гейтед DC-чекеров (checks/repair.go:131, checks/checkers.go:457,474) — инертны без dc-данных пары; кандидат на c.wordScript (строка 79). Почему протухло: Долг ПОГАШЕН и это записано в двух носителях. Код по обоим якорям сегодня говорит обратное README: repair.go:131 — «The boundary is the TARGET's declared word script, not a hardcoded Cyrillic predicate (строка 79, D39.39)», repair.go:190 — «TARGET-SCRIPT DEBT REPAID (D39.24 §15.3 amended by D39.62/П1)», checkers.go:494 — «data-driven off c.wordScript instead of a hardcoded Cyrillic predicate». Сама строка бэклога 79 фиксирует: «Первые два компонента строки (isCyrLetter→isTargetWordLetter · magnitude SourceScripts-гейт) исполнены пакетом-чекеров, D39.78». Плюс мёртвые line-якоря: в checkers.go по 457/474 лежит lintRegisterLexicon, а нужный комментарий — на 494. Улика — команда: cd backend && sed -n '131p;190,191p' internal/checks/repair.go; sed -n '494p' internal/checks/checkers.go; grep -nE '^\| *\*{0,2}79\*{0,2} *\|' ../docs/PROGRESS.md | cut -c1-260 Улика — вывод: // TARGET's declared word script, not a hardcoded Cyrillic predicate (строка 79, D39.39). // TARGET-SCRIPT DEBT REPAID (D39.24 §15.3 amended by D39.62/П1): the word boundary was unicode.Cyrillic in // BOTH the lint tokenizer and here; it is now the target's DECLARED word script (data), so a non-Cyrillic // word boundary, data-driven off c.wordScript instead of a hardcoded Cyrillic predicate (строка 79, D39.39).

| 79 | Граница ПЛОТНОСТИ письма для серий: enabled через Go-константу cjkScriptNames ... Первые два компонента строки (isCyrLetter→isTargetWordLetter · magnitude SourceScripts-гейт) исполнены пакетом-чекеров, D39.78 | Предложенное действие: Снести строку 99 целиком и заменить одной: «Остаток строки 79 — граница ПЛОТНОСТИ письма (cjkScriptNames Go-константой); Cyrillic-хардкоды DC-чекеров ПОГАШЕНЫ пакетом-чекеров, D39.78 (checks/repair.go:190, checks/checkers.go:494).» Разрушающее: True ⚠ Опровергатель поправил действие: Действие верное по существу, но предложенная замена воспроизводит ту же болезнь — снова номера строк (repair.go:190, checkers.go:494), которые уедут при следующей же правке пакета; ровно так и протухли 457/474. Пиши через СИМВОЛЫ: «Cyrillic-хардкоды словесной границы чекеров ПОГАШЕНЫ (Checkers.isTargetWordLetter · brokenWordCandidates); открытый остаток строки 79 — только граница ПЛОТНОСТИ письма (cjkScriptNames Go-константой)». Атрибуцию тоже уточни: код погашение приписывает D39.24 §15.3 с поправкой D39.62/П1, а строка 79 — пакету-чекеров D39.78; называй строку 79 и оба номера, а не один D39.78.


N011 · MAJOR · backend/README.md:101 · B-мёртвый-указатель

Срез: Доки зоны бэкенда: backend/README.md + backend/docs/*.md (9 файлов) Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): Хвост LOW/NOTE-находок адверсариала D39.4 — в ledger-очереди (строка 53). Почему протухло: Строка 53 в едином трекере не существует: она закрыта D39.82 (носитель этого сказан в самой таблице — строка 111: «D39.82 закрыл строку 53 через putUniqueDC»), а закрытые строки таблица по своему же правилу не хранит. Хвост D39.4 получил ЯВНУЮ диспозицию «не чинить — легитимно отложено, носитель = D39.4/D39.5 (пересекается с живыми строками 43/78)». README же подаёт это как открытый техдолг в секции, чья шапка гласит «ЕДИНЫЙ трекер — таблица «Бэклог» в docs/PROGRESS.md, номера строк = она» — читатель грепает 53 и не находит ничего. Улика — команда: grep -nE '^\| *\*{0,2}53\*{0,2} *\|' docs/PROGRESS.md; echo "rc=$? (пусто = строки нет)"; grep -n 'закрыл строку 53' docs/PROGRESS.md | cut -c1-160; grep -n 'D39.4 ledger-очередь LOW/NOTE' docs/archive/reports/SMALLPACK_TAILS_2026-08-02.md | cut -c1-300 Улика — вывод: (пустой вывод по '^| 53 |') rc=1 251:| 111 | Дубль ключа пар-файла: фикс накрыл половину. D39.82 закрыл строку 53 через putUniqueDC в parseDCCheckers... 90: - D39.4 ledger-очередь LOW/NOTE: припаркованная 14 LOW + 14 NOTE хвост пака-1.5 адверсариала (Retries-снапшот · pair-fail-loud · trustGateEvent · golden-глосс-ячейка · пунктуационные огрызки); не чинить — легитимно отложено, носитель = D39.4/D39.5 (пересекается с живыми строками 43/78) Положительный контроль: Инструмент в этой области находит: тем же паттерном по таблице извлекается 189 существующих ID — grep -oE '^\| *\*{0,2}[0-9]+[а-я]?\*{0,2} *\|' docs/PROGRESS.md | wc -l → 189, среди них 49, 49а, 50, 79, 146, 173, которые README цитирует рядом и которые ЖИВЫ. Предложенное действие: Заменить на: «Хвост LOW/NOTE-находок адверсариала D39.4 — диспозиция «не чинить» (D39.82, строка 53 закрыта); живые пересечения — строки 43/78.» Разрушающее: True ⚠ Опровергатель поправил действие: Действие верное, но одну деталь предложенной редакции надо снять: «строка 53 закрыта» D39.82 закрыл СВОЙ предмет (дубль ключа пар-файла через putUniqueDC), а хвост LOW/NOTE адверсариала — это ДРУГАЯ сущность, чья диспозиция «не чинить» стоит в docs/archive/reports/SMALLPACK_TAILS_2026-08-02.md:90 с носителем D39.4/D39.5. Склеивать их одной фразой значит завести новый неверный факт вместо старого. Пиши так: «Хвост LOW/NOTE-находок адверсариала D39.4 (14 LOW + 14 NOTE) — диспозиция «не чинить», носитель D39.4/D39.5; живые пересечения — строки 43/78 (прежний указатель на строку 53 мёртв: она закрыта D39.82)». И раз ссылка уходит в docs/archive/reports/, назови файл явно — иначе следующая сессия снова не найдёт, откуда взялась диспозиция.


N012 · MAJOR · backend/docs/D15.2-content-addressed-resume-spec.md:529 · A-дрейф

Срез: Доки зоны бэкенда: backend/README.md + backend/docs/*.md (9 файлов) Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): Схема: миграция v8 (текущий максимум — v7 после cheap-gate миграции этой сессии: migrate.go v7 = ALTER TABLE retrieval_state ADD COLUMN n_style_flags/style_detail) Почему протухло: Голова схемы сегодня v16, а не v7, и номер v8 давно занят чужой миграцией. Спека при этом ЖИВА: строка бэклога 49 («Этапы Б+В спеки D15.2») открыта, и README:96 отсылает к спеке как к дизайну-оф-рекорд. Сессия, реализующая §11/§12 по букве, заведёт коллизию номера. Рядом — мёртвый line-якорь render.go:212: литерал tm-request-v2, о котором спека говорит, лежит на render.go:306, а на 212 — тело подстановщика плейсхолдеров. Улика — команда: cd backend && grep -n '^\t// v1[0-6]' internal/store/migrate.go | tail -3; grep -n 'func SchemaHead' internal/store/migrate.go; grep -rn 'tm-request-v' --include=*.go internal/ | grep -v _test; sed -n '212p' internal/pipeline/render.go Улика — вывод: // v14 (pack-19): the per-chunk observability of the two new $0 flaggers, riding the same // v15 (row 103): the run-event OUTBOX. A line of events.jsonl is a projection of a row committed // v16 — the bank-stop memory (the D39.144 flag model): every normalized surface a --verify-bank stop 485:func SchemaHead() int { return len(migrations) } internal/pipeline/render.go:306: w("tm-request-v2", req.BookID, strconv.Itoa(req.Chapter), ...

(render.go:212) return "", fmt.Errorf("pipeline: unknown placeholder {{%s}} in template", name) Другие носители того же факта: backend/docs/D15.2-content-addressed-resume-spec.md:560 — «Миграция v8 (§11): колонки guard_hash, verdict_snapshot» Предложенное действие: Дописать в ⚠-шапку спеки одну строку-эррату (тело §11/§12 по норме не переписывать): «⚠ Эррата: с момента письма голова схемы уехала v7→v16, номер v8 занят; при реализации взять СЛЕДУЮЩИЙ свободный (store.SchemaHead()+1). Якорь render.go:212 съехал — литерал tm-request-v2 на render.go:306Разрушающее: False


N013 · MAJOR · docs/PROGRESS.md:3 · A-дрейф

Срез: ГОЛОВА журнала: CURRENT-STATE и очередь — docs/PROGRESS.md, строки 118 (шапка ⟶ ТЕКУЩЕЕ СОСТОЯНИЕ, ~34 КБ) Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): зеркало отстало на ШЕСТЬ миноров (frontend/docs/api-contract/openapi.yaml0.2.3 против канона 0.8.0, и первый из них — ЛОМАЮЩИЙ 0.3.0), 16 операций против 20 Почему протухло: Канон уже НЕ 0.8.0: в docs/architecture/14-api-contract/openapi.yaml стоит 0.9.0 — минор уехал с лендингом P12 31.08, о котором сообщает ЭТА ЖЕ строка шапки. Значит и отставание зеркала теперь СЕМЬ миноров, а не шесть. Число операций (16 против 20) проверено и верно. Улика — команда: grep -rn '^ version:' docs/architecture/14-api-contract/*.yaml; grep -n '^ version:' frontend/docs/api-contract/openapi.yaml; echo -n 'canon ops: '; grep -c 'operationId:' docs/architecture/14-api-contract/openapi.yaml; echo -n 'mirror ops: '; grep -c 'operationId:' frontend/docs/api-contract/openapi.yaml Улика — вывод: docs/architecture/14-api-contract/openapi.yaml:5: version: 0.9.0 5: version: 0.2.3 canon ops: 20 mirror ops: 16

(перепроверено на текущем HEAD 162ab9b: canon version: 0.9.0 / mirror version: 0.2.3)

docs/README.md:26 знает верное: «пак P12 ... ПРИНЯТ И ЗАЛЕНДЖЕН 31.08 (D39.180) вместе с контрактным минором 0.9.0» Другие носители того же факта: docs/PROGRESS.md:10 — «зеркало на 0.2.3, канон на 0.8.0 и уйдёт на 0.9.0 с лендингом P12» (лендинг уже был 31.08), frontend/docs/frontend-PROGRESS.md:9 — «Состояние зеркала контракта — 0.2.3 против канона 0.8.0 (и 0.9.0 придёт с лендингом P12)», docs/README.md:29 (строка «Контракт» таблицы активных промтов) — «минор 0.8.0 ПРИНЯТ и заленджен 29.08», про 0.9.0 в этой строке нет ни слова, хотя соседняя строка «Платформа» (README:26) его называет Предложенное действие: В обеих строках шапки заменить «канона 0.8.0» → «канона 0.9.0» и «ШЕСТЬ миноров» → «СЕМЬ миноров»; в строке 10 снять будущее время «уйдёт на 0.9.0 с лендингом P12» на «ушёл на 0.9.0 лендингом P12 31.08, D39.180». Тем же заходом поправить frontend/docs/frontend-PROGRESS.md:9 и строку «Контракт» в docs/README.md:29 — иначе разморозка фронта будет спланирована на неверное число перегенераций. Разрушающее: True ⚠ Опровергатель поправил действие: Действие верно, и лучше исполнить именно вторую, критериальную его половину: заменить ручной счёт на «ни один пак платформы после P7 этот долг не взял (проверка: grep -rn 'createExport\|getExport' platform/internal/ --include=*.go — пусто)», чтобы число не гнило при каждом следующем лендинге. Если счёт всё-таки оставлять — «шесть паков (P8-FIX · P8-REVIEW · P9 · P10 · P11 · P12), плюс непронумерованный sqlc».


N014 · MAJOR · docs/PROGRESS.md:3 · A-дрейф

Срез: ГОЛОВА журнала: CURRENT-STATE и очередь — docs/PROGRESS.md, строки 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-предпочтение). Разрушающее: True ⚠ Опровергатель поправил действие: Действие верно, и лучше исполнить именно вторую, критериальную его половину: заменить ручной счёт на «ни один пак платформы после P7 этот долг не взял (проверка: grep -rn 'createExport\|getExport' platform/internal/ --include=*.go — пусто)», чтобы число не гнило при каждом следующем лендинге. Если счёт всё-таки оставлять — «шесть паков (P8-FIX · P8-REVIEW · P9 · P10 · P11 · P12), плюс непронумерованный sqlc».


N015 · MAJOR · docs/PROGRESS.md:3 · E-второй-носитель

Срез: ГОЛОВА журнала: CURRENT-STATE и очередь — docs/PROGRESS.md, строки 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 ⚠ Опровергатель поправил действие: Действие верно, и лучше исполнить именно вторую, критериальную его половину: заменить ручной счёт на «ни один пак платформы после P7 этот долг не взял (проверка: grep -rn 'createExport\|getExport' platform/internal/ --include=*.go — пусто)», чтобы число не гнило при каждом следующем лендинге. Если счёт всё-таки оставлять — «шесть паков (P8-FIX · P8-REVIEW · P9 · P10 · P11 · P12), плюс непронумерованный sqlc».


N016 · MAJOR · docs/PROGRESS.md:80 · C-исполненное-как-открытое

Срез: ЕДИНЫЙ БЭКЛОГ docs/PROGRESS.md — таблица (строки 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). Живым остатком оставить только то, что действительно не измерено, — цена ДОВЕДЁННОЙ до конца книги. Разрушающее: True ⚠ Опровергатель поправил действие: Действие верно и строку НЕ закрывать — только переписать. Живым остатком оставить ровно то, что находка и называет: цена ДОВЕДЁННОЙ до конца книги (прогон отдал 3 юнита из 14). При переписывании привести в согласие и колонку гейта: сегодня она говорит «первый прогон добора идеала», а CURRENT-STATE числит строку 16 среди снятых холодным прогоном — оставить одну версию, иначе класс E воспроизведётся внутри одного файла.


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). Разрушающее: True ⚠ Опровергатель поправил действие: Действие верно, но одна деталь его формулировки опасна: «назначить настоящее предусловие … отдельным носителем» надо делать НЕ молча — публикация размеров юнитов в знаках это новое поле контракта манифеста (manifest_version), то есть решение уровня ноты, а не строки бэклога. Плюс уточнение по косвенной мере: 15571863 — это входные токены на ЧАНК, а юнит по этому же прогону несёт от 1 до 2 чанков (медиана 1), поэтому пере-считывать в цену юнита надо через chunk_count, а не приравнивать юнит чанку.


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»), и тем же касанием пройти прочих живых носителей, где файл всё ещё числится существующим каналом. Разрушающее: True ⚠ Опровергатель поправил действие: Действие верно; список носителей уточняю по исполнению. docs/architecture/18-bank-ontology.md:45 ПРАВИТЬ НЕ НАДО — там файл назван в ПРОШЕДШЕМ времени как урок («Машинная таблица стопа .bank-stop.json была…»), это легитимная история, а не дрейф; так же и backend/docs/SEAM_FIX3_PLAN.md:14 / SEAM_PACK_FINDINGS.md:1415-1424, которые описывают САМ снос. Протухли ровно три живых носителя, где файл числится СУЩЕСТВУЮЩИМ каналом: platform/docs/STACK_DECISIONS.md:410 (строка таблицы каналов «⚠ канал существует и не потребляется» — прочитал, там же ложное «атомарно»), backend/docs/pipeline.puml:72 и frontend/docs/frontend-PROGRESS.md:499. Два из них — чужие зоны: правка пингом, не касанием.


N019 · MAJOR · docs/PROGRESS.md:182 · D-противоречие-ратифицированному

Срез: ЕДИНЫЙ БЭКЛОГ docs/PROGRESS.md — таблица (строки 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). Разрушающее: True ⚠ Опровергатель поправил действие: Действие верно; добавить два уточнения, чтобы новая цифра не протухла так же. Первое: $0.436110 — цена НЕПОЛНОГО захода (доставлено 3 юнита из 14), поэтому в строку надо писать не её одну, а связку «куплено $0.436110 при собственной проекции движка projected_book_usd = 1.1377277707 и полном оттоке кассы ≈$1.44 по §4 отчёта» — иначе платформа снова возьмёт в смету число меньше правды. Второе: указатель на ключи ставить греп-указателем (TM_PLATFORM_ENGINE_KEYS_PATH в platform/internal/config/config.go), без номера строки — файл в живой зоне платформы.


N020 · MAJOR · docs/PROGRESS.md:196 · C-исполненное-как-открытое

Срез: ЕДИНЫЙ БЭКЛОГ docs/PROGRESS.md — таблица (строки 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) — при закрытии не «чинить». Разрушающее: True ⚠ Опровергатель поправил действие: НЕ закрывать и НЕ убирать строку 232. СУЗИТЬ её: снять первую половину (ложное «NEVER delivered» после добавления стадии) как исполненную A11 — store.AnnouncedOnceKeys() + предикат delivered[key] && unitShipped(rows); оставить живой ВТОРУЮ половину — юнит, прерванный между волнами, повторно тратит слот гранта — со ссылкой на backend/docs/MONEY_HONESTY_REPORT.md §4.5, где эта граница названа автором лекарства явно. Якорь backend/internal/store/outbox.go:31 (const onceKeyLookup) не трогать.


N021 · MAJOR · docs/PROGRESS.md:202 · C-исполненное-как-открытое

Срез: ЕДИНЫЙ БЭКЛОГ docs/PROGRESS.md — таблица (строки 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). Разрушающее: True ⚠ Опровергатель поправил действие: Действие в целом верно, но его ФОРМУЛИРОВКА протухла на сутки: закрывать «при лендинге/приёмке P12» нечего — P12 ПРИНЯТ И ЗАЛЕНДЖЕН 31.08 (0a680a3, ратификация D39.180), так что закрытие делается СЕЙЧАС нотой со ссылкой на D39.180, а не в будущем лендинге. Тем же касанием обязательно чинить второй носитель, который находка назвала, но не пометила протухшим: в platform/docs/platform-PROGRESS.md:2009 ряд Д3 регистра держит якорь reconcile.go:1122-1126 на снятый гард — я прочёл эти строки, там ReleaseUnspawned/MarkSettled (файл 1621 строка), то есть указатель МЁРТВ и по норме D39.179 п.4 в живом файле зоны обязан быть греп-указателем без номера. Оговорку находки про пробу принимаю и подтверждаю: obstacle пака говорит «Живой пробой на ОБЛАЧНОМ провайдере не удался … Цепь пробита на local-заглушке».


N022 · MAJOR · docs/README.md:13 · A-дрейф

Срез: Корневые онбординг-доки: CLAUDE.md · docs/README.md · docs/STACK.md Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): зонная копия frontend/docs/api-contract/ — байт-зеркало, сверять при лендинге Почему протухло: Канон контракта уехал до 0.9.0 (заленджен 31.08, D39.180), а зонная копия стоит на 0.2.3 от 15.08 — 2999 строк против 1444. «Байт-зеркало» сегодня ЛОЖНО: копии разошлись шестью минорами (0.3.0…0.9.0). Верно: копия ОТСТАЛА и зеркалом не является; дисциплина «сверять при лендинге» не исполнялась ни разу с заморозки фронта. Улика — команда: cmp docs/architecture/14-api-contract/openapi.yaml frontend/docs/api-contract/openapi.yaml; grep -n 'version:' <оба> | head -1; wc -l <оба>; md5sum <оба> Улика — вывод: docs/architecture/14-api-contract/openapi.yaml frontend/docs/api-contract/openapi.yaml differ: byte 61, line 5 cmp exit=1 канон 5: version: 0.9.0 зеркало 5: version: 0.2.3 2999 docs/architecture/14-api-contract/openapi.yaml 1444 frontend/docs/api-contract/openapi.yaml 34bb4b6d0496f4377633597cd12bc73a docs/architecture/14-api-contract/openapi.yaml 4e4ab5e6d313bd56d395032a718ab18a frontend/docs/api-contract/openapi.yaml Другие носители того же факта: docs/architecture/14-api-contract/openapi.yaml:9, docs/architecture/14-api-contract/README.md:73, docs/glossary.md:31, docs/ORCHESTRATOR_SESSION_PROMPT.md:173, docs/research/28-contract-review.md:1589, frontend/README.md:66, frontend/docs/API_CONTRACT_DRAFT.md:16, frontend/docs/frontend-PROGRESS.md:48 Предложенное действие: Заменить «— байт-зеркало, сверять при лендинге» на факт: «зонная копия frontend/docs/api-contract/ ОТСТАЛА на 0.2.3 (канон 0.9.0) — зона заморожена, синхронизация при разморозке; читать ТОЛЬКО канон». Завести строку единого бэклога «зонное зеркало контракта отстало на шесть миноров». Правку самого зеркала не делать — это чужая (замороженная) зона. Разрушающее: False


N023 · MAJOR · docs/architecture/03-implementation-notes.md:64 · A-дрейф

Срез: Заметки реализации и Go-нормы — docs/architecture/03-implementation-notes.md · docs/architecture/12-go-style-notes.md Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): request_hash = sha256(book_id | chapter | chunk_index | stage | role | model+sampling | snapshot_id | sha256(rendered_messages)). Почему протухло: Шапка строки 3 объявляет §3-контракты «живым референсом бэкенда», а формула §3.1 не совпадает с кодом ни одним полем-разделителем: реальный RequestHash фолдит версионный тег tm-request-v2, ДОБАВЛЯЕТ attempt, разворачивает «model+sampling» в пять отдельных полей (model · temperature · reasoning · json_only · max_tokens) и фолдит сообщения НАПРЯМУЮ с length-prefix и явным счётчиком, а НЕ как вложенный sha256(rendered_messages). Сессия, реализовавшая ключ по букве §3.1, получила бы чужие чекпоинты (attempt отсутствует ⇒ ре-генерация садится на только что провалившийся чекпоинт — ровно тот дефект, который код в комментарии называет). Улика — команда: sed -n '292,316p' /home/ubuntu/projects/textmachine-main/backend/internal/pipeline/render.go Улика — вывод: func RequestHash(req Request) string { h := sha256.New() // Each field is length-prefixed (8 bytes LE) instead of a NUL separator ... Version v2 pins the hash-format change. var lb [8]byte w := func(parts ...string) { ... } w("tm-request-v2", req.BookID, strconv.Itoa(req.Chapter), strconv.Itoa(req.ChunkIdx), strconv.Itoa(req.Attempt), req.Stage, req.Role, req.Model, strconv.FormatFloat(req.Temperature, 'f', -1, 64), req.Reasoning, strconv.FormatBool(req.JSONOnly), strconv.Itoa(req.MaxTokens), req.SnapshotID) // Explicit message count: the list length is also part of the key. binary.LittleEndian.PutUint64(lb[:], uint64(len(req.Messages))) h.Write(lb[:]) for _, m := range req.Me Предложенное действие: Сноской по образцу уже стоящей в §3.4 «ПОПРАВКА»: «⚠ Формула устарела — действующий ключ см. backend/internal/pipeline/render.go:292 (RequestHash): версионный тег tm-request-v2, поля length-prefixed, attempt В КЛЮЧЕ, sampling развёрнут (temperature·reasoning·json_only·max_tokens), сообщения фолдятся напрямую со счётчиком, а не вложенным sha256. Изменение зафиксировано волнами §6 (attempt в ключе и схеме, length-prefix + счётчик сообщений), но §3.1 не был обновлён». Разрушающее: False


N024 · MAJOR · docs/architecture/03-implementation-notes.md:262 · D-противоречие-ратифицированному

Срез: Заметки реализации и Go-нормы — docs/architecture/03-implementation-notes.md · docs/architecture/12-go-style-notes.md Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): D1 — редактор C1 видит исходник ({{text}} в editor.md); для Фазы 1+ сделать редактора монолингвальным (только {{draft}}), верность → coverage-гейт + билингвальный судья ... но {{text}} убрать, когда они приедут. Почему протухло: D30 п.1 ратифицировал ПРЯМО ОБРАТНОЕ и с явным supersede: «Флип D1: редактор — БИЛИНГВАЛЬНЫЙ (черновик + исходник), новый дефолт». Реестр решений помечает саму формулировку доку («{{text}} убрать из editor.md») как SUPERSEDED → D17 → D30.1. Док не знает об этом и продолжает предписывать имплементеру Фазы 1+ снять {{text}} — исполнение этой строки развернуло бы ратифицированный контракт и двинуло prompt_version → resnapshot. Боевой конфиг сегодня явно ссылается на D30.1 как на основание билингва. Улика — команда: grep -n '| D1 |' docs/architecture/05-decisions-index.md; sed -n '115,115p' docs/architecture/05-decisions-log.md | cut -c1-260; grep -n '{{text}}' backend/prompts/zh-ru/editor.md; grep -n 'D30.1' backend/configs/pipeline-c1.yaml Улика — вывод: docs/architecture/05-decisions-index.md:17:| D1 | | Редактор C1 — монолингвальный: видит только черновик + approved-глоссарий, без исходника; {{text}} убрать из editor.md, поднять edit prompt_version | SUPERSEDED → D17 → D30.1 (редактор БИЛИНГВ) | слайс D1-D38 | модели промпт | 05-decisions-log.md:115: 1. Флип D1: редактор — БИЛИНГВАЛЬНЫЙ (черновик + исходник), новый дефолт. Ратифицирован с явным supersede D17.в backend/prompts/zh-ru/editor.md:37:{{text}} backend/configs/pipeline-c1.yaml:96: # против D30.1 — редактор БИЛИНГВ и видит плотный CJK-исходник (prompts/zh-ru/editor.md, {{text}}). Предложенное действие: Сноской сразу за пунктом D1: «⚠ ОПРОВЕРГНУТО D30 п.1 (12.07): редактор БИЛИНГВАЛЬНЫЙ — новый дефолт, с явным supersede D17.в; реестр помечает D1 как SUPERSEDED → D17 → D30.1. {{text}} из prompts/zh-ru/editor.md НЕ снимать. Моно-вариант живёт как editor-mono.md — дормант-референс (D33.4a), не план». Текст пункта не сносить — он объясняет, откуда взялась нота D1. Разрушающее: False


N025 · MAJOR · docs/architecture/06-memory-risk-registry.md:81 · B-мёртвый-указатель

Срез: Ранние арх-доки (кандидаты в архив): docs/architecture/01-decisions.md · 02-mvp-plan.md · 04-unhappy-paths.md · 06-memory-risk-registry.md Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): snapshotID (snapshot.go:82 (до пакета №4 — runner.go:145-163)) хэширует brief/chunker/план-стадий/сэмплинг ... RequestHash (render.go:158) ... render.go:23-29 ... инъектит память в msgs (render.go:133-136) ... resnapshot-гейт (bookrun.go:108/stagerun.go:77) Почему протухло: Колонка «Где в коде/схеме» реестра указывает шесть file:line-якорей, и пять из шести уехали: реальные адреса — MessagesWithInjection render.go:246 (а не render.go:133-136, где сегодня разбор разделителя промпта), RequestHash render.go:292 (а не :158 = fewShotEnabled), snapshotID() snapshot.go:209 (а не :82), resnapshot-гейт целиком в stagerun.go:52-65 (в bookrun.go его НЕТ вовсе, :108 — пустая строка перед CompletedWithFlags), memoryVersion() snapshot.go:190 (а не :175). Плюс имя render.Messages больше не место инъекции: Messages() на render.go:229 делегирует в MessagesWithInjection с ПУСТОЙ инъекцией. Улика — команда: cd /home/ubuntu/projects/textmachine-main/backend && for a in render.go:133 render.go:158 snapshot.go:82 bookrun.go:108 stagerun.go:77 snapshot.go:175; do f=${a%%:*}; n=${a##*:}; printf '%-16s -> ' "$a"; sed -n "${n}p" internal/pipeline/$f | cut -c1-95; done; grep -n '^func MessagesWithInjection\|^func RequestHash\|^func (r \*Runner) snapshotID\|^func (r \*Runner) memoryVersion' internal/pipeline/render.go internal/pipeline/snapshot.go; grep -n 'was started under snapshot' internal/pipeline/stagerun.go Улика — вывод: render.go:133 -> parts := strings.SplitN(canon, userSeparator, 2) render.go:158 -> // fewShotEnabled resolves a stage's few_shot toggle: absent (nil) defaults to ON, so snapshot.go:82 -> } bookrun.go:108 -> stagerun.go:77 -> contentHash := msgsContentHash(msgs) snapshot.go:175 -> s.ModelWire = wire.capability

internal/pipeline/snapshot.go:190:func (r *Runner) memoryVersion() string { internal/pipeline/snapshot.go:209:func (r *Runner) snapshotID() (id, payload string, err error) { internal/pipeline/render.go:246:func MessagesWithInjection(tpl *PromptTemplate, v RenderVars, injection string) ([]llm.Message, error) { internal/pipeline/render.go:292:func RequestHash(req Req Положительный контроль: Тот же sed по якорю, который НЕ уехал, попадает точно: sed -n '22,28p' internal/pipeline/render.go → «// render.go enforces the determinism invariant (03-implementation-notes §3.1): the rendered prompt is a PURE function of (snapshot, committed prior outputs, chunk)» — т.е. якорь «render.go:23-29» из того же дока резолвится, инструмент в этой области работает. Другие носители того же факта: docs/architecture/06-memory-risk-registry.md:10 (шапка 25.07 несёт тот же уехавший «snapshot.go:175»), docs/architecture/06-memory-risk-registry.md:32 (A1: «render.Messages … render.go:133-136»), docs/architecture/06-memory-risk-registry.md:37 (A6: «раскладка render.Messages»), docs/architecture/06-memory-risk-registry.md:100 («impl-notes §3.6 (строки 123-124)» — цитируемая фраза про FTS5 живёт на 03-implementation-notes.md:127) Предложенное действие: Пере-нацелить якоря одной правкой: render.go:133-136 → pipeline.MessagesWithInjection (render.go:246); render.go:158 → render.go:292; snapshot.go:82 → snapshot.go:209; «bookrun.go:108/stagerun.go:77» → stagerun.go:52-65; snapshot.go:175 → snapshot.go:190; «impl-notes §3.6 (строки 123-124)» → 03-implementation-notes.md:127. Имя render.Messages в A1/A6 заменить на render.MessagesWithInjection (Messages() передаёт пустую инъекцию). Устойчивее — заменить номера строк на ИМЕНА функций, они пережили уже два рефакторинга. Разрушающее: False


N026 · MAJOR · docs/architecture/09-target-architecture.md:12 · A-дрейф

Срез: Целевая архитектура — docs/architecture/09-target-architecture.md Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока):Канон контракта — 0.8.0, не 0.4.0 и не 0.7.0: пять миноров за трое суток (0.5.0 — D39.161, 0.6.0 — D39.162/163, 0.7.0 — D39.166, 0.8.0 — D39.169, кадр session_ended). Носители — 14-api-contract/openapi.yaml и platform/internal/httpapi/capabilities.go, они сходятся гейтом. Почему протухло: Канон стоит на 0.9.0, а не на 0.8.0: минор 0.9.0 залендён 31.08 паком платформы P12 (D39.180), и ОБА названных в этой же строке носителя показывают 0.9.0. Строка сама называет носителей и сама им противоречит — то есть читатель, послушавшийся её вместо грепа, получит ложное число. Улика — команда: grep -n '^ version:' docs/architecture/14-api-contract/openapi.yaml; grep -n 'ContractVersion = ' platform/internal/httpapi/capabilities.go; grep -n '| D39.180 |' docs/architecture/05-decisions-index.md | cut -c1-150 Улика — вывод: 5: version: 0.9.0 13:const ContractVersion = "0.9.0" 244:| D39.180 | 31.08 | Пак платформы P12 «долги под ногами» ПРИНЯТ И ЗАЛЕНДЖЕН вместе с кон Другие носители того же факта: docs/README.md:29, docs/product-requirements.md:56, docs/PROGRESS.md:10 Предложенное действие: Заменить «Канон контракта — 0.8.0» на «Канон контракта — 0.9.0 (D39.180, лендинг пака P12 31.08)» и дописать 0.9.0 в перечень миноров. Те же три носителя (README §активные промты, product-requirements ПТ-20, CURRENT-STATE в PROGRESS) правятся тем же заходом — иначе класс E воспроизведётся: product-requirements сам предупреждает «версия здесь живёт хуже всего». Разрушающее: True ⚠ Опровергатель поправил действие: Действие верно; предпочтительна названная автором «лучшая» ветка — внести объёмный потолок, гейт схемы и закон раскрытия В САМУ таблицу слоёв и снять оговорку целиком, иначе числительное будет протухать при каждом следующем лендинге. Если правится только текст — не заменять «двух» на «трёх» (то же протухнет), а писать «ряда» + ссылку на реестр нот.


N027 · MAJOR · docs/architecture/10-prompt-architecture.md:51 · C-исполненное-как-открытое

Срез: Архитектура промптов — docs/architecture/10-prompt-architecture.md Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): 4. Билингв-редактору src→dst вместо голых dst-форм? — открытый дизайн-вопрос D30.1, после exp15. Почему протухло: Вопрос закрыт и ПОСТРОЕН: WS2 пака-11 отрендерил редакторский констрейнт-блок именно как src→dst-мэппинг, и D39.13 ратифицировал это дословно как «закрытие D30.1» (19.07 — через три дня после рождения дока). В коде это несущая константа снапшота RenderFormatVersion = renderfmt-v3-draft-gender+editor-src2dst+dc3-gender, а доккоммент RenderEditorConstraintBlock прямо говорит «WS2 §2в resolves the D30.1-open question». Улика — команда: grep -n 'RenderFormatVersion = ' backend/internal/membank/memory.go; sed -n '745,747p' backend/internal/membank/memory.go; grep -n '^| D39.13 ' docs/architecture/05-decisions-index.md; sed -n '212p' docs/archive/architecture/05-decisions-D39-arch-reset.md | grep -o 'src→dst констрейнт-блок редактора.\{0,30\}' Улика — вывод: memory.go:738: const RenderFormatVersion = "renderfmt-v3-draft-gender+editor-src2dst+dc3-gender" memory.go:745-747: // RenderEditorConstraintBlock serializes the selected records' CONFIRMED renderings into the // editor's injection as a src→dst MAPPING (WS2 §2в resolves the D30.1-open question): "源термин → // «dst»", like the translator block, NOT bare canonical Russian. 05-decisions-index.md:75: | D39.13 | 19.07 | Приёмка пака-11 Block A: WS2 целиком (бюджет в выходных токенах, edit-единицы, src→dst-блок редактора = закрытие D30.1) + WS1a/b... 05-decisions-D39-arch-reset.md:212: src→dst констрейнт-блок редактора [закрытие D30.1] Другие носители того же факта: docs/architecture/09-target-architecture.md:144-145 — «Убрать устаревшую D1-моно-рационализацию из renderEditorConstraintBlock (редактор теперь БИЛИНГВ, D30.1); открытый вопрос — давать ли билингв-редактору src→dst-мэппинг, а не голые dst-формы» (живой арх-док, тот же протухший открытый вопрос; заодно имя функции сегодня экспортное — RenderEditorConstraintBlock), docs/architecture/09-target-architecture.md:77 — «инъекция per-role (переводчику src→dst, редактору CONFIRMED-dst-констрейнты)»: половина верна (CONFIRMED-only), половина протухла — редактору сегодня едут src→dst-пары, а не голые dst-формы, docs/research/20-bank-mining.md:780 — «Открытый вопрос D30.1-редактора (src→dst вместо голых dst-форм, memory.go:505-511)»: тело ресёрча, по D23.3 не переписывается, но указатель memory.go:505-511 туда больше не ведёт Предложенное действие: Заменить п.4 на закрытый: «Билингв-редактору src→dst вместо голых dst-форм — ЗАКРЫТО (D39.13, пак-11 WS2): констрейнт-блок редактора рендерится как src→dst-мэппинг, CONFIRMED-only; форма фолдится в снапшот через RenderFormatVersion (memory.go:738). Довод — дизамбигуация омонимичного dst.» Тем же касанием оркестратору: 09-target-architecture.md:144-145 и :77 несут ту же протухшую формулировку. Разрушающее: True ⚠ Опровергатель поправил действие: Действие верное по существу, две поправки. (1) Не вешать в правку сырой номер строки memory.go:738 — именно такие якоря и гниют дрейфом (в этом же доке §2 уже несёт ⚠ «пути таблицы устарели механически»); назвать символ: константа RenderFormatVersion в internal/membank/memory.go, проводка — internal/pipeline/chunkrun.go:102. (2) В 09-target-architecture.md:144-145 протух НЕ только хвост про открытый вопрос: первая половина буллета («убрать устаревшую D1-моно-рационализацию») тоже ИСПОЛНЕНА — доккоммент сегодня читается «the editor is BILINGUAL (editor.md feeds it the source)», — а имя функции в буллете строчное (renderEditorConstraintBlock), тогда как в коде символ экспортный RenderEditorConstraintBlock (memory.go:769; строчного варианта в backend/*.go нет вовсе — он остался только в backend/docs/D15.2-спеке и в телах экспериментов). То есть буллет сносится целиком, а не правится хвостом.


N028 · MAJOR · docs/architecture/10-prompt-architecture.md:52 · C-исполненное-как-открытое

Срез: Архитектура промптов — docs/architecture/10-prompt-architecture.md Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): 5. Q4a/Q4c exp15 — где жить структуре (переводчик/редактор/отдельный пасс) — решится эмпирикой. Почему протухло: Q4a ИЗМЕРЕН 18.07 и ратифицирован D39.9 («верность НЕ покупается на translate-стадии»), а топологию ролей решил эксп-21 (D39.117: full-regen, починка = пре-гейт ПЕРЕД редактором). Открытым остался только Q4c. Собственная §1 того же дока (испр. 22.08) это уже знает и говорит ровно противоположное — §4 п.5 не был поправлен тем же касанием, и читатель §4 уходит с выводом, что структура «решится эмпирикой», хотя эмпирика получена и ратифицирована. Улика — команда: grep -n '^| D39.9 \|^| D39.117 ' docs/architecture/05-decisions-index.md; sed -n '391p' docs/experiments/19-editor-contract-q4b.md; sed -n '18p;52p' docs/architecture/10-prompt-architecture.md | cut -c1-160 Улика — вывод: index:71: | D39.9 | 18.07 | Q4a «сильный переводчик»: верность НЕ покупается на translate-стадии (flash уже верен, pro ×3.67 не ратифицирован); смысл-катастрофы рождает glm-РЕДАКТОР → свап-арм приоритетен | закрыт index:181: | D39.117 | 08.08 | Эксп-21 принят: строка 65 закрыта; починка = пре-гейт перед редактором; опровергнут вывод D39.108 | ЖИВОЕ: диспозиции 142/65/106/55 exp19:391: Reflow-проход (Q4c) не гонялся — проза и переверстка НЕ сравнивались doc10:18: ... Q4a ИЗМЕРЕН (D39.9: верность на translate-стадии не покупается) ... Не гонялся только Q4c doc10:52: 5. Q4a/Q4c exp15 — где жить структуре ... решится эмпирикой. Предложенное действие: Заменить п.5 на: «Q4c (reflow отдельным пассом) — единственный не прогнанный арм exp15 (19-editor-contract-q4b.md:391). Q4a измерен и закрыт D39.9; топология ролей решена эксп-21 (D39.117: full-regen, починка — пре-гейтом ПЕРЕД редактором, строка 142).» Разрушающее: True ⚠ Опровергатель поправил действие: Действие верное. Добавить в тот же обход одну проверку, которую автор не сделал: 09-target-architecture.md:138 несёт «⚠ Арм «переводчик отдаёт структуру» никогда не тестировался», что тем же D39.9 выглядит протухшим — но осторожно: D39.9 мерил ВЕРНОСТЬ черновика, а не структуру, так что это ЛИД для отдельной сверки, а не готовая правка. Ссылку на Q4c лучше дать якорем секции (19-editor-contract-q4b.md §2.10 «Границы»), а не только номером строки.


N029 · MAJOR · docs/architecture/13-tech-debt-anchors.md:52 · E-второй-носитель

Срез: Ранние арх-доки (кандидаты в архив): docs/architecture/01-decisions.md · 02-mvp-plan.md · 04-unhappy-paths.md · 06-memory-risk-registry.md Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): F4-бэкап (83): 0 вхождений VACUUM/integrity_check/backup во всём backend/, tmctl без такой подкоманды (main.go:73-83). Почему протухло: Это ВТОРОЙ (и единственный не-баннированный) носитель статуса строки F4 реестра памяти, и он разошёлся с кодом: tmctl backup существует (cmd/tmctl/main.go:243 case "backup", в списке команд main.go:184), store.IntegrityCheck с PRAGMA integrity_check — internal/store/backup.go:18,79, пре-флайт-бэкап зовётся из translate (main.go:221,236). Всё это приехало коммитом 9f60746 (D39.82) ПОСЛЕ того, как строка сводки была написана (036dc78, D39.67). Живой журнал уже знает правду: docs/PROGRESS.md:93 говорит про «F4-хвост» (т.е. механизм есть, остались доработки), а не про отсутствие. Заодно уехал и якорь: main.go:73-83 сегодня — словарь exit-кодов, а не список подкоманд. Улика — команда: cd /home/ubuntu/projects/textmachine-main && grep -rniI 'VACUUM|integrity_check|backup' -E --include='*.go' backend/ | grep -v _test | head -6; grep -n 'case "backup"' backend/cmd/tmctl/main.go; git log --oneline --diff-filter=A -1 -- backend/internal/store/backup.go; git log --oneline -1 -S 'F4-бэкап (83)' -- docs/architecture/13-tech-debt-anchors.md Улика — вывод: backend/cmd/tmctl/main.go:184: "translate", "report", "status", "export", "build", "redrive", "manifest", "backup", "migrate", "seed-lint", backend/cmd/tmctl/main.go:221: if err := preflightBackup(inv.cfgPath, os.Stdout); err != nil { backend/cmd/tmctl/main.go:243: case "backup": backend/internal/store/backup.go:18:// IntegrityCheck runs PRAGMA integrity_check against the SQLite file at dbPath … backend/internal/store/backup.go:79:// integrityCheck runs the pragma over db and demands the single canonical "ok" row … 243: case "backup" 9f60746 Land smallpack tails as D39.82: … tmctl backup with production preflight … 036dc78 Ratify D39.67: … debt anchors moved to living architecture referen Другие носители того же факта: docs/architecture/06-memory-risk-registry.md:88 (строка F4 со статусом «🔶 нет строки бэкапа/интеграла» — покрыта ⚠-баннером 25.07 «статусы /🔶 датированы 04.07», потому НЕ находка), docs/PROGRESS.md:93 (актуальный носитель: «F4-хвост» — механизм есть, остались доработки) Предложенное действие: Переписать пункт сводки: «F4-бэкап: ЗАКРЫТ D39.82 (9f60746) — tmctl backup (main.go:243), store.IntegrityCheck + PRAGMA integrity_check (store/backup.go:18,79), пре-флайт в translate (main.go:221,236); остаток — строка 93 PROGRESS (dry-run обходит пре-флайт)». Якорь main.go:73-83 заменить на main.go:184 (список команд). Разрушающее: True ⚠ Опровергатель поправил действие: Находка верна, но предложенное действие бьёт мимо в трёх местах.

  1. НЕ переписывать сводку заново своим текстом. Строка 16 того же файла уже несёт ратифицированную формулировку закрытия («→ ИСПОЛНЕНО D39.82: tmctl backup + pre-flight (backend/cmd/tmctl/backup.go), строка 83 закрыта; остаток F4-хвоста — строка 93»). Минимальная и достаточная правка — привести сводку в соответствие с ней тем же механизмом, который объявлен баннером дока: вставить в предложение о F4 стрелку «→ ЗАКРЫТО D39.82 (9f60746), см. строку 16» и не трогать саму запись свипа. Сводка — часть протокола верификационного свипа D39.66 (author≠reviewer, evidence-документ); переписывать её вердикт задним числом — то же, что править тело ресёрча по норме D23.3. Помечать закрытие — можно и нужно, стирать улику свипа — нет.

  2. Якорь заменить не на «main.go:184», а на «main.go:183-186 (dispatchCommands)»: :184 — лишь одна строка литерала внутри объявления, и она сама поедет при следующей вставке команды. Ещё устойчивее — якорить по ИМЕНИ символа (dispatchCommands в backend/cmd/tmctl/main.go), без номера строки: именно этот якорь только что и сгнил номером. Отдельно стоит заметить в отчёте, что в репозитории есть анкор-гейт (коммит 37206fd «Refresh two doc anchors … keeping the anchor gate clean»), и он этот случай НЕ поймал — диапазонная форма «main.go:73-83» внутри прозаической строки, видимо, вне его хватки; это отдельный вопрос оркестратору, а не правка дока.

  3. Не расширять правку на строки 16-18. Тело среза (строка 18) содержит те же протухшие утверждения в настоящем времени, но оно стоит НЕПОСРЕДСТВЕННО под стрелкой закрытия — то есть механизм баннера там отработал. Трогать его — оверрич.

  4. Второй носитель в отчёте назвать точно: docs/architecture/06-memory-risk-registry.md:84 (не :88), статус «🔶 нет строки бэкапа», покрыт ⚠-баннером строки 11 — НЕ находка, флагать не надо.


N030 · MAJOR · docs/architecture/13-tech-debt-anchors.md:72 · D-противоречие-ратифицированному

Срез: Реестр загейченных триггеров — docs/architecture/13-tech-debt-anchors.md (весь реестр, §Б-108 первым) Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): сегодня gender: neuter в сиде — ТИХИЙ no-op без линта, т.е. дыра хуже «невыразимо» — при стройке носителя добавить и seed-lint-отбраковку неизвестных значений рода Почему протухло: Обе половины ложны и противоречат ратифицированной D39.69 §3 («строки 27/84 ЗАКРЫТЫ … neuter end-to-end (enum · gender_neuter в injection-данных · SeedLint громко отбраковывает неизвестный род)»). В коде: genderConstraintNote отдаёт tx.GenderNeuter на case "neuter", "n" — значит это НЕ no-op; GenderVocabViolations вызывается из сид-линта (memseed.go:533) — значит линт ЕСТЬ, и его собственный докстринг говорит о neuter в прошедшем времени: «the row-84 class where gender:neuter (now valid) was an unnoticed no-op». Строка 84 в таблице бэклога отсутствует (закрыта), а тело пункта выше (пере-сверка 23.08) уже это признаёт — не обновлён только абзац «Фикс/скоуп», то есть предписание к действию. Улика — команда: sed -n '846,850p' backend/internal/membank/memory.go; sed -n '531,534p;540,552p' backend/internal/membank/memseed.go; grep -nE '^\| 84 \|' docs/PROGRESS.md; grep -nE '^\| (85|86) \|' docs/PROGRESS.md | cut -c1-40 Улика — вывод: case "neuter", "n": return tx.GenderNeuter

if bad := GenderVocabViolations(entries); len(bad) > 0 {

var knownGenders = map[string]bool{ "": true, "male": true, "m": true, "female": true, "f": true, "neuter": true, "n": true, "hidden": true, } // GenderVocabViolations lists seed rows whose gender is outside the known vocabulary — the row-84 class where // gender:neuter (now valid) was an unnoticed no-op.

(grep '^| 84 |' — пусто: строки 84 в бэклоге НЕТ) 78:| 86 | **Мелкие открытые 91:| 85 | Пилот-преп добор Другие носители того же факта: docs/architecture/13-tech-debt-anchors.md:104 (Сводка того же среза — вторая копия того же протухшего утверждения), docs/archive/architecture/05-decisions-D39-59-90.md (архивный носитель, легитимен как история) Предложенное действие: Заменить абзац «Фикс/скоуп» пункта на: «→ ЗАКРЫТО D39.69 §3 (строки 27/84): neuter end-to-end построенgenderConstraintNote case "neuter","n" (memory.go:846-848), GenderNeuter в lang/embedded.go:398/456, seed-lint GenderVocabViolations (memseed.go:533/546), ось = scoped neuter-directive-v1-фолд в memory_version. ЖИВОЙ ОСТАТОК ОДИН — косметика: комментарий схемы store/migrate.go:184 всё ещё перечисляет male|female|hidden|'' без neuter.» Пункт (б) про decl.invariant оставить. Разрушающее: True ⚠ Опровергатель поправил действие: Действие в целом верно и его якоря я проверил (memory.go:848-849 внутри предложенного 846-848 ✓; embedded.go:398 GenderNeuter string и :456 case "gender_neuter": ✓; memseed.go:533 вызов и :546 докстринг ✓; живой остаток migrate.go:184 -- male|female|hidden|'' (C2 …) без neuter ✓). ДВЕ правки к нему. (1) Не вписывать НОВЫЕ голые номера строк: этот файл гниёт именно ими (см. вердикт по строке 134), а в репозитории есть гейт, который сверяет якорь по содержимому ТОЛЬКО в опт-ин токен-форме `путь:848`=`case "neuter", "n":` (docs/scripts/counts.py, ратифицировано D39.126, бэклог-строка 205). Тело этого же пункта на строке 70 УЖЕ написано в токен-форме — новый абзац обязан быть в ней же, иначе закрытие протухнет третьим кругом. (2) Правку строки 72 нельзя делать в отрыве от строки 104: там ВТОРАЯ, противоположная копия того же факта (находка №2), и починка одной половины оставит документ противоречащим самому себе.


N031 · MAJOR · docs/architecture/13-tech-debt-anchors.md:100 · C-исполненное-как-открытое

Срез: Реестр загейченных триггеров — docs/architecture/13-tech-debt-anchors.md (весь реестр, §Б-108 первым) Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): feed_cap НЕ построен: grep feed_cap/FeedCap по backend/ = 0 Почему протухло: feed_cap ЗАКРЫТ ратифицированной нотой D39.69 §1 («value-порядок отреза бюджета (частота-desc — feed_cap ЗАКРЫТ этим, из строки 86 изъят)»), и код это несёт: terminology.Batch упорядочивает батчи по УБЫВАНИЮ частоты именно чтобы бюджетный потолок резал наименее ценное, а не лексикографический хвост; греп, объявленный нулевым, сегодня даёт два хита. Пункт стоит без стрелки закрытия, вопреки собственной норме файла «исполненный пункт помечать ссылкой на D-номер»; та же ложь повторена в «Сводке среза» (строка 104: «feed_cap: не построен (grep=0)»). Трекер уже исправлен — строка 86 бэклога несёт «D39.69(feed_cap закрыт §1)». Улика — команда: grep -rn 'feed_cap' backend/ | head; sed -n '1162,1165p' backend/internal/terminology/terminology.go; cd backend && go test ./internal/terminology/ -run TestBatchValueOrder -count=1 -v Улика — вывод: backend/internal/terminology/series_test.go:199:// (feed_cap) rather than the lexicographic tail. backend/internal/terminology/terminology.go:1164:// (feed_cap, §7).

// The batches are then ordered by descending source frequency, so a budget ceiling drops the least-frequent terms instead of the lexicographic tail // (feed_cap, §7).

=== RUN TestBatchValueOrder --- PASS: TestBatchValueOrder (0.00s) PASS ok textmachine/backend/internal/terminology 0.002s Другие носители того же факта: docs/PROGRESS.md:78 (строка бэклога 86 — уже несёт закрытие «D39.69(feed_cap закрыт §1)»), docs/architecture/05-decisions-log.md:194 (тело D39.69 — носитель закрытия), docs/architecture/05-decisions-log.md:182 (тело D39.45 — историческая констатация ДО закрытия), docs/architecture/13-tech-debt-anchors.md:104 (Сводка среза — вторая копия протухшего утверждения в том же файле) Предложенное действие: Дописать по месту стрелку, как велит норма файла: «→ ЗАКРЫТО D39.69 §1: value-порядок отреза бюджета (частота-desc) построен — terminology.Batch, гвоздь TestBatchValueOrder; из строки 86 изъят.» Строку 102 «Занесено правильно … формулировка точна» заменить на «формулировка БЫЛА точна на 01.08; опровергнута лендингом D39.69». В «Сводке среза» (строка 104) убрать фрагмент «feed_cap: не построен (grep=0), и претензия … подтверждена кодом — батчи в лексикографическом порядке src». Разрушающее: False


N032 · MAJOR · docs/architecture/13-tech-debt-anchors.md:134 · B-мёртвый-указатель

Срез: Реестр загейченных триггеров — docs/architecture/13-tech-debt-anchors.md (весь реестр, §Б-108 первым) Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): membank/mempostcheck.go:12 — пост-чек стоит на посылке «SRC actually fired in THIS chunk»; «сработал» = Aho-Corasick ПОДСТРОКА (memory.go:532). Han-ключи намеренно НЕ проверяются на границы: memory.go:1071-1072 Почему протухло: Единственная объявленная функция файла — быть справочником file:line-якорей («Этот файл — справочные file:line-якоря … Актуализируется лендингами паков»). Механизм не работает: сплошной проверкой 59 якорей (по ДОСЛОВНО процитированному в доке токену) 29 не указывают на то, что цитируют. Дрейф не косметический: status.go:561-582 (RedriveSelector) → 907 (+346); render.go:308 («incl. dropped c-lite members») → 471; cheapgates.go:414-416 (гейт magnitudeStem) → 549; terminologist.go:603 → 380 (223); terminology.go:917-919 (Batch) → 1172; book.go:250 (валидатор ceilings) → 321; archguard.go:79-82 — цель touchesPkg вообще исчезла (переписана в reaches/fromPkg дофиксом D39.97). Особый случай: якорь KANA/HAN уже правился один раз в этом же файле (1048→1071) и уехал СНОВА — фактически 1077, то есть обе редакции мертвы одновременно. Улика — команда: grep -n 'KANA and HAN are deliberately NOT boundary-checked' backend/internal/membank/memory.go; grep -n 'type RedriveSelector struct' backend/internal/pipeline/status.go; grep -n 'incl. dropped c-lite members' backend/cmd/tmctl/render.go; grep -n 'len(c.magnitudeStem) == 0' backend/internal/checks/cheapgates.go; grep -n 'run.texts\[i\]' backend/internal/pipeline/terminologist.go | head -1; grep -c 'touchesPkg' backend/internal/archguard/archguard.go Улика — вывод: memory.go:1077 (док: 1048-1051 и, в исправленной редакции, 1071-1072) status.go:907 (док: 561-582) render.go:471 (док: 308) cheapgates.go:549 (док: 414-416) terminologist.go:380 (док: 603) archguard.go: touchesPkg = 0 хитов (при 17 живых func в файле — цель переименована дофиксом D39.97) Сплошной прогон 59 якорей: == OK=30 DRIFT/GONE=29 == Другие носители того же факта: docs/PROGRESS.md:89 (строка бэклога 81 — несёт ИСПРАВЛЕННЫЙ якорь migrate.go:187/194, тогда как справочник на строке 12 всё ещё пишет :186), docs/PROGRESS.md:251 (строка 111 — свой якорь langpack.go:806-826, тоже мимо: cats[f[0]][f[1]] на 829), docs/PROGRESS.md:255-256 (строки 121/122 явно отсылают за file:line именно в §Б-121/§Б-122 этого файла) Предложенное действие: Не переписывать 29 чисел руками — это третий круг того же гниения (якорь memory.go правился и уехал снова). Завести $0-гвоздь: тест/скрипт, который читает пары «путь:строка + дословная цитата» из этого файла и краснеет, когда цитата не лежит на названной строке (форма уже отработана в репо — TestMemnormXTextVersionPin пинит константу против go.mod). До гвоздя — заменить голые номера строк на ЦИТАТУ+имя символа (membank.InjectivityCollisions, RedriveSelector), которые греп находит без номера, и поставить в шапку файла предупреждение «номера строк — снимок лендинга, сверяйся грепом токена». Разрушающее: True ⚠ Опровергатель поправил действие: Находка верна, ПРЕДПИСАНИЕ НЕВЕРНО в главном: «завести $0-гвоздь» — предложение построить то, что УЖЕ ПОСТРОЕНО И РАТИФИЦИРОВАНО. Гейт якорей живёт в docs/scripts/counts.py (D39.126; исполнен 21.08, носитель — бэклог-строка 205 в docs/PROGRESS.md:185), сверяет якорь ПО СОДЕРЖИМОМУ через опт-ин токен ожидания `путь:123`=`подстрока` (регексп ANCHOR, counts.py:344-347) и вплетён в pre-commit хук (D39.148, warn-only). Я его исполнил ($0): python3 docs/scripts/counts.py --lint → «0 проблемных якорей в 100 живых доках · сверено ПО СОДЕРЖИМОМУ (токен): 137 · без токена — несверяемы: 708». Вот и объяснение зелени при 14 уехавших якорях: в самом 13-tech-debt-anchors.md токен-форму несут ВСЕГО ШЕСТЬ якорей (grep -c '=' = 4 строки), остальные ~130 гейт не видит по построению. Поэтому правильное действие: не строить второй гвоздь, а МИГРИРОВАТЬ якоря этого файла в токен-форму — ровно тот «органический остаток», который строка 205 уже объявила («токен требуется на ЗАТРОНУТЫХ строках, поэтому счёт растёт сам с каждым касанием»); этот док — концентрат якорей, и на нём органика не сработала, значит он законный кандидат на разовую пакетную миграцию, а не на новый механизм. Совет «поставить в шапку предупреждение о снапшоте» верен и дёшев (форма уже отработана на строке 162 и 94 этого же файла). Две поправки к фактуре самой находки: (1) цель якоря terminologist.go:603 — это run.texts[i] = att.text на строке 817, а не 380 (на 380 другое чтение run.texts[i]); (2) при планировании учесть бэклог-строку 244 (docs/PROGRESS.md:206) — известный дефект гейта: хук судит ЗАКОММИЧЕННЫЙ док против РАБОЧЕГО кода и врёт в обе стороны.


N033 · MAJOR · docs/architecture/13-tech-debt-anchors.md:180 · C-исполненное-как-открытое

Срез: Реестр загейченных триггеров — docs/architecture/13-tech-debt-anchors.md (весь реестр, §Б-108 первым) Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): grep sourceScripts/SourceScripts по cheapgates.go — 0 вхождений: механизм SetSourceScripts существует (checkers.go:104-125) и используется другими путями, но lintNumberMagnitude его НЕ читает Почему протухло: Гейт по письму источника ПОСТРОЕН и стоит внутри самой lintNumberMagnitude; код называет заказ по имени в комментарии — «SOURCE-script gate (строка 79, D39.39)». Греп, объявленный нулевым, даёт хит cheapgates.go:555. Есть гвоздь TestNumberMagnitudeSourceScriptGate, который проверяет ровно три случая (zh → файрит, en → инертен, ja → файрит). Вердикт строки 182 «Не требуется — дописка 79 точна» сегодня ложен, и это самый дорогой класс: сессия, читающая справочник, возьмётся строить уже построенное. Трекер снова впереди — строка 79 бэклога уже пишет, что этот компонент исполнен. Улика — команда: grep -ni 'sourcescripts' backend/internal/checks/cheapgates.go; sed -n '552,557p' backend/internal/checks/cheapgates.go; cd backend && go test ./internal/checks/ -run TestNumberMagnitudeSourceScriptGate -count=1 -v Улика — вывод: 555: if len(c.sourceScripts) > 0 && !c.sourceHasDenseScript() {

// SOURCE-script gate (строка 79, D39.39): the 万/億/兆 markers are Han, so a DECLARED non-dense source
// cannot carry a magnitude — stay inert rather than firing on a stray Han rune quoted inside e.g. an
// English source. An UNDECLARED source (nil scripts) keeps the prior content-only behaviour.
if len(c.sourceScripts) > 0 && !c.sourceHasDenseScript() {
	return 0, nil
}

=== RUN TestNumberMagnitudeSourceScriptGate --- PASS: TestNumberMagnitudeSourceScriptGate (0.00s) PASS ok textmachine/backend/internal/checks 0.011s Другие носители того же факта: docs/PROGRESS.md:85 (строка бэклога 79 — уже несёт «Первые два компонента строки (isCyrLetter→isTargetWordLetter · magnitude SourceScripts-гейт) исполнены пакетом-чекеров») Предложенное действие: Пометить пункт стрелкой: «→ ИСПОЛНЕНО пакетом-чекеров: SOURCE-script-гейт стоит в lintNumberMagnitude (cheapgates.go:555, sourceHasDenseScriptcheckers.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.yaml0.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 в Resumehttpapi/v0.go, хендлер resumeRun), а контракт теперь требует 409 run_not_resumable · cause.code: ceiling_reached Почему протухло: Расхождение ЗАКРЫТО: Resume больше не отвечает 202 на исчерпанный прогон — вердикт ceilingSpent возвращает ErrCeilingReached, и httpapi/v0.go:807 кладёт его в 409 run_not_resumable · cause: ceiling_reached. Регистр платформы держит PD-282 как fixed(акт 5) с пином теста. Ветки exhausted в reopen/Resume, на которую указывает док, в коде НЕТ вовсе — она пере-названа в ceilingSpent/creditUnavailable. Улика — команда: grep -n 'ceilingSpent\|ErrCeilingReached' platform/internal/runs/reconcile.go platform/internal/httpapi/v0.go | grep -v _test; grep -n 'exhausted' platform/internal/runs/reconcile.go; grep -o 'PD-282[^|]*| fixed[^|]*|' platform/docs/DEFECT_REGISTER.md Улика — вывод: platform/internal/runs/reconcile.go:1325: ceilingSpent platform/internal/runs/reconcile.go:1371: return pgstore.LiveRun{}, ceilingSpent, nil platform/internal/runs/reconcile.go:1570: case ceilingSpent: platform/internal/runs/reconcile.go:1578: return pgstore.Run{}, ErrCeilingReached platform/internal/httpapi/v0.go:807: case errors.Is(err, runs.ErrCeilingReached): --- grep 'exhausted' (положительный контроль: греп В ЭТОМ ФАЙЛЕ находит) --- 950: // The reason it is not cosmetic: a ceiling reason of credit_exhausted lights the ACCOUNT-level 980: // this way. Guessing credit_exhausted would light the account-level halted flag 1277: // the account's reason is what made it look like an exha Положительный контроль: grep -n 'exhausted' platform/internal/runs/reconcile.go → 3 хита (950, 980, 1277). Инструмент в этом файле находит слово; отсутствует именно ВЕТКА case "exhausted", а не способность грепа её увидеть. Другие носители того же факта: docs/architecture/14-api-contract/README.md:1724 — Приложение А-2, строка run_not_resumable: «где платформа сегодня молча отвечает 202» с тем же мёртвым указателем «ветка exhausted функции reopen», platform/docs/DEFECT_REGISTER.md:446 — PD-282, уже помечен fixed (носитель актуален, менять нечего) Предложенное действие: В §3 (строка 981) заменить «платформа отвечает 202 и возвращает прогон в то же состояние (…ветка exhausted…)» на «СОШЛОСЬ: платформа отвечает 409 run_not_resumable · cause: ceiling_reached (runs/reconcile.go, вердикт ceilingSpent в Resumehttpapi/v0.go:807); PD-282 закрыт, пин runs.TestResumeOfARunWithNothingLeftIsRefusedWithTheCeilingReached». То же — в Приложении А-2, строка 1724: снять «где платформа сегодня молча отвечает 202» и указатель на ветку exhausted, добавить второй вердикт creditUnavailablecause: credit_unavailable. Разрушающее: True ⚠ Опровергатель поправил действие: Предложенное действие верно по сути; добавить четыре вещи. (1) НЕ трогать первую половину строки 981 про паузу — она подтверждена кодом (reconcile.go:1537-1544). (2) В обеих правках назвать ОБА вердикта, а не один: ceilingSpentcause: ceiling_reached и creditUnavailablecause: credit_unavailable (reconcile.go:1581, v0.go:813) — прежняя редакция дока схлопывала их в один exhausted, и разведение как раз и есть суть закрытия. (3) Пин цитировать МОЖНО, но с оговоркой: TestResumeOfARunWithNothingLeftIsRefusedWithTheCeilingReached без TM_PLATFORM_TEST_DSN уходит в SKIP, поэтому «зелёный тест» доказательством закрытия НЕ является — доказательство это reconcile.go:1570-1578 + v0.go:807-813. (4) Находка назвала не все носители внутри самого дока: третий — README:1265+ (§6в «A. Прогон с исчерпанным бюджетом (PD-282)») повторяет «Resume отвечает на оба одинаково: 202 + неизменённый Run» и «reopen даёт exhausted в ДВУХ местах». Это ДАТИРОВАННАЯ запись решения («Синк с платформой 20.08 → канон 0.4.0: что решено и почему»), то есть улика-обоснование, а не живое утверждение: её ТЕЛО переписывать не надо, ей нужен ⚠-указатель «закрыто, PD-282 fixed(акт 5), см. §3». Живой переписи подлежат только 981 и 1724.


N036 · MAJOR · docs/architecture/14-api-contract/README.md:1637 · A-дрейф

Срез: Контракт API и его зеркала — docs/architecture/14-api-contract/README.md + docs/architecture/14-api-contract/openapi.yaml Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): - Чем и на каком слое сжимать (middleware, обратный прокси, CDN). Go stdlib не сжимает, edge-конфига в репозитории нет, grep -rn "gzip|Content-Encoding" platform --include=*.go → ноль. Почему протухло: Собственная команда дока, исполненная сегодня, даёт 26 хитов, а не ноль: платформа сжимает САМА, в Go, в platform/internal/httpapi/conditional.go (gzipFloor, acceptsGzip, head.Set("Content-Encoding","gzip")). Файл приехал с паком P7 (коммит 9b23e8c), то есть абзац родился верным и сгнил лендингом. Он же противоречит §3 ТОГО ЖЕ дока (строка 994: «Условные чтения (ETag/304), сжатие | ПОСТРОЕНО P7 — httpapi/conditional.go») и последнему пункту §7 («шаги 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». Разрушающее: True ⚠ Опровергатель поправил действие: Действие в целом верное, но с тремя правками. (1) НЕ подменять число в команде («26 вместо ноля»): команду из абзаца убрать целиком — она BRE-формы и её ноль ничего не значит; вместо ссылки на греп поставить file:line факта (platform/internal/httpapi/conditional.go:64-70, порог gzipFloor = 1<<10, Vary: Accept-Encoding на всяком согласуемом представлении, acceptsGzip по RFC 9110 §12.5.3). (2) Клаузу «Go stdlib не сжимает» сохранить как верную, снести только вывод «edge-конфига нет ⇒ деплой обязан устроить сжатие сам»: в эксплуатационном примечании остаётся ровно одно — не жать ВТОРОЙ раз на прокси/CDN и не жать text/event-stream (поток по построению не проходит через writeJSON — комментарий conditional.go:20-24 и README:991). (3) Хвост §7 «шаги 12 — конфигурация» править обязательно и в ОДНОМ коммите с абзацем 1637 — иначе останется третий расходящийся носитель внутри одного файла (994 говорит «шаги 12 закрыты D39.153»).


N037 · MAJOR · docs/architecture/14-api-contract/README.md:1718 · B-мёртвый-указатель

Срез: Контракт API и его зеркала — docs/architecture/14-api-contract/README.md + docs/architecture/14-api-contract/openapi.yaml Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): | gone | 410 | pgstore.ErrNoChapterv0.go:761 … | run_in_flight | 409 | pgstore.ErrRunInFlight (v0.go:548) | … | payload_too_large | 413 | *http.MaxBytesErrorv0.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 er Положительный контроль: Тем же методом (sed по номеру) проверены якоря, которые ЖИВЫ: middleware.go:68Fail(w, r, CodeInternalError) (в точку), problem.go:42CodeContentRefused 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,111server.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.ErrBadIntakebooks.go:102-117. Отдельно завести строку долга: линтер counts.py --lint этот класс не покрывает — якорь без токена проверяется только на существование файла. Разрушающее: False


N038 · MAJOR · docs/architecture/14-api-contract/openapi.yaml:1414 · E-второй-носитель

Срез: Контракт API и его зеркала — docs/architecture/14-api-contract/README.md + docs/architecture/14-api-contract/openapi.yaml Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): Today's deployments answer drafting or editing; other values are not a breaking change and do not raise this version. Почему протухло: Деплой отвечает ТРЕМЯ значениями: runStage (platform/internal/pgstore/readmodel.go:576) выдаёт re_pass для всего пере-прохода, помимо drafting/editing, и значение доезжает на провод (project.go:53 → v0.go:155 Stage). Фраза родилась верной в 0.6.0 (58bae30) и сгнила пере-проходом 0.7.0 (7d45e24). Ломающим это не делает (словарь объявлен открытым), но нормативная проза компилируется в JSDoc генерённых типов — клиент учится по ней неправде. Улика — команда: sed -n '570,579p' platform/internal/pgstore/readmodel.go; grep -n 'Stage' platform/internal/httpapi/project.go platform/internal/httpapi/v0.go | grep -v test; git log --oneline -S"'re_pass'" -- platform/internal/pgstore/readmodel.go Улика — вывод: 576: runStage = (case when r.ceiling_chapters = 0 then 're_pass' 577: when + editWave + and + draftBar + >= + draftWork + 578: then 'editing' else 'drafting' end) (комментарий 574-575: «always drafting where drafting is the only pass there is, and re_pass for the whole of a re-pass run, which is neither») platform/internal/httpapi/project.go:53: Done: r.Progress.Done, Total: r.Progress.Total, Stage: r.Progress.Stage, platform/internal/httpapi/v0.go:155: Stage string json:"stage" 7d45e24 Land the pack that lets a bank correction reach translated text: the run no longer dies on the snapshot guard and a re-pass can be bought Другие носители того же факта: docs/architecture/14-api-contract/README.md:883 — «Сегодняшние значения — drafting и editing.» (§2.20, тот же факт, та же протухшая пара) Предложенное действие: В openapi.yaml:1414 заменить на «Today's deployments answer drafting, editing or re_pass; other values are not a breaking change and do not raise this version.» и синхронно поправить README:883 («Сегодняшние значения — drafting, editing и re_pass (последнее — на всём пере-проходе).»). ⚠ Правка НОРМАТИВНОГО файла: минор поднимать не надо (словарь открыт по объявлению), но лендинг обязан пройти cmp-сверку и гейт platform/internal/gates/contract_test.go. Разрушающее: True ⚠ Опровергатель поправил действие: Текст правки верен, обоснование и процедура — нет; передать принимающему три поправки. (1) НОСИТЕЛЕЙ ТРИ, а не два: к openapi.yaml:1414 и README:883 добавить комментарий кода platform/internal/pgstore/books.go:776-777 — он утверждает ту же неправду над самим полем Stage и относится к зоне ПЛАТФОРМЫ, значит уходит не правкой оркестратора, а пингом в platform/docs/platform-PROGRESS.md. (2) «Гейт platform/internal/gates/contract_test.go» страховкой этой правки НЕ является: он читает из канона одну строку info.version регэкспом и сверяет с httpapi.ContractVersion (contract_test.go:34-40) — нормативной прозы не касается вовсе, а info.version эта правка не двигает, так что гейт к ней просто безразличен. (3) «cmp-сверка» невыполнима и её нельзя ставить условием лендинга: байт-зеркало, объявленное самим каноном (openapi.yaml:8-9 «frontend/docs/api-contract/ is a byte-mirror and a divergence is a defect of one of the two»), СЕГОДНЯ разошлось катастрофически — frontend/docs/api-contract/openapi.yaml держит version 0.2.3 и 61 189 байт против канонных 0.9.0 и 147 925 байт (cmp расходится уже на строке 5), поля stage там нет вовсе. Это отдельный, гораздо более крупный дефект, и правку stage на него вешать нельзя — но и рационале находки надо поправить: генерённые типы (frontend/package.json:19openapi-typescript docs/api-contract/openapi.yaml -o src/api/schema.ts) компилируются из ЗЕРКАЛА, а не из канона, и в frontend/src/api/schema.ts слова drafting/editing/re_pass не встречаются ни разу. Значит «клиент учится по этой прозе неправде» сегодня неверно — клиент вообще не видит этой прозы; вред от протухшей фразы чисто канонический (человек-читатель нормы), и заявлять его надо так.


N039 · MAJOR · docs/architecture/15-money-path.md:6 · B-мёртвый-указатель

Срез: Денежный путь — docs/architecture/15-money-path.md Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): Стабильна не ЗОНА, а ЧИСТЫЙ ФАЙЛ. Номер строки ставится только туда, где git status <цель> показывает файл чистым … Ось именно такая (D39.177 §2в), и зонная ось её НЕ заменяет Почему протухло: Норму «чистый ФАЙЛ» ратифицировал D39.179 п.4, который сам говорит: «уточняет D39.177 §2в … Ось «зона» была НЕВЕРНА … Носитель нормы — шапка 15-money-path.md». А D39.177 §2(в), на который шапка ссылается, несёт как раз ЗОННУЮ/«живая сессия» ось. То есть единственный указатель на ратификацию ведёт в ноту, которая эту норму ОПРОВЕРГАЕТ, а ратифицирующая нота D39.179 в доке не названа НИ РАЗУ. Читатель, грепнувший D39.177, увидит шапку противоречащей своему же основанию и либо починит её обратно в зонную ось, либо решит, что норма не ратифицирована. Улика — команда: grep -n 'Норма формы указателей заострена' docs/architecture/05-decisions-log.md; grep -c 'D39\.179' docs/architecture/15-money-path.md Улика — вывод: 1673:4. Норма формы указателей заострена (уточняет D39.177 §2в): стабильна не ЗОНА, а ЧИСТЫЙ ФАЙЛ. … Ось «зона» была НЕВЕРНА и поймана опровергателем на моей же правке … Носитель нормы — шапка 15-money-path.md. 0

Цель ссылки дока (D39.177 §2в): «(в) Якорь по номеру строки в файл, который правит ЖИВАЯ сессия, — заведомо мёртвый … Это касается зонных журналов, канона под минором и кода зоны в работе.» Положительный контроль: grep -o 'D39.[0-9]*' docs/architecture/15-money-path.md | sort -u → D39.100 D39.110 D39.112 D39.115 D39.122 D39.123 D39.125 D39.131 D39.132 D39.134 D39.154 D39.161 D39.165 D39.167 D39.170 D39.177 D39.84 D39.86 (18 номеров найдено — инструмент в этой области работает, то есть ноль по D39.179 не артефакт грепа) Предложенное действие: Заменить «Ось именно такая (D39.177 §2в)» на «Ось именно такая — ратифицировано D39.179 п.4, который уточняет D39.177 §2в и прямо объявляет зонную ось НЕВЕРНОЙ (там же: «Носитель нормы — шапка 15-money-path.md»)», и внести D39.179 в карту носителей §6. Разрушающее: False


N040 · MAJOR · docs/architecture/15-money-path.md:19 · A-дрейф

Срез: Денежный путь — docs/architecture/15-money-path.md Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): На границе юнита сидел РЕПЭЙР-суб-бюджет — ужесточён до пер-вызовного с ценой вызова (строка 135 закрыта D39.131); эскалационный кап хоп НЕ прицениваает — перелёт ≤1 хопа, задокументирован и запинен Почему протухло: Перечень движковых суб-бюджетов читается как исчерпывающий (репэйр + эскалация) и не знает третьего семейства — книго-широких бюджетов банк-ролей gates.terminology.budget_usd и classify_budget_usd. Именно оно резало ОПЛАЧЕННУЮ работу на холодном прогоне 31.08 (D39.182 §4: «инцидент холодного прогона был на классификаторе»; комментарий в terminologist.go: «its $0.02 was exhausted … three of four classify batches were never bought»), и именно его поведение ИЗМЕНИЛОСЬ в тот же день: до bb541a8 проход обрывался ПОСЕРЕДИНЕ, после — план режется ЦЕНОЙ ПАРТИИ ДО первого вызова, и срез отчитывается новыми носителями BatchesDropped/ClassifyBatchesDropped. Денежный вход этого не несёт ни строкой, а калибровка этих цифр ($0.05/$0.02/$0.08 в coldrun-v16) — июльская. ⚠ ПРОВЕРЕНО И ОБРАТНОЕ: утверждение «перелёт ≤1 хопа» ДЕРЖИТСЯ и правки НЕ требует. Улика — команда: grep -n 'probe+want > plan.budgetUSD' backend/internal/pipeline/terminologist.go; git show bb541a8^:backend/internal/pipeline/terminologist.go | grep -n 'spent+want > plan.budgetUSD'; grep -n 'budget_usd' books/gu-zhenren/coldrun-v16/pipeline.yaml Улика — вывод: 782: if probe+want > plan.budgetUSD { <- СЕГОДНЯ: план режется ДО первого вызова 725: if want := r.bankCallEstimateUSD(st, msgsPer[i]); !paid && spent+want > plan.budgetUSD { <- ДО bb541a8: обрыв ПОСЕРЕДИНЕ прохода 97: budget_usd: 0.05 101: classify_budget_usd: 0.02 110: budget_usd: 0.08 Положительный контроль: ОТРИЦАТЕЛЬНОЕ: grep -in 'classify_budget|terminolog|банк-рол|gates.terminology' docs/architecture/15-money-path.md → 0 хитов (rc=1). ПОЛОЖИТЕЛЬНЫЙ КОНТРОЛЬ в той же области: grep -on 'суб-бюджет|бюджет' docs/architecture/15-money-path.md → 19:суб-бюджет и 22:бюджет — греп по русской теме бюджетов в этом файле работает, значит ноль не артефакт инструмента. Предложенное действие: Дописать в тот же буллет третий пункт: «Банк-роли несут СОБСТВЕННЫЕ книго-широкие бюджеты — gates.terminology.budget_usd и gates.terminology.classify_budget_usd; с 31.08 (D39.182) план прохода режется ценой партии ДО первого вызова, а не обрывается посередине, и срез отчитывается полями BatchesDropped/ClassifyBatchesDropped (backend/internal/pipeline/terminologist.go, греп probe+want > plan.budgetUSD). Это УСЕЧЕНИЕ, а не отказ: проход идёт внутри банк-стопа, после оплаченной черновой волны. ⚠ Цифры этих суб-бюджетов в книжных конфигах калиброваны под ИЮЛЬСКИЕ цены (D39.179 п.1).» И внести pipeline/terminologist.go в карту §6. Разрушающее: False


N041 · MAJOR · docs/architecture/15-money-path.md:21 · D-противоречие-ратифицированному

Срез: Денежный путь — docs/architecture/15-money-path.md Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): Остановка по объёму — ЗАВЕРШЕНИЕ (exit 0), не пауза: словарь кодов выхода не расширялся, различение живёт в отчёте прогона и в логе; отчёт разводит ДОСТАВКУ и ПЕРЕ-ДЕЛКУ. Почему протухло: «Различение живёт в отчёте прогона и в логе» перестало быть верным 31.08. D39.181 п.2 ратифицировал закон раскрытия: «признак остановки по объёму обязан ехать ЧИСЛОМ» в кадре finished шва, и обоснование ноты ровно в том, что прозаическая строка отчёта до потребителя потока не доезжала («said it in ONE PROSE LINE that no consumer of this stream reads»). Поле построено И проведено на обоих исходах. Док остался в редакции ДО закона и посылает читателя-платформу в тот самый канал, который закон объявил недостаточным. Улика — команда: sed -n '200,206p' backend/internal/runevents/runevents.go; grep -n 'Volume: volumeLedger(res)' backend/internal/pipeline/events.go Улика — вывод: // run: the line would say volume while OutcomeOf(0) said clean. … Numbers have no vocabulary to // contradict, so the fact travels as numbers, and its PRESENCE is the boolean … // // Ratified with the disclosure law, D39.181 п.2. Volume *VolumeLedger json:"volume,omitempty" }

backend/internal/pipeline/events.go:440: e.emit(runevents.TypeFinished, runevents.Finished{Outcome: runevents.OutcomeFlagged, Volume: volumeLedger(res)}) backend/internal/pipeline/events.go:442: e.emit(runevents.TypeFinished, runevents.Finished{Outcome: runevents.OutcomeClean, Volume: volumeLedger(res)}) Предложенное действие: Заменить хвост предложения на: «…словарь кодов выхода не расширялся и нового значения Finished.Outcome тоже нет — признак едет ЧИСЛАМИ в кадре finished шва: Finished.Volume — леджер доставки (max_units/delivered/reworked/flagged/free/left_fresh/left_rework), и его ПРИСУТСТВИЕ и есть булев признак остановки по объёму (ратифицировано D39.181 п.2; backend/internal/runevents/runevents.go, греп Volume *VolumeLedger). Отчёт прогона и лог — вторая копия того же факта, разводящая ДОСТАВКУ и ПЕРЕ-ДЕЛКУ.» Разрушающее: False


N042 · MAJOR · docs/architecture/16-events-emitter.md:11 · B-мёртвый-указатель

Срез: Эмиттер · закон шва · онтология банка — docs/architecture/16-events-emitter.md · 17-seam-inbound-law.md · 18-bank-ontology.md Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): Что. Строка 103 бэклога (docs/PROGRESS.md:149), дословно ядро ячейки: Почему протухло: Якорь ведёт не туда И цель больше не существует: docs/PROGRESS.md:149 — это строка бэклога 42, а строк 103 и 165 в таблице нет вовсе (закрыты D39.131 «строки 103+135+165+102 закрыты»). Тот же дубль в §6 строка 123 (docs/PROGRESS.md:149, :113) — там :113 показывает строку 160. Улика — команда: sed -n '149p;113p' docs/PROGRESS.md | cut -c1-70; for n in 100 103 160 165; do printf "row %s -> %s\n" $n "$(grep -c "^| $n |" docs/PROGRESS.md)"; done Улика — вывод: | 42 | prompt_override — легальный обход гарантии слоя-2, сформул | 160 | Структура глав, Этап 0 ($0, wire-нейтрально): titleRaw row 100 -> 0 row 103 -> 0 row 160 -> 1 row 165 -> 0 Положительный контроль: Тот же счётчик по соседним номерам находит живые строки: for n in $(seq 204 216); do printf "%s:%s " $n $(grep -c "^| $n |" docs/PROGRESS.md); done204: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.go5:// 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 (токен уникален и переживёт сдвиг лучше номера). Разрушающее: True ⚠ Опровергатель поправил действие: Находка верна, предложенный ЯКОРЬ неверен и провалит гейт. counts.py:anchor_window даёт для :243 окно ровно [243,243], а предложенный токен return false, fmt.Errorf("pipeline: re-seed the bank with the auto rows физически лежит на строке 244 — гейт напечатает «ожидаемого ... в цели НЕТ (цель уехала)». Верная форма: backend/internal/pipeline/mining.go:243-244=re-seed the bank with the auto rows (диапазон объединяется, токен попадает в окно). Полный путь от корня ОБЯЗАТЕЛЕН: голое mining.go:221 гейт считает внешней ссылкой и не проверяет, поэтому починка без пути оставит якорь ровно так же беззащитным. Правка одного якоря в ратифицированном доке — обслуживание, не пере-ратификация.


N047 · MAJOR · docs/architecture/17-seam-inbound-law.md:71 · C-исполненное-как-открытое

Срез: Эмиттер · закон шва · онтология банка — docs/architecture/16-events-emitter.md · 17-seam-inbound-law.md · 18-bank-ontology.md Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): загрузчике сида (строка 212) новое поле теряется МОЛЧА. Почему протухло: Загрузчик сида СТРОГИЙ с пака входной двери: backend/internal/seed/decode.go называет строку 212 своим именем и ставит dec.KnownFields(true) — неизвестный ключ теперь громкая ошибка с номером строки, а не тихая потеря. Строка 212 закрыта и снята с бэклога. Довод п.5 («иначе новое поле теряется МОЛЧА») сегодня опирается на несуществующий дефект. Улика — команда: grep -n 'backlog row 212' backend/internal/seed/decode.go; grep -n 'KnownFields(true)' backend/internal/seed/decode.go; for n in $(seq 209 216); do printf "%s:%s " $n $(grep -c "^| $n |" docs/PROGRESS.md); done Улика — вывод: 16:// struct does not have is a typo, and yaml.v3 DROPS one silently, so gendr: for gender: reads as 17:// "not set" and the run looks normal (backlog row 212). The same class the strict book.yaml decoder 61: dec.KnownFields(true) 209:1 210:1 211:0 212:0 213:1 214:1 215:0 216:1 Положительный контроль: Тот же счётчик по соседям возвращает единицы (209, 210, 213, 214, 216) — таблица читается, и ноль у 212 означает отсутствие строки, а не промах грепа. Предложенное действие: Заменить довод: «…а строгий с D39.158 загрузчик сида (backend/internal/seed/decode.go:61=dec.KnownFields(true), строка 212 ЗАКРЫТА) роняет неизвестный ключ ГРОМКО — именно поэтому пиннинг бинаря остаётся единственной защитой от чтения нового файла старым кодом». Правка ратифицированного носителя. Разрушающее: True ⚠ Опровергатель поправил действие: Находка верна, атрибуция в предложенной формулировке неточна. Строгость КОДА приехала лендингом d1eb8a9 (пак D39.158), но строку 212 закрыла и сняла с таблицы ЭРРАТА 27.08-д = D39.159 п.7 — на неё и надо ссылаться, иначе следующий читатель пойдёт грепать 212 в теле D39.158 и не найдёт (я проверил: в теле D39.158 номера 212 нет вовсе). Форма якоря: backend/internal/seed/decode.go:61=dec.KnownFields(true) — корректна и гейт её судит. ⚠ Заодно, при той же правке абзаца: п.5 ссылается ещё и на «строку бэклога 139», а grep -c '^| 139 |' docs/PROGRESS.md = 0 при живых соседях — этот номер надо пере-судить тем же заходом, иначе абзац починят наполовину.


N048 · MAJOR · docs/architecture/17-seam-inbound-law.md:97 · C-исполненное-как-открытое

Срез: Эмиттер · закон шва · онтология банка — docs/architecture/16-events-emitter.md · 17-seam-inbound-law.md · 18-bank-ontology.md Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): | приём правок банка движковым глаголом | строка 199(а) — развилка владения book.yaml растворяется этим | Почему протухло: Первая строка таблицы «Двери, УЖЕ подразумеваемые живыми строками» описывает как ПРЕДСТОЯЩУЮ ту самую дверь, которую собственная шапка дока объявляет построенной («⚠ ИСПОЛНЕН 27.08: первая дверь по этому закону ПОСТРОЕНА и заленджена — tmctl bank-apply»). Три соседние строки таблицы получили зачёркивание и пометку ПОСТРОЕН, эта — нет. Строка 199 закрыта (D39.156 п.3 «растворением», D39.158 п.5) и снята с бэклога; D39.166 отдельно фиксирует «Бэклог держал ДВЕ ЗАКРЫТЫЕ строки (199, 215)». Улика — команда: grep -n 'bank-apply' backend/cmd/tmctl/main.go | head -3; for n in $(seq 195 203); do printf "%s:%s " $n $(grep -c "^| $n |" docs/PROGRESS.md); done; grep -n 'Бэклог держал ДВЕ ЗАКРЫТЫЕ строки' docs/architecture/05-decisions-log.md | cut -c1-90 Улика — вывод: backend/cmd/tmctl/main.go:2:// backup / migrate / seed-lint / bank-apply (dispatchCommands). backend/cmd/tmctl/main.go:185: "bank-apply", backend/cmd/tmctl/main.go:249: case "bank-apply": 195:1 196:1 197:1 198:1 199:0 200:0 201:1 202:1 203:1 1121:3. Бэклог держал ДВЕ ЗАКРЫТЫЕ строки (199, 215) вопреки собственному правилу Положительный контроль: Соседи 195198 и 201203 возвращают по единице — таблица бэклога читается грепом, и ноль у 199 не артефакт формы. Другие носители того же факта: platform/docs/platform-PROGRESS.md:2750 (исторический фикс-лист приёмки: «Труба доставки решений банка в движок — единый бэклог, строка 199(а)» — тоже описывает дверь как предстоящую) Предложенное действие: Привести строку к форме соседей: «приём правок банка движковым глаголом ПОСТРОЕН 27.08 (tmctl bank-apply, D39.158); строка 199(а) ЗАКРЫТА растворением (D39.156 п.3) и снята с бэклога». Заодно перечитать строку 103 таблицы («приоритет ПАДАЕТ после двери 199(а)») — падение уже произошло. Разрушающее: True ⚠ Опровергатель поправил действие: Действие верное, но чинить надо ОБЕ строки одним заходом, а не одну: строка 103 той же таблицы («tmctl init (форма В) … приоритет ПАДАЕТ после двери 199(а) — та забирает её главный довод») стоит в будущем времени о двери, которая построена, — падение уже произошло, и её надо переписать в прошедшее. Атрибуцию брать точную: дверь — D39.158 (лендинг d1eb8a9), развилка 199(а) растворена D39.156 п.3, сама строка снята с бэклога сверткой D39.166 п.3. ⚠ ВТОРОЙ НОСИТЕЛЬ из находки трогать НЕ НАДО: platform/docs/platform-PROGRESS.md:2750 сидит внутри раздела «### Фикс-лист приёмки — завести строками СВОЕГО бэклога» (:2738) — это ХРОНИКА чужой зоны, запись о том, что было верно на своей дате, а не живой носитель факта; правка чужого журнала задним числом стирает след, а не чинит его.


N049 · MAJOR · docs/architecture/17-seam-inbound-law.md:100 · C-исполненное-как-открытое

Срез: Эмиттер · закон шва · онтология банка — docs/architecture/16-events-emitter.md · 17-seam-inbound-law.md · 18-bank-ontology.md Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): ⚠ Родня строки 231: та просит ту же способность («оценить, ничего не покупая») со стороны ШВА, и решаться они, скорее всего, одним глаголом Почему протухло: Строка 231 закрыта паком «деньги» (D39.170, 29.08) и снята с бэклога — сам PROGRESS.md говорит это в лоб. Док цитирует её ДВАЖДЫ как живую: на строке 100 («родня», решится одним глаголом) и на строке 101 («Родня — строка 231 … смету пере-прохода до покупки показать нечем, и это названная цена минора 0.7.0»). Второе утверждение прямо опровергнуто телом D39.170: слепое окно закрыто, foldMemoryForRead стал первым ответом читающего пути. Улика — команда: for n in $(seq 228 234); do printf "%s:%s " $n $(grep -c "^| $n |" docs/PROGRESS.md); done; grep -n 'строк 231 и 236 в таблице БОЛЬШЕ НЕТ' docs/PROGRESS.md; grep -n 'строка бэклога$' -A1 docs/architecture/05-decisions-log.md | sed -n '1,2p'; sed -n '1271,1273p' docs/architecture/05-decisions-log.md | cut -c1-160 Улика — вывод: 228:1 229:1 230:1 231:0 232:1 233:1 234:1 docs/PROGRESS.md:285:> (⚠ строк 231 и 236 в таблице БОЛЬШЕ НЕТ — 231 съел пак «деньги», 236 сужена D39.175; прежняя редакция (D39.170) «Слепое окно закрыто. Следствия проведены этим же лендингом: строка бэклога 231 закрывается, комментарий платформы получает строку в её регистр, а проводка полей через шов становится ВОЗМОЖНОЙ» Положительный контроль: Соседние номера 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) остаётся открытой САМА ПО СЕБЕ, без родни». Правка ратифицированного носителя. Разрушающее: True ⚠ Опровергатель поправил действие: Действие в целом верное, но формулировку для строки 101 надо взять точнее, иначе док качнётся в противоположную ложь. «Смету показать нечем» неверно, однако смета до сих пор НЕ доезжает до покупателя через шов: платформа сознательно НЕ берёт rebill_units/rebill_usd, и это записано её же кодом (platform/internal/ingest/resync.go:37-39=The engine's re-bill projection (rebill_units/rebill_usd) is deliberately NOT taken, регистровая строка PD-427), а D39.170 отдельно оговаривает, что проводка полей «становится ВОЗМОЖНОЙ — но не выполняется». Верная замена хвоста: «⚠ испр.: строка 231 ЗАКРЫТА D39.170 (29.08) — движок оценивает пере-проход $0-глаголом status, свернув банк; открытым остаётся не ГЛАГОЛ, а проводка полей через шов (PD-427 платформы). Движковая оценка $/глава (строка 166) остаётся открытой сама по себе, без родни». Правка ратифицированного носителя ⇒ нотой оркестратора.


N050 · MAJOR · docs/glossary.md:11 · C-исполненное-как-открытое

Срез: Глоссарий и продуктовые требования — docs/glossary.md · docs/product-requirements.md Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): P12 «долги под ногами» — семь открытых major + снятие обхода --verify-bank, ВЫДАН 30.08, на приёмке. Почему протухло: P12 принят и залёнджен 31.08 нотой D39.180 (коммит 0a680a3), обязательство D39.158 закрыто целиком, регистр: major 2 против 7. Глоссарий лежит в онбординг-пути и сообщает свежей сессии, что пак ещё на приёмке. Улика — команда: git log --oneline -1 0a680a3; git log -1 --format='%h %ad' --date=short -- docs/glossary.md; grep -m1 '| D39.180 |' docs/architecture/05-decisions-index.md | cut -c1-200 Улика — вывод: 0a680a3 Land platform pack P12 with contract minor 0.9.0: the bank-stop bypass is gone, a chapter counts by the pass current for it, and the boundary now judges prose too df4b064 2026-08-30 | D39.180 | 31.08 | Пак платформы P12 «долги под ногами» ПРИНЯТ И ЗАЛЕНДЖЕН вместе с контрактным минором 0.9.0. Приёмка — 5 линз, 6 major / 16 minor / 0 блокеров, 8 дофиксов исполнены. Обязательство D39.158 ... закрыто целиком Другие носители того же факта: docs/PROGRESS.md:37 — «1. Приёмка пака P12 — платформа, сессия textmachine-main-b5, работает» в блоке «ОТКРЫТО НА ОРКЕСТРАТОРЕ №21 (снимок 31.08)»; блок помечен как снимок, но пункты 13 уже исполнены Предложенное действие: Заменить хвост строки на «P12 «долги под ногами» — семь открытых major + снятие обхода --verify-bank, ПРИНЯТ и ЗАЛЕНДЖЕН 31.08, D39.180, вместе с минором 0.9.0». Разрушающее: True


N051 · MAJOR · docs/glossary.md:22 · E-второй-носитель

Срез: Глоссарий и продуктовые требования — docs/glossary.md · docs/product-requirements.md Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): - V0V5 — секции идей владельца в START_PROMT.MD (живой бриф; разобраны в product-requirements.md). Почему протухло: В START_PROMT.MD есть секция V6 от 18.08.2026 (идея переделки архитектуры пайплайна), разобранная в реестр 23.08 строками ПТ-37..ПТ-43. Парный носитель — шапка product-requirements.md — уже говорит «идеи V0V6». Диапазон в глоссарии не обновили, и он занижает список секций владельца ровно на ту, что предлагает переделку пайплайна. Улика — команда: 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 идеи V0V6 Другие носители того же факта: docs/product-requirements.md:3 — «Источники: ../START_PROMT.MD (ЖИВОЙ бриф, идеи V0V6...)» — ВЕРНЫЙ носитель Предложенное действие: Заменить на «V0V6 — секции идей владельца в START_PROMT.MD (живой бриф; разобраны в product-requirements.md; V6 от 18.08 — ПТ-37..ПТ-43)». Впредь диапазон в глоссарии двигать тем же коммитом, что разбирает новую V-секцию. Разрушающее: True


N052 · MAJOR · docs/product-requirements.md:32 · E-второй-носитель

Срез: Глоссарий и продуктовые требования — docs/glossary.md · docs/product-requirements.md Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): $0.85/ранобэ (D30.4); ре-проба исполнена и строка 74 закрыта (замер D39.86/91, закрытие D39.95); пере-калибровка цены — при следующем платном прогоне Почему протухло: Число — июльские деньги ДО пере-пина цен DeepSeek 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 (они не в моём срезе). Разрушающее: True ⚠ Опровергатель поправил действие: Действие в целом верное, но одну его половину надо адресовать точнее: фразы «отстаёт на СЕМЬ миноров» в самой ПТ-20 НЕТ — ячейка говорит только «зеркало ФРОНТА ратифицированно отстаёт на 0.2.3», и это утверждение ОСТАЁТСЯ ВЕРНЫМ (frontend/docs/api-contract/openapi.yaml = 0.2.3). Счёт «шесть миноров» живёт в docs/PROGRESS.md:3 — правку числа шесть→семь вносить ТУДА, а в ПТ-20 менять только хвост про 0.9.0/P12. Ячейку 0.9.0 при этом писать без нового номера строки openapi.yaml (сама ячейка предупреждает, что номер двигается).


N054 · MAJOR · docs/product-requirements.md:63 · D-противоречие-ратифицированному

Срез: Глоссарий и продуктовые требования — docs/glossary.md · docs/product-requirements.md Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): остаток: страница лимитов и оповещение на фронте (после P7) Почему протухло: D39.147 (слово владельца 17.08) АМЕНДИРОВАЛ условие разморозки: «лендинг P7 зону БОЛЬШЕ НЕ размораживает», S5-промт не выдаётся, разморозка — отдельным словом владельца. P7 залёнджен 20.08 (D39.153), а остаток ПТ-35 по-прежнему адресован «после P7» — гейт, который прошёл, ничего не открыв. Тот же коммит D39.147 п.3 синхронизировал ПТ-20 и ПТ-35 не тронул. Улика — команда: sed -n '500p' docs/architecture/05-decisions-log.md | grep -o 'лендинг P7 зону БОЛЬШЕ НЕ размораживает.\{0,90\}'; grep -m1 '| D39.153 |' docs/architecture/05-decisions-index.md | cut -c1-90 Улика — вывод: лендинг P7 зону БОЛЬШЕ НЕ размораживает**, S5-промт не выдаётся; разморозка — отдельным словом владельца по достижении сквозного пути | D39.153 | 20.08 | Приёмка и лендинг P7 платформы (пять актов, читающая поверхность): **ПРИН Другие носители того же факта: docs/glossary.md:11 — верный носитель: «зона ЗАМОРОЖЕНА, и лендинг P7 её НЕ разморозил — D39.147», docs/product-requirements.md:56 (ПТ-20) — верный носитель того же факта в том же доке Предложенное действие: Заменить «(после P7)» на «(после РАЗМОРОЗКИ фронта — D39.147: лендинг P7 её не размораживает, разморозка отдельным словом владельца)». Разрушающее: True


N055 · MAJOR · frontend/README.md:7 · A-дрейф

Срез: Доки замороженной зоны фронта — frontend/**/*.md (10 файлов, 5167 строк) Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): Продуктовых экранов ещё нет — они на 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)». Разрушающее: True ⚠ Опровергатель поправил действие: Формулировка предложенной замены верна, но названный «второй носитель» — ОШИБКА: строка Ф-1 стоит на frontend/docs/BACKLOG.md:7, а не на 7 в смысле находки… проверено: Ф-1 действительно на строке 7, и её столбец «Вес» гласит «по очереди сессий (S0/S1 — текущая)» — это тоже протухло (текущая не S0/S1), так что носитель назван верно, но его правка — отдельный пункт, а не «тот же факт». ⚠ И тут, и в Ф-1 исполнитель — фронт-сессия: frontend/README.md в зоне фронта, зона заморожена (D39.147). Оркестратору доступна только запись в frontend/docs/frontend-PROGRESS.md («шапка README зоны отстала на S4 — поправить первым касанием»).


N056 · MAJOR · frontend/README.md:66 · D-противоречие-ратифицированному

Срез: Доки замороженной зоны фронта — frontend/**/*.md (10 файлов, 5167 строк) Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): эта копия — байт-зеркало канона docs/architecture/14-api-contract/; правит её фронт-сессия, переносит в канон диффом оркестратор при лендинге (прецедент Ф-47/0.2.2). Почему протухло: Зеркало НЕ байт-равно: 0.2.3/16 операций против канона 0.9.0/20 операций, разные md5. И правило «зеркало = байт-копия» ратифицированно ПРИОСТАНОВЛЕНО на время фриза (D39.142 п.5) — нормативен только канон. README — точка входа зоны и учит обратному. Улика — команда: md5sum frontend/docs/api-contract/openapi.yaml docs/architecture/14-api-contract/openapi.yaml; grep -m1 'version:' в обоих; awk-счёт операций Улика — вывод: 4e4ab5e6d313bd56d395032a718ab18a frontend/docs/api-contract/openapi.yaml 34bb4b6d0496f4377633597cd12bc73a docs/architecture/14-api-contract/openapi.yaml зеркало: version: 0.2.3, 15 путей, 16 операций канон: version: 0.9.0, 17 путей, 20 операций (добавлены /capabilities, /books/{bookId}/events, /runs/{runId}; bank/decisions → bank/corrections; снят /runs/{runId}/events) Другие носители того же факта: frontend/docs/api-contract/openapi.yaml (info.description: «byte-mirror of it, and a divergence is a defect»), frontend/docs/frontend-PROGRESS.md:47-48, frontend/docs/frontend-PROGRESS.md:9 (пинг №20 п.4 — называет 0.8.0, канон уже 0.9.0) Предложенное действие: Дописать в этот буллет: «⚠ С фриза зеркало отстаёт РАТИФИЦИРОВАННО (D39.142 п.5): нормативен только канон, зеркало на 0.2.3. Синк байт-в-байт (cmp) + перегенерация типов — первое касание зоны при разморозке». Разрушающее: False


N057 · MAJOR · frontend/docs/BACKLOG.md:50 · D-противоречие-ратифицированному

Срез: Доки замороженной зоны фронта — frontend/**/*.md (10 файлов, 5167 строк) Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): Модель владельца и движка совпадают: подписывается банк ЦЕЛИКОМ, стоп снимается только когда решён каждый термин (pipeline/mining.go:201), в контракте это POST /runs/{id}/resume с 409 при неполном наборе. Почему протухло: D39.144 (16.08) снял гейт полноты. Канон: awaiting_bank с ЛЮБЫМ состоянием решений → 202. Якорь mining.go:201 тоже уехал: там сегодня стоит прямая ссылка на D39.144 и слова владельца, что марш по пер-термным решениям — НЕ продукт. Улика — команда: sed -n "$(grep -n '/runs/{runId}/resume:' docs/architecture/14-api-contract/openapi.yaml | cut -d: -f1),+20p" docs/architecture/14-api-contract/openapi.yaml; sed -n '202,206p' backend/internal/pipeline/mining.go Улика — вывод: | the run stands in | this call answers | | awaiting_bank — with ANY state of decisions | continues it — 202 | | paused — ANY reason, null included | 409 run_not_resumable, cause.code: ceiling_reached |

mining.go:202: // THE FLAG (D39.42 п.5 · D39.144, owner's words 26.08: «стоп на банке памяти — это просто флажок»). mining.go:205-207: «only then: a flag that re-stopped on everything still undecided would turn «остановиться один раз» into a march over per-term decisions the product does not have» Другие носители того же факта: frontend/docs/BACKLOG.md:16 (Ф-14: «стоп снимается ТОЛЬКО полным набором (mining.go:200-202)»), frontend/docs/frontend-PROGRESS.md:508 (пинг №17 — модель названа, строки не поправлены) Предложенное действие: Переписать хвост Ф-44 по D39.144: «подписывается ВЕСЬ банк одним ОК (= resume); гейт полноты снят, pending_decisions/complete информационные; пер-термная масс-подпись НЕ строится». Якорь mining.go:201 заменить на mining.go:202-207. Разрушающее: True ⚠ Опровергатель поправил действие: Содержание правки верно; уточнить якорь: тело флага занимает mining.go:202-207 (строка 202 — заголовок коммента с ссылкой на D39.144), а не «201». ⚠ Не переписывать Ф-44 в «строка закрыта»: пинг №17 (frontend-PROGRESS.md:508) ратифицирует не закрытие, а ПЕРЕЧТЕНИЕ — «строки Ф-44/Ф-45/Ф-46 при разморозке читать через эту модель: действие подписи = один ОК + опциональные точечные правки». Хвост «экран действия строить при разморозке» остаётся живым. Исполнитель — фронт-сессия (зона заморожена).


N058 · MAJOR · frontend/docs/BACKLOG.md:51 · B-мёртвый-указатель

Срез: Доки замороженной зоны фронта — frontend/**/*.md (10 файлов, 5167 строк) Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): Контракт это выражает (BankDecision: promote с непустым dst либо decline, частичное накопление на сервере), то есть редактирование строки — это отправка решения, а не PATCH. Почему протухло: В каноне 0.9.0 нет ни схемы BankDecision, ни значения promote, ни пути POST …/bank/decisions, ни «частичного накопления»: канон 0.5.0 снёс их и завёл POST /books/{bookId}/bank/corrections со словарём approve/decline, применяемый ВСЁ-ИЛИ-НИЧЕГО. Улика — команда: grep -c 'promote\|BankDecision' docs/architecture/14-api-contract/openapi.yaml; grep -n 'bank/corrections' -A 12 docs/architecture/14-api-contract/openapi.yaml | head -14; grep -n 'снесены путь' docs/architecture/14-api-contract/README.md Улика — вывод: 0 (промоут/BankDecision в каноне отсутствуют) /books/{bookId}/bank/corrections: post: operationId: applyBankCorrections «The per-term half of the bank model: signing is ONE act over the whole bank (resumeRun), and what exists per term is a CORRECTION — approve a rendering, or decline a surface … The whole document is applied as one act, all or nothing» README.md:75: ⚠ 0.5.0 ломающий по построению: снесены путь POST …/bank/decisions и схема BankDecision Положительный контроль: grep -c 'decline' docs/architecture/14-api-contract/openapi.yaml → 17 (инструмент в этой области находит словарь решений; ноль по promote/BankDecision — не опечатка грепа) Другие носители того же факта: frontend/docs/api-contract/openapi.yaml:1149-1169 (зеркало 0.2.3, отстаёт ратифицированно), frontend/src/api/schema.ts:853-864, frontend/src/mock/handlers.ts:195 Предложенное действие: Заменить в Ф-45 «BankDecision: promote/decline, частичное накопление» на «POST /books/{bookId}/bank/corrections, BankCorrection: approve/decline, всё-или-ничего, превью перед сохранением, эффект — на СЛЕДУЮЩЕМ прогоне». Разрушающее: True ⚠ Опровергатель поправил действие: Текст замены верен, но ОСТАЛЬНЫЕ НОСИТЕЛИ ПРАВИТЬ НЕЛЬЗЯ, и это важнее самой правки: (1) frontend/docs/api-contract/openapi.yaml — не «отстающий док», а РАТИФИЦИРОВАННО замороженное зеркало (D39.142 п.5; 14-api-contract/README.md:102), синк 0.2.3→0.9.0 — первое касание зоны при разморозке, одним актом с перегенерацией типов; (2) frontend/src/api/schema.ts — ГЕНЕРИРУЕМЫЙ файл (openapi-typescript из зеркала, FRONTEND_PLAN.md:125): править руками = завести четвёртую точку истины, он переедет сам вместе с зеркалом; (3) frontend/src/mock/handlers.ts — код замороженной зоны. Единственное, что стоит сделать сейчас: строка в frontend/docs/frontend-PROGRESS.md, что Ф-45 читается через bank/corrections (approve/decline, всё-или-ничего, превью).


N059 · MAJOR · frontend/docs/BACKLOG.md:62 · C-исполненное-как-открытое

Срез: Доки замороженной зоны фронта — frontend/**/*.md (10 файлов, 5167 строк) Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): Живой канал принадлежит ПРОГОНУ (GET /runs/{runId}/events), а книга в uploading/parsing прогона не имеет вовсе: значит «разбор закончился» не приходит ничем. Почему протухло: Канон 0.9.0 перевесил поток на КНИГУ: GET /books/{bookId}/events, и его описание прямо называет причину — «a book is received and cut into chapters before any run exists, and those minutes are what a user watches». Пути /runs/{runId}/events в каноне больше нет. Улика — команда: grep -n '^ /runs/{runId}/events:\|^ /books/{bookId}/events:' docs/architecture/14-api-contract/openapi.yaml; sed -n '619,634p' docs/architecture/14-api-contract/openapi.yaml Улика — вывод: 619: /books/{bookId}/events: (пути /runs/{runId}/events в каноне НЕТ) 623: get: 625: operationId: streamBookEvents 626: summary: Live events of a book (SSE). 628: text/event-stream, on the BOOK and not on a run: a book is received and cut into chapters 629: before any run exists, and those minutes are what a user watches. Положительный контроль: grep -n '^ /runs/{runId}' канона → /runs/{runId}: · /runs/{runId}/stop: · /runs/{runId}/resume: (греп по путям /runs/ в этом файле находит три штуки — отсутствие /runs/{runId}/events не артефакт шаблона) Другие носители того же факта: frontend/docs/frontend-PROGRESS.md:495 (пинг №17 п.1) Предложенное действие: Пере-диспозиционировать Ф-56: опрос intakePollMs = 3000 в src/api/queries.ts + useIntakeEnd.ts СНОСЯТСЯ при разморозке, подписка переезжает на GET /books/{bookId}/events. Разрушающее: True ⚠ Опровергатель поправил действие: Действие верно, но формулировка «опрос СНОСИТСЯ» слишком категорична для строки бэклога: intakePollMs обслуживает ДВА чтения (queries.ts:171 библиотека и :178 карточка книги), и снос — не одна строка, а переезд обоих на подписку плюс useIntakeEnd.ts. Записывать так: «Ф-56 ОТВЕЧЕНА каноном (Б-6а, GET /books/{bookId}/events); опрос остаётся легитимным лечением до синка зеркала; при разморозке — переезд обоих чтений (queries.ts:171,178) и useIntakeEnd.ts на поток». Исполнитель — фронт-сессия.


N060 · MAJOR · frontend/docs/BACKLOG.md:63 · C-исполненное-как-открытое

Срез: Доки замороженной зоны фронта — frontend/**/*.md (10 файлов, 5167 строк) Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): Кода на замену немного (одна ручка + чтение вместо константы), но ручки в контракте нет: POST /books принимает любой LangCode, а отказ по неподдерживаемой паре не выражен ни одним классом ответа. Почему протухло: Ручка построена: канон 0.9.0 даёт GET /capabilities с обязательным language_pairs («Every pair the deployment knows about, unavailable ones included»). Ф-57 держит как открытую развилку то, что контракт уже решил. Улика — команда: awk '/^ Capabilities:/{f=1} f{print NR": "$0; c++} c>26{exit}' docs/architecture/14-api-contract/openapi.yaml Улика — вывод: 1286: Capabilities: 1290: required: 1291: - contract_version 1292: - language_pairs 1293: - intake_enabled 1294: - intake_max_bytes 1295: - export_formats 1296: - bank_corrections_enabled 1297: - page_size_default 1306: language_pairs: 1309: Every pair the deployment knows about, unavailable ones included: "absent" and "listed as unavailable" are different facts to a user waiting for one. Другие носители того же факта: frontend/docs/frontend-PROGRESS.md:496 (пинг №17 п.2 — предсказано, носитель не поправлен) Предложенное действие: Пере-диспозиционировать Ф-57 в работу первого касания: «src/showcase/languages.ts перестаёт быть константой и становится чтением GET /capabilities.language_pairs» — продуктовой развилки больше нет. Разрушающее: True ⚠ Опровергатель поправил действие: Находка верна, но список носителей НЕПОЛОН — есть ТРЕТИЙ, и он в коде: докблок frontend/src/showcase/languages.ts:3-8 дословно повторяет ту же протухшую посылку («the contract has no handle for "what this deployment can translate" … the gap is written down rather than papered over (BACKLOG Ф-57: a handle in the contract)»). Комментарий переедет вместе с заменой константы на чтение GET /capabilities, но в диспозицию его назвать надо, иначе при разморозке поправят строку бэклога и оставят ложь в коде. Исполнитель — фронт-сессия первого касания.


N061 · MAJOR · frontend/docs/BACKLOG.md:68 · C-исполненное-как-открытое

Срез: Доки замороженной зоны фронта — frontend/**/*.md (10 файлов, 5167 строк) Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): POST /books кладёт название один раз (рукой или из имени файла, 0.2.3), дальше только чтения: GET /books/{bookId} и ни одного PATCH/PUT. … Пока её нет, ошибку в названии лечит только удаление и повторная загрузка, а ручки удаления в контракте тоже нет. Почему протухло: Канон 0.9.0 несёт и PATCH /books/{bookId} (merge patch, только title, 409 при живом прогоне), и DELETE /books/{bookId} (204, 409 при живом прогоне). Обе половины утверждения ложны. Улика — команда: sed -n '281,325p' docs/architecture/14-api-contract/openapi.yaml | grep -n 'patch:\|delete:\|operationId\|summary' Улика — вывод: patch: operationId: updateBook summary: Rename a book. «Merge patch (RFC 7386). Only title may be changed. … Accepted while a run is live» delete: operationId: deleteBook summary: Delete a book. «Removes the book and everything derived from it … 409 while a run is live — stop it first.» Другие носители того же факта: frontend/docs/frontend-PROGRESS.md:498 (пинг №17 п.4) Предложенное действие: Закрыть Ф-62 как «отвечена каноном (Б-5/0.4.0+)», оставив ЗОННЫЙ хвост: построить экраны переименования и удаления при разморозке; жанр из формы снять (Б-23). Разрушающее: True ⚠ Опровергатель поправил действие: Действие верно по существу. Одно уточнение: «жанр из формы снять (Б-23)» — это НЕ хвост Ф-62, а отдельное слово владельца (D39.136 / пинг №17 п.4: «жанр из продукта УХОДИТ совсем»), и вешать его хвостом на строку про переименование значит спрятать самостоятельное решение внутри чужой строки — завести отдельной зонной строкой при разморозке. Исполнитель — фронт-сессия (зона заморожена, D39.147).


N062 · MAJOR · frontend/docs/FRONTEND_PLAN.md:692 · D-противоречие-ратифицированному

Срез: Доки замороженной зоны фронта — frontend/**/*.md (10 файлов, 5167 строк) Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): 1. Прогресс — пофазный, не «готово N из M». Юнит становится done только когда есть и черновые строки всех членов, и строка редактуры … Тип прогресса — две пары { done, total } (черновик ∥ редактура), а не одно число. Почему протухло: Ратифицированный канон 0.9.0 кладёт на провод ОДНУ пару {done,total}, считаемую в ГЛАВАХ по всем проходам, плюс открытую строку stage. Клиент, написанный по этому пункту, спроектирует тип, которого контракт не отдаёт. Улика — команда: sed -n '1396,1414p' docs/architecture/14-api-contract/openapi.yaml Улика — вывод: required: [done, total, stage, eta_seconds] properties: done: description: Chapters of this run's own work that are finished. total: description: Chapters of work this run bought, counted across the passes it owes. stage: description: | What the run is doing NOW — the caption beside the bar, phrased by the client. … Today's deployments answer drafting or editing Другие носители того же факта: frontend/docs/STACK_DECISIONS.md:249, frontend/docs/FRONTEND_SESSION_PROMPT.md:437 Предложенное действие: Во всех ТРЁХ носителях заменить «две пары {done,total} (черновик ∥ редактура)» на «одна пара {done,total} в ГЛАВАХ за все проходы + открытая строка stage; фазы с провода сняты (Б-0/D39.138, канон ≥0.3.0)». Разрушающее: True ⚠ Опровергатель поправил действие: Формулировка замены верна по существу, но: (а) третий носитель FRONTEND_SESSION_PROMPT.md §9 стоит под ⚠-баннером, который перечисляет ЖИВЫЕ секции — §9 в их числе НЕ назван; правку туда вносить можно, но не как в живое задание, а строкой-эрратой под тем же баннером; (б) ⚠ ЗЕРКАЛО frontend/docs/api-contract/openapi.yaml НЕ ТРОГАТЬ: его отставание на 0.2.3 РАТИФИЦИРОВАНО на время фриза (D39.142 п.5; docs/architecture/14-api-contract/README.md:102 — «зонное зеркало фронта стоит на 0.2.3: при разморозке фронт платит переход целиком»), и правка зеркала врозь с каноном = вторая точка правки; (в) исполнитель — фронт-сессия первого касания (зона заморожена, D39.147), заказ уже стоит в пинге №17 аддендум п.1.


N063 · MAJOR · frontend/docs/STACK_DECISIONS.md:245 · C-исполненное-как-открытое

Срез: Доки замороженной зоны фронта — frontend/**/*.md (10 файлов, 5167 строк) Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): ## 8. Что придётся достроить в движке

Через оркестратора, в зону backend/, в порядке критичности. Обосновано разбором кода. Почему протухло: Все ЧЕТЫРЕ пункта («пофазный прогресс», «персист манифеста глав и чанков», «JSON-выход таблицы подписи банка», «приём внешнего TRACEPARENT в cmd/tmctl») в движке ПОСТРОЕНЫ. Раздел учит сессию, что этих поверхностей нет и их надо заказывать через оркестратора. Улика — команда: sed -n '9,13p' backend/internal/pipeline/artifact.go; sed -n '17,18p' backend/internal/pipeline/contractblockers_test.go; grep -n 'TM_TRACE_ID' backend/cmd/tmctl/main.go | head -2 Улика — вывод: artifact.go:9-11: «the sidecars beside the project DB … the chapter/chunk manifest (row 100), the machine bank-stop table (row 101), the bank export (row 125)» contractblockers_test.go:17-18: «the five engine surfaces the API contract is blocked on — per-phase progress (backlog row 99), the persisted chapter/chunk manifest (100), the machine bank-stop table (101), the bank export (125)…» + TestPhaseProgressSplitsTheWaves main.go:163: v := os.Getenv("TM_TRACE_ID") Предложенное действие: Заменить §8 целиком на строку-указатель: «все четыре поверхности построены (манифест/bank-stop/bank-export — D39.122, pipeline/artifact.go; трасса — TM_TRACE_ID, cmd/tmctl/main.go:163); актуальный список долгов движка — единый бэклог docs/PROGRESS.md». ⚠ Уже названо пингом №17 (frontend-PROGRESS.md:504 п.2) — носитель не поправлен. Разрушающее: True ⚠ Опровергатель поправил действие: Действие «заменить §8 ЦЕЛИКОМ» НЕВЕРНО в двух местах. (1) §8 содержит не только четыре пункта: ниже них лежат ⚠-абзацы «Подпись термина — это НЕ UPDATE строки» (ReplaceBank жив — backend/internal/store/glossary.go:129; seedGlossary жив — backend/internal/membank/memseed.go:21), «Выравнивание колонок достраивать не нужно», «Спаны внутри текста не делаем (решение владельца 02.08)» и «После апгрейда бинарника движка…». Снос всего §8 унёс бы живые ратифицированные предупреждения. Правится только НУМЕРОВАННЫЙ список из четырёх пунктов. (2) Предложенный текст-замена сам ставит МЁРТВЫЙ указатель: «bank-stop — D39.122, pipeline/artifact.go». Машинная (JSON) таблица подписи БЫЛА построена D39.122 (коммит 0e69bc1) и СНЕСЕНА 27.08 паком шва (d1eb8a9, D39.158) как не имеющая читателя ни в одной зоне — см. backend/docs/SEAM_PACK_FINDINGS.md:1415 («Контрольный греп bank-stop.json|TableJSONPath|… по *.go → 0»). Сегодня существует только человеческий .bank-stop.txt (mining.go:370), и он пишется ОБЫЧНЫМ os.WriteFile (mining.go:198), т.е. в список writeFileAtomic не входит вовсе. Комментарий artifact.go:11, которым опровергаемая находка доказывает существование поверхности, сам протух — это отдельная находка ЗОНЫ БЭКЕНДА, а не улика для фронта. Верная замена п.3: «машинная таблица подписи была построена (row 101, D39.122) и снесена как беспотребительская (D39.158); фронту её заменяет проекция платформы GET /books/{bookId}/bank + движковый экспорт банка <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 «при разморозке обновить все три». Единственное, что здесь принадлежит оркестратору сегодня, — ⚠-баннер/запись в зонном журнале.


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, живой остаток). Разрушающее: True ⚠ Опровергатель поправил действие: Не «заменить абзац 251253 на новую формулировку про P7» — это завело бы ВТОРОЙ носитель того же факта в одном файле, ровно тот класс, из-за которого он и разошёлся. Абзац 251253 снести и на его месте оставить одну отсылку вверх («проверка исполнением — см. врезку P7 выше»), а сам факт держать в единственном месте (строки 187189). Абзац 255258 не сносить: его живой остаток — автомат самолечения — по регистру PD-201 действительно open; пере-написать только зачин «движковый migrate ПРИДЁТ с машиноразличимой ошибкой» на «форма ошибки финализирована (D39.134, schema_mismatch found=N expected=M); не построен автомат».


N066 · MAJOR · platform/docs/PLATFORM_DIRECTION.md:159 · C-исполненное-как-открытое

Срез: Доки зоны платформы: platform/README.md · platform/deploy/README.md · platform/docs/{ENGINEERING_STANDARDS,PLATFORM_DIRECTION,STACK_DECISIONS}.md · platform/docs/p8-review/* · platform/docs/p9/* Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока):НЕ ИСПОЛНЕНО на 14.08, срок «до первого хендлера» пройден — см. баннер статуса выше (PD-44) Почему протухло: sqlc ВЗЯТ и заленджен 29.08 (D39.172: «Пак sqlc принят и заленджен: 40 запросов пяти файлов pgstore на типизированный слой, пин 1.31.1, sqlc diff в make check»; коммит 63fcee5). В дереве лежат platform/sqlc.yaml, internal/pgstore/queries/{credits,identity,idempotency,sessions}.sql (ровно 40 -- name:), генерённые *.sql.go, пин SQLC_VERSION := 1.31.1 в tools-check и цель sqlc-check внутри check. Ратифицированный документ направления при этом объявляет инструмент неисполненным, а его верхний баннер («ЧИТАТЬ ПРЕЖДЕ ВСЕГО») — работой, ожидающей отдельной сессии («Носитель работы — BACKLOG.md П-19»). Побочно устарела и граница взятия в баннере: «пять файлов… idempotency.go(7) · sessions.go(5) · observe.go(1) = 41 запрос» — в дереве ЧЕТЫРЕ файла запросов, idempotency 6, sessions 6, observe не конвертирован вовсе, итог 40 (D39.172 и П-19 бэклога уже исправлены на 40, направление — нет). Документ обязателен к ссылке в КАЖДОМ промте платформы, поэтому следующая платформенная сессия читает «инструмент не взят» первым делом. Улика — команда: git log -1 --format='%h %ad %s' --date=short -- platform/sqlc.yaml | cut -c1-90; ls platform/internal/pgstore/queries/; grep -rh '^-- name:' platform/internal/pgstore/queries/*.sql | wc -l; grep -n 'sqlc-check\|SQLC_VERSION' platform/Makefile | head -4 Улика — вывод: 63fcee5 2026-08-29 Land the sqlc pack: the coverage answer said almost nothing… credits.sql idempotency.sql identity.sql sessions.sql 40 26:SQLC_VERSION := 1.31.1 30:.PHONY: build vet fmt lint test check tools-check version-check vuln fuzz sqlc-check sqlc-generate 78:sqlc-check: tools-check 99:check: build vet fmt lint sqlc-check Другие носители того же факта: platform/docs/PLATFORM_DIRECTION.md:106-116 (верхний баннер: «БЕРЁМ… отдельной сессией», «= 41 запрос», «Носитель работы — BACKLOG.md П-19»), platform/docs/PLATFORM_DIRECTION.md:145 («sqlc («до первого хендлера»): НЕ ВЗЯТ»), platform/docs/STACK_DECISIONS.md:27 (ВЕРНАЯ редакция: «Взят по решению владельца 20.08», 40 запросов), platform/BACKLOG.md:31 (П-19, уже исправлен D39.172 на «40 КОНВЕРТИРУЕМЫХ») Предложенное действие: Клетку таблицы 159 пере-подписать: «ВЗЯТ и заленджен 29.08 (D39.172, 63fcee5): 40 запросов четырёх файлов pgstore/queries/, пин 1.31.1 в tools-check, sqlc diff в make check». Баннер 106116 свернуть в исторический (с датой) и поправить счёт 41→40; строку 145 «НЕ ВЗЯТ» пометить как историю P7. П-19 в BACKLOG.md — закрыть/пере-скоупить (зона платформы, не мой ход). Разрушающее: True ⚠ Опровергатель поправил действие: Действие верно, но формулировку замены поправить, чтобы она не спорила с ратифицированной нотой: писать «40 запросов, вынесенных в ЧЕТЫРЕ файла pgstore/queries/» (D39.172 говорит «сорок запросов пяти файлов pgstore» — это счёт по исходным Go-файлам, и переписывать его в «четырёх файлов» нельзя, номер бьёт док). Строку 145 «НЕ ВЗЯТ» и блок 136147 не сносить: это записанный ДОВОД отказа P7, улика решения; пометить датой как историю пака. П-19 в platform/BACKLOG.md — зона платформы, не ход оркестратора: пинг, а не правка.


N067 · MAJOR · platform/docs/STACK_DECISIONS.md:621 · B-мёртвый-указатель

Срез: Доки зоны платформы: platform/README.md · platform/deploy/README.md · platform/docs/{ENGINEERING_STANDARDS,PLATFORM_DIRECTION,STACK_DECISIONS}.md · platform/docs/p8-review/* · platform/docs/p9/* Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): /sys/fs/cgroup/user.slice/user-1000.slice/user@1000.service/tm-runs.slice/cgroup.subtree_control. **Почему протухло:** Лечебная команда «самой частой грабли стенда» указывает на путь, которого не существует: tm-runs.sliceпо конвенции имён systemd вложен вtm.slice(срезX-Y.sliceживёт вX.slice/X-Y.slice), поэтому реальный путь — …/user@1000.service/**tm.slice/**tm-runs.slice/cgroup.subtree_control. Оператор получает «No such file or directory» — ровно тот симптом, ради устранения которого правка 29.08 и делалась: тогда tm.sliceзаменили наtm-runs.slice, но нужны ОБА уровня. Зонный журнал уже замерил верную вложенность и записал её (platform/docs/platform-PROGRESS.md:599: «Сам tm-runs.sliceлежит глубже, чем его ищут:user@1000.service/**tm.slice**/tm-runs.slice»), но рецепт с ней не сверили. Цена: TestARunIsBoundedByItsOwnCgroupостаётся красным, а сессия, честно исполнившая рецепт, ищет дефект в своём диффе. **Улика — команда:**B=/sys/fs/cgroup/user.slice/user-1000.slice/user@1000.service; test -e $B/tm-runs.slice/cgroup.subtree_control && echo 'РЕЦЕПТНЫЙ ПУТЬ ЕСТЬ' || echo 'РЕЦЕПТНЫЙ ПУТЬ ОТСУТСТВУЕТ'; test -e $B/tm.slice/tm-runs.slice/cgroup.subtree_control && echo 'РЕАЛЬНЫЙ ПУТЬ (через tm.slice) ЕСТЬ'; cat $B/tm.slice/cgroup.subtree_control; find /sys/fs/cgroup -maxdepth 3 -name '-.slice' -type d | head -3 **Улика — вывод:** РЕЦЕПТНЫЙ ПУТЬ ОТСУТСТВУЕТ РЕАЛЬНЫЙ ПУТЬ (через tm.slice) ЕСТЬ memory pids /sys/fs/cgroup/user.slice/user-1000.slice /sys/fs/cgroup/system.slice/system-modprobe.slice /sys/fs/cgroup/system.slice/system-getty.slice **Положительный контроль:** Тот же find на этом хосте находит РЕАЛЬНЫЕ вложенные срезы (user.slice/user-1000.slice, system.slice/system-modprobe.slice, system.slice/system-getty.slice) — инструмент видит срезы; и .../tm.slice/tm-runs.slice/cgroup.subtree_controlсуществует, то есть отсутствует именно рецептный путь, а не каталог cgroup вообще. **Другие носители того же факта:** platform/docs/p8-review/README.md:23 («опустевшийcgroup.subtree_controlуtm.slice, лечится строкой из рецепта») — называет другой уровень, чем рецепт, на который ссылается, platform/docs/STACK_DECISIONS.md:485 (кандидат-замена условия — «состояние cgroup.subtree_controlсрезаtm-runs.slice», тот же недоуказанный путь) **Предложенное действие:** В строке 621 вставить недостающий уровень: …/user@1000.service/tm.slice/tm-runs.slice/cgroup.subtree_control. Заодно свериться, что tm.slice/cgroup.subtree_controlтоже несёт+memory +pids(на этом хосте несёт) — без родителя лист контроллеров не получит. **Разрушающее:** True **⚠ Опровергатель поправил действие:** Действие верно, но одного вставленного уровня мало: рецепт обязан лечить ОБА уровня, иначе он останется ложно-успокаивающим на свежей машине. Правильная форма —echo "+memory +pids" > …/user@1000.service/tm.slice/cgroup.subtree_control(если пуст), затем то же в…/tm.slice/tm-runs.slice/cgroup.subtree_control: лист не получит контроллеров, которых не раздал родитель. Заодно поправить p8-review/README.md:23— там названtm.sliceтам, где симптом уtm-runs.slice`, и это второй разошедшийся носитель одного факта.


N068 · MINOR · CLAUDE.md:8 · B-мёртвый-указатель

Срез: Корневые онбординг-доки: CLAUDE.md · docs/README.md · docs/STACK.md Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): «бэкенд только под русский» — объявленная АНТИЦЕЛЬ (architecture/09-target-architecture.md §0.1) Почему протухло: Путь написан от docs/, а файл — корневой: от корня репозитория architecture/09-target-architecture.md не существует; реальный путь — docs/architecture/09-target-architecture.md. Ровно этот дефект CLAUDE.md:94 объявляет ИСПРАВЛЕННЫМ своими же словами: «(пути от КОРНЯ репозитория — прежняя редакция писала их от docs/, и от корня они не резолвились)» — правка прошла только по секции онбординга и до шапки не дошла. ⚠ Сам якорь §0.1 ЖИВ — битый только префикс пути. Улика — команда: for p in architecture/09-target-architecture.md docs/architecture/09-target-architecture.md; do [ -e "$p" ] && echo "$p EXISTS" || echo "$p MISSING"; done; grep -n '^## 0.1' docs/architecture/09-target-architecture.md Улика — вывод: architecture/09-target-architecture.md : MISSING --- POSITIVE CONTROL --- docs/architecture/09-target-architecture.md : EXISTS (44039 байт, Aug 30 19:22) --- якорь жив --- 44:## 0.1 Инвариант общности (поправка владельца, 2026-07-13) — НЕСУЩИЙ Положительный контроль: Тот же [ -e ] в том же дереве отвечает EXISTS на docs/architecture/09-target-architecture.md и MISSING на тот же путь без префикса docs/; файл читается и в нём находится §0.1. Значит отрицание — по факту, а не по опечатке в имени. Предложенное действие: Дописать префикс: docs/architecture/09-target-architecture.md §0.1 — привести шапку к норме, которую CLAUDE.md:94 уже объявила действующей. Разрушающее: False


N069 · MINOR · CLAUDE.md:21 · B-мёртвый-указатель

Срез: Корневые онбординг-доки: CLAUDE.md · docs/README.md · docs/STACK.md Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): код и комментарии — по-английски (architecture/12-go-style-notes §1) Почему протухло: Второй уцелевший docs-относительный путь, битый ДВАЖДЫ: нет префикса docs/ И нет расширения .md. Реальный носитель — docs/architecture/12-go-style-notes.md, и правильную форму эта же CLAUDE.md печатает на строке 70 (гардрейл общности). Как и на строке 8, объявленная на строке 94 правка «пути от КОРНЯ» сюда не дошла. ⚠ Якорь §1 ЖИВ — битый только путь. Улика — команда: for p in architecture/12-go-style-notes architecture/12-go-style-notes.md docs/architecture/12-go-style-notes.md; do [ -e "$p" ] && echo "$p EXISTS" || echo "$p MISSING"; done; grep -n '^## §' docs/architecture/12-go-style-notes.md | head -3 Улика — вывод: architecture/12-go-style-notes : MISSING architecture/12-go-style-notes.md : MISSING --- POSITIVE CONTROL --- docs/architecture/12-go-style-notes.md : EXISTS (11134 байт, Aug 30 23:44) --- якорь жив --- 5:## §0. Общность движка (директива владельца 24.07 — жёсткая) 14:## §1. Go-ответы (только то, о чём реально спорим) Положительный контроль: Тот же [ -e ] в том же дереве отвечает EXISTS на docs/architecture/12-go-style-notes.md, и grep в нём находит §1 на строке 14. Отрицание по двум коротким формам получено тем же инструментом. Другие носители того же факта: CLAUDE.md:70 — та же цель, форма ПРАВИЛЬНАЯ (docs/architecture/12-go-style-notes.md) Предложенное действие: Привести к форме строки 70: docs/architecture/12-go-style-notes.md §1. Разрушающее: False


N070 · MINOR · CLAUDE.md:93 · A-дрейф

Срез: Корневые онбординг-доки: CLAUDE.md · docs/README.md · docs/STACK.md Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): ⚠ «Живая голова» целиком в обязательное чтение НЕ входит: это 33 ноты Почему протухло: Число родилось верным и сгнило дрейфом. На 24.08 (ff03dca — коммит, вписавший фразу) голова, как её определяет титул D-лога («голова D39.124+ (подрезка D39.139)»), была D39.124…D39.156 — РОВНО 33 ноты. Сегодня голова — D39.124…D39.184, то есть 61 нота (а всех живых тел в файле 87 — сверх головы ещё D22, D25, D29, D30 и два десятка D39.25…D39.106). Число занижает объём почти вдвое; вывод «целиком не читать» от этого крепче, но сессия, планирующая бюджет чтения по этому числу, ошибётся вдвое. Улика — команда: grep -c '^## D' docs/architecture/05-decisions-log.md; grep -o '^## D39\.[0-9]*' docs/architecture/05-decisions-log.md | sed 's/^## D39\.//' | awk '$1+0>=124' | wc -l; git show ff03dca:docs/architecture/05-decisions-log.md | grep -o '^## D39\.[0-9]*' | sed 's/^## D39\.//' | awk '$1+0>=124' | wc -l Улика — вывод: === TODAY: total ## D headings === 87 === TODAY: head D39.124+ === 61 === на ff03dca (2026-08-24 15:54, коммит фразы) === 33 --- титул D-лога определяет голову одинаково тогда и сейчас: «голова D39.124+ (подрезка D39.139)» --- Предложенное действие: Заменить «это 33 ноты» на форму, которая не гниёт: «это ВСЯ голова D39.124+, сегодня шестой десяток нот». Если число всё же нужно — поставить 61 И завести его шестой сверкой в docs/scripts/counts.py --check (скрипт уже считает голову по трём носителям, но литерал в CLAUDE.md не сторожит — именно поэтому он и уехал на 28 нот). Разрушающее: False


N071 · MINOR · backend/README.md:20 · B-мёртвый-указатель

Срез: Доки зоны бэкенда: backend/README.md + backend/docs/*.md (9 файлов) Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): Ингест (txt/epub, кодировки, ruby) + чанкер: Ingest/IngestEncodedDocument Почему протухло: Экспортированной chunk.Ingest не существует — в пакете живёт неэкспортированная ingest(p string), обёртка над IngestEncoded. Родилась ошибка в ТОМ ЖЕ коммите e33e3be (25.07), который и разэкспортировал функцию, и написал эту строку README: сессия, грепающая chunk.Ingest, получает ноль. Улика — команда: cd backend && grep -rn '^func [Ii]ngest' internal/chunk/*.go; grep -rn 'chunk\.Ingest' --include=*.go . | head -3; git blame -L 20,20 --date=short README.md | cut -c1-60 Улика — вывод: internal/chunk/ingest.go:67:func ingest(p string) (*Document, error) { internal/chunk/ingest.go:77:func IngestEncoded(p, encoding, sourceLang string) (*Document, error) {

internal/pipeline/events.go:577: doc, err := chunk.IngestEncoded(r.Book.SourceFile, ...) internal/pipeline/contractblockers_test.go:381: doc, err := chunk.IngestEncoded(...) internal/pipeline/bookbuild_test.go:29: doc, err := chunk.IngestEncoded(epubPath, "", "")

e33e3be2 (Claude (backend session) 2026-07-25 20) Положительный контроль: Тот же греп в той же области находит живой символ: grep -rn 'chunk\.Ingest' --include=*.go . → три вызова chunk.IngestEncoded (events.go:577, contractblockers_test.go:381, bookbuild_test.go:29). Пустота относится именно к Ingest, а не к инструменту. Предложенное действие: Заменить «Ingest/IngestEncoded» на «IngestEncoded (единственный экспорт; ingest — внутренняя терсная обёртка)». Разрушающее: True ⚠ Опровергатель поправил действие: Действие в целом верно, но формулировка неточна: ingest — не «терсная обёртка», а точка входа по расширению (она разветвляется на ingestTXT/ingestEPUB), и главное — она ТЕСТОВАЯ: вне пакета недостижима, вызывается только из internal/chunk/ingest_test.go. Точнее: «IngestEncodedDocument (единственный экспорт пакета; неэкспортированная ingest — вход по умолчанию для тестов пакета)».


N072 · MINOR · backend/README.md:94 · B-мёртвый-указатель

Срез: Доки зоны бэкенда: backend/README.md + backend/docs/*.md (9 файлов) Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): Известный техдолг (не трогать молча; ЕДИНЫЙ трекер — таблица «Бэклог» в docs/PROGRESS.md, номера строк = она) Почему протухло: README объявляет, что все его «строка N» разрешаются в таблицу «Бэклог», и цитирует тринадцать номеров. Семь из них в таблице отсутствуют — закрыты и по правилу таблицы вынесены: 53, 100, 101, 125, 145, 174, 236 (236 закрыта D39.175, 174 — паком migrate, 100/101/125 — контракт-блокерами). Живы только 49, 49а, 50, 79, 146, 173. Для номеров-провенансов это не техдолг, но обещание разрешимости README даёт всем. Улика — команда: for id in 49 49а 50 53 79 100 101 125 145 146 173 174 236; do printf '%-5s: ' "$id"; grep -qE "^\| *\*{0,2}${id}\*{0,2} *\|" docs/PROGRESS.md && echo ЖИВА || echo 'НЕТ В ТАБЛИЦЕ'; done Улика — вывод: 49 : ЖИВА 49а : ЖИВА 50 : ЖИВА 53 : НЕТ В ТАБЛИЦЕ 79 : ЖИВА 100 : НЕТ В ТАБЛИЦЕ 101 : НЕТ В ТАБЛИЦЕ 125 : НЕТ В ТАБЛИЦЕ 145 : НЕТ В ТАБЛИЦЕ 146 : ЖИВА 173 : ЖИВА 174 : НЕТ В ТАБЛИЦЕ 236 : НЕТ В ТАБЛИЦЕ Положительный контроль: Тот же паттерн в той же области находит 189 существующих ID таблицы (grep -oE '^\| *\*{0,2}[0-9]+[а-я]?\*{0,2} *\|' docs/PROGRESS.md | wc -l → 189), в том числе шесть из тринадцати цитируемых README — значит пустота по остальным семи не артефакт грепа. Предложенное действие: Добавить в шапку секции одну фразу: «номер, которого в таблице нет, — ЗАКРЫТАЯ строка: её тело ушло в D-ноту, ищи грепом номер в 05-decisions-log.md». Это дешевле, чем переписывать 7 провенансов, и лечит класс, а не случай. Разрушающее: False


N073 · MINOR · backend/docs/MONEY_HONESTY_REPORT.md:787 · C-исполненное-как-открытое

Срез: Доки зоны бэкенда: backend/README.md + backend/docs/*.md (9 файлов) Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): Ничего не закоммичено; лендинг за оркестратором. Почему протухло: Заленджено — коммитом bb541a8 (31.08) и принято нотой D39.182. Фраза стоит в секции, которая сама называет себя «§11. Последний абзац — СОСТОЯНИЕ, не план», а docs/PROGRESS.md:299-300 отправляет читателя в этот файл как в долговременный носитель — значит абзац читается как текущее состояние. Тот же файл (строка 2) и записка-план (строка 3) указывают на docs/BACKEND_MONEY_HONESTY_SESSION_PROMPT.md, которого в docs/ нет: промт переехал в docs/archive/prompts/. Улика — команда: git log --format='%h %ad %s' --date=short -1 -- backend/docs/MONEY_HONESTY_REPORT.md | cat; grep -n '^## D39.182' docs/architecture/05-decisions-log.md | cut -c1-120; ls docs/BACKEND_MONEY_HONESTY_SESSION_PROMPT.md; ls docs/archive/prompts/BACKEND_MONEY_HONESTY_SESSION_PROMPT.md Улика — вывод: bb541a8 2026-08-31 Land the disclosure law and its eleven instances: two of the three acceptance blockers were defects inside the cure itself 1733:## D39.182 — БЭКЕНД-ПАК «ДЕНЬГИ И ЧЕСТНОСТЬ ВЫДАЧИ» ПРИНЯТ И ЗАЛЕНДЖЕН ls: cannot access 'docs/BACKEND_MONEY_HONESTY_SESSION_PROMPT.md': No such file or directory docs/archive/prompts/BACKEND_MONEY_HONESTY_SESSION_PROMPT.md Положительный контроль: Указатель проверен на живом образце той же формы: ls docs/ORCHESTRATOR_SESSION_PROMPT.md резолвится (32182 байта), то есть отсутствие относится к файлу, а не к каталогу или команде. Другие носители того же факта: backend/docs/MONEY_HONESTY_PLAN-NOTE.md:3 — тот же мёртвый указатель на промт, backend/docs/MONEY_HONESTY_REPORT.md:2 — тот же указатель, docs/PROGRESS.md:298 — тот же указатель (зона оркестратора) Предложенное действие: Добавить в шапку отчёта (тело по D23.3 не переписывать) дату-баннер: «⚠ Дописано 01.09: пак ЗАЛЕНДЖЕН bb541a8 и принят D39.182; §11 «ничего не закоммичено» — состояние на момент сдачи. Промт переехал в docs/archive/prompts/BACKEND_MONEY_HONESTY_SESSION_PROMPT.mdТо же — в шапку записки-плана. Разрушающее: False


N074 · MINOR · backend/docs/SEAM_FIX3_PLAN.md:1 · F-целиком-историческое

Срез: Доки зоны бэкенда: backend/README.md + backend/docs/*.md (9 файлов) Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): # План фикс3 (промт BACKEND_SEAM_FIX3_SESSION_PROMPT.md)

Комплектность: grep -cE '^### 4\.' docs/BACKEND_SEAM_FIX3_SESSION_PROMPT.md6. Почему протухло: Четыре записки-плана паков шва — чистые списки намерений отработавших сессий. Паки приняты и заленджены (D39.158: «Четыре пака зоны бэкенда приняты и заленджены одним деревом»), а их промты архивированы коммитом 31b96d1 — так что первая строка каждой записки и её команда комплектности указывают в несуществующий файл. Ни один ЖИВОЙ док и ни одна строка кода на эти четыре записки не ссылается: единственные ссылки — из SEAM_PACK_FINDINGS.md и из уже архивированных промтов. ⚠ ВАЖНОЕ ИСКЛЮЧЕНИЕ: SEAM_PACK_FINDINGS.md в архив НЕ кандидат — на неё ссылается ратифицированная нота D39.158 как на доказательную базу. Улика — команда: ls docs/BACKEND_SEAM_*; ls docs/archive/prompts/ | grep -i seam; grep -rn 'SEAM_FIX3_PLAN\|SEAM_FIX2_PLAN\|SEAM_FIX_PLAN\|SEAM_PACK_PLAN' --include='*.md' --include='*.go' . | grep -v '^./backend/docs/SEAM' | grep -v '^./docs/archive/'; sed -n '698p' docs/architecture/05-decisions-log.md | cut -c1-160 Улика — вывод: ls: cannot access 'docs/BACKEND_SEAM_*': No such file or directory BACKEND_SEAM_FIX2_SESSION_PROMPT_2026-08-27.md BACKEND_SEAM_FIX3_SESSION_PROMPT_2026-08-27.md BACKEND_SEAM_FIX_SESSION_PROMPT_2026-08-27.md BACKEND_SEAM_PACK_SESSION_PROMPT_2026-08-27.md --- ссылки из живых доков/кода: (пусто) --- Четыре пака зоны бэкенда приняты и заленджены одним деревом. Механика и доказательная база — backend/docs/SEAM_PACK_FINDINGS.md (вход §26 и §43) Положительный контроль: Инструмент в этой области ссылки находит: тем же грепом видны хиты из SEAM_PACK_FINDINGS.md и из docs/archive/prompts/*, а ls docs/ORCHESTRATOR_SESSION_PROMPT.md подтверждает, что живые промты в docs/ резолвятся — то есть ноль по docs/BACKEND_SEAM_* относится к файлам, а не к команде. Другие носители того же факта: backend/docs/SEAM_FIX_PLAN.md:1 — тот же мёртвый указатель на docs/BACKEND_SEAM_FIX_SESSION_PROMPT.md, backend/docs/SEAM_FIX2_PLAN.md:1,3 — на BACKEND_SEAM_FIX2_SESSION_PROMPT.md, backend/docs/SEAM_PACK_PLAN.md:3 — на docs/BACKEND_SEAM_PACK_SESSION_PROMPT.md Предложенное действие: Вынести ЧЕТЫРЕ записки (SEAM_PACK_PLAN · SEAM_FIX_PLAN · SEAM_FIX2_PLAN · SEAM_FIX3_PLAN) в docs/archive/ рядом с их промтами, с ⚠-баннером «план отработавшего пака, принят D39.158». SEAM_PACK_FINDINGS.md ОСТАВИТЬ на месте — на неё смотрит D39.158; если её всё же двигать, сначала править ноту. Разрушающее: True ⚠ Опровергатель поправил действие: Находка верна, ДЕЙСТВИЕ — нет, в двух местах. (1) Автор объявил SEAM_PACK_FINDINGS.md неприкосновенной, но не заметил, что она же держит ссылки на переносимые файлы: строки 679-680 прямо говорят «Едут вместе с деревом и без них его нельзя принять: backend/docs/SEAM_FIX_PLAN.mdbackend/docs/SEAM_PACK_PLAN.md», плюс :3, :270, :1327. Перенос без правки этих пяти ссылок ПОРОДИТ ровно тот класс дефекта, который находка чинит, — и породит его в доке, куда ратифицированная D39.158 посылает читателя. Значит: либо переносить вместе с обновлением пяти указателей в FINDINGS, либо не переносить вовсе, а поставить каждой записке ⚠-баннер «план отработавшего пака, принят D39.158; промт — docs/archive/prompts/BACKEND_SEAM_*_2026-08-27.md» — второе дешевле и по правилам проекта достаточно: баннер И ЕСТЬ механизм легитимности исторического. (2) Зоны: backend/docs/ — зона БЭКЕНДА, docs/archive/ — зона оркестратора; перенос файлов из чужой зоны оркестратор в одиночку не делает. Это либо пинг бэкенд-сессии, либо явное согласование. Рекомендую минимальный ход: ⚠-баннеры на месте (правит бэкенд-зона), FINDINGS не трогать вовсе.


N075 · MINOR · docs/PROGRESS.md:3 · B-мёртвый-указатель

Срез: ГОЛОВА журнала: CURRENT-STATE и очередь — docs/PROGRESS.md, строки 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/». Разрушающее: True ⚠ Опровергатель поправил действие: Действие верно, и лучше исполнить именно вторую, критериальную его половину: заменить ручной счёт на «ни один пак платформы после P7 этот долг не взял (проверка: grep -rn 'createExport\|getExport' platform/internal/ --include=*.go — пусто)», чтобы число не гнило при каждом следующем лендинге. Если счёт всё-таки оставлять — «шесть паков (P8-FIX · P8-REVIEW · P9 · P10 · P11 · P12), плюс непронумерованный sqlc».


N076 · MINOR · docs/PROGRESS.md:3 · A-дрейф

Срез: ГОЛОВА журнала: CURRENT-STATE и очередь — docs/PROGRESS.md, строки 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 как «развязки нет, но линия не запушена». Разрушающее: True ⚠ Опровергатель поправил действие: Действие верно, и лучше исполнить именно вторую, критериальную его половину: заменить ручной счёт на «ни один пак платформы после P7 этот долг не взял (проверка: grep -rn 'createExport\|getExport' platform/internal/ --include=*.go — пусто)», чтобы число не гнило при каждом следующем лендинге. Если счёт всё-таки оставлять — «шесть паков (P8-FIX · P8-REVIEW · P9 · P10 · P11 · P12), плюс непронумерованный sqlc».


N077 · MINOR · docs/PROGRESS.md:3 · A-дрейф

Срез: ГОЛОВА журнала: CURRENT-STATE и очередь — docs/PROGRESS.md, строки 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 его не взял»), чтобы число не гнило при каждом лендинге. Разрушающее: True ⚠ Опровергатель поправил действие: Действие верно, и лучше исполнить именно вторую, критериальную его половину: заменить ручной счёт на «ни один пак платформы после P7 этот долг не взял (проверка: grep -rn 'createExport\|getExport' platform/internal/ --include=*.go — пусто)», чтобы число не гнило при каждом следующем лендинге. Если счёт всё-таки оставлять — «шесть паков (P8-FIX · P8-REVIEW · P9 · P10 · P11 · P12), плюс непронумерованный sqlc».


N078 · MINOR · docs/PROGRESS.md:15 · C-исполненное-как-открытое

Срез: ГОЛОВА журнала: CURRENT-STATE и очередь — docs/PROGRESS.md, строки 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на длинной книге, с новым гейтом вместо отработавшего. **Разрушающее:** True **⚠ Опровергатель поправил действие:** Действие верно, но сужать надо аккуратнее, чем написано:families_held=0замерен на ДЕСЯТИГЛАВНОЙ книге, а строка требует пере-подтверждения на ДЛИННОЙ — значит вывод «ослаблять гард не нужно» пишется с явной оговоркой «на 10 главах», иначе он сам протухнет на первой длинной книге. Тем же касанием починить указатель гейта «§6 отчёта»: в отчёте холодного прогона §6 — это «Две ловушкиbuild», числа семейств в §9; ставить греп-указатель (families_held` по отчёту), а не номер секции.


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/ — ждёт слова владельца по развилке», чтобы холодная сессия не приняла его за свой. Разрушающее: True ⚠ Опровергатель поправил действие: Переезд ДЕЛАТЬ — оговорка «если развилка не закрыта, не двигать» снята замером: закрыта. Одним коммитом pathspec-формой: (1) git mv промта в docs/archive/prompts/ — проверено, что переезд link-safe: внутри промта НОЛЬ относительных markdown-ссылок (grep -cE '\]\(\.\.?/' → 0, тот же ноль у уже переехавшего money-промта), все пути — репо-корневые в бэктиках, так что норма пере-углубления D39.80 здесь не срабатывает; (2) тем же коммитом починить README:25, где ДВЕ протухшие половины, а не ноль: «развилка … НА ВЛАДЕЛЬЦЕ» → «закрыта словом владельца «не доводить» 31.08, $0.436110 из $0.80 (D39.184)» и «отчёт ждёт лендинга» → «отчёт заленджен, archive/reports/COLDRUN_V16_REPORT_2026-08-31.md». (3) docs/PROGRESS.md:3 правки НЕ требует — переезд делает его утверждение истинным; после коммита сверить это грепом. (4) А вот предложенную находкой правку docs/archive/reports/COLDRUN_V16_REPORT_2026-08-31.md:51 я бы НЕ делал в том же движении: это ТЕЛО архивного отчёта с ревью-шапкой, и оно фиксирует, где промт лежал на момент прогона; конвенция в каталоге и так смешанная (MINIRUN_REPORT_2026-07-25.md:5 цитирует архивный путь, COLDRUN_V16 — docs/). Если оркестратор хочет живой указатель — правка допустима, но она отдельное решение, а не часть переезда.


N085 · MINOR · docs/STACK.md:7 · B-мёртвый-указатель

Срез: Корневые онбординг-доки: CLAUDE.md · docs/README.md · docs/STACK.md Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): configs/pipeline-c1.yaml:36-60 Почему протухло: Блок стадии draft лежит на строках 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)» (ци Другие носители того же факта: 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 Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): Подтверждённые исправлены двумя волнами (коммиты dc6ea8ea32ec2e → фиксы 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 dc6ea8ee861541, a32ec2e214e005; ниже в подзаголовке «Волна 1 (коммит a32ec2e)» → «Волна 1 (коммит e861541)», «Волна 2 (эта)» → «Волна 2 (коммит 214e005)». Внешнее ревью §6 приземлено 9d78385/56670e3 — можно добавить туда же. Разрушающее: True ⚠ Опровергатель поправил действие: Правка нужна, но НЕ той подстановкой, что предложена: находка в одной строке отображает a32ec2e→214e005, а строкой ниже тот же токен a32ec2e→e861541 — это несогласованно и спорит с архивным носителем. Верное отображение по СОДЕРЖАНИЮ коммитов: dc6ea8e = каркас 7954325 («Add Phase 0 backend skeleton…»), a32ec2e = волна 1 e861541 (subject дословно повторяет перечень §6 волны 1), волна 2 = 214e005 (blame: этим коммитом строка и написана, его же подзаголовок «Волна 2 (эта)»). Внешнее ревью — 56670e3/9d78385. Архивную копию docs/archive/PROGRESS-2026-07-04-10.md:164-165 НЕ править (замороженная хроника); если её мёртвые хэши мешают — только пометка, не переписывание. Рекомендую в §6 добавить полсловом «(исходные хэши в отчёте были указаны ошибочно; в истории их нет)», чтобы следующий ревизор не искал переписанную историю.


N093 · MINOR · docs/architecture/03-implementation-notes.md:267 · C-исполненное-как-открытое

Срез: Заметки реализации и Go-нормы — docs/architecture/03-implementation-notes.md · docs/architecture/12-go-style-notes.md Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): D3 — изоляция канала B в failover не принуждается кодом (пара primary/fallback задаётся вызывающим); при подключении failover (Фаза 2) нужен структурный запрет Anthropic в канале B, иначе пустой ответ local-abliterated молча уйдёт в Anthropic. Все три — Фаза 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-op Положительный контроль: grep -n 'provider:' backend/configs/models.yaml | awk -F': ' '{print $3}' | sort -u → deepseek, gemini, kimi, local, mistral, openai, xai, zai (восемь провайдеров реально находятся тем же грепом; пустота по anthropic — отсутствие провайдера, не поломка команды) Предложенное действие: Дописать в конец пункта: «⚠ ЗАКРЫТО паком-17 (D39.25/D39.26): маршрутизация по лейблам запрещает выдачу клиента структурно — отказ на загрузке (config/pipeline.go:731) и на вызове (pipeline.clientFor, runner.go:573-578); Anthropic из стека убран 04.07 (configs/models.yaml), объект запрета отсутствует». Тот же абзац: D2 (per-chunk skip+flag) тоже исполнен — дисп-классификатор DispOK/FlagReason + exit 2 «completed-with-flags». Разрушающее: False


N094 · MINOR · docs/architecture/04-unhappy-paths.md:120 · C-исполненное-как-открытое

Срез: Ранние арх-доки (кандидаты в архив): docs/architecture/01-decisions.md · 02-mvp-plan.md · 04-unhappy-paths.md · 06-memory-risk-registry.md Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): равномерные гейты по всем главам + «паспорт качества главы» в отчёте — продуктовая фича доверия. → в бэклог продукта. Почему протухло: Паспорт качества главы ПОСТРОЕН и отдаётся tmctl status: тип pipeline.ChapterPassport (internal/pipeline/status.go:157-159, «the per-chapter quality passport (D12): unit counts, the worst flag reason, a pass|attention|fail verdict (exp07 chapter rule) and the chapter's spend»), поле Chapters []ChapterPassport в отчёте (status.go:313), CLI-дашборд описан в main.go:389. §10 отправляет его «в бэклог продукта», хотя это уже поставленный механизм. Ни один из двух ⚠-баннеров дока это не покрывает: они оговаривают колонки «Фаза/Статус» ТАБЛИЦ, а §10 — прозаические буллеты без колонок. Улика — команда: cd /home/ubuntu/projects/textmachine-main && sed -n '120p' docs/architecture/04-unhappy-paths.md | cut -c1-160 && sed -n '157,159p' backend/internal/pipeline/status.go && grep -n 'per-chapter quality passports' backend/cmd/tmctl/main.go && grep -n 'Chapters \[\]ChapterPassport' backend/internal/pipeline/status.go && grep -n 'паспорта глав' backend/README.md | cut -c1-90 Улика — вывод: - «Проклятие 300-й главы» … + «паспорт качества главы» в отчёте — продуктовая фича доверия. → в бэклог продукта. 157:// ChapterPassport is the per-chapter quality passport (D12): unit counts, the worst flag 158:// reason, a pass|attention|fail verdict (exp07 chapter rule) and the chapter's spend. 159:type ChapterPassport struct { 389:// dashboard (unit counts, per-chapter quality passports, money, secondary ETA). BOTH modes exit 2 313: Chapters []ChapterPassport json:"chapters" 11:| cmd/tmctl | CLI: translate / status (read-only N/M+паспорта глав+деньги, --jsonДругие носители того же факта: docs/architecture/02-mvp-plan.md:34 (тот же факт как ПОСТРОЕННЫЙ: «tmctl export … паспорт-rollup» в составе D29.1) — то есть два ранних дока уже расходятся в статусе одного механизма Предложенное действие: В §10 заменить «→ в бэклог продукта» на «→ ПОСТРОЕНО: pipeline.ChapterPassport (status.go:157), поверхность tmctl status --json + rollup в tmctl export (D29.1)». Строку бэклога не заводить — предмет закрыт. Разрушающее: False


N095 · MINOR · docs/architecture/09-target-architecture.md:12 · A-дрейф

Срез: Целевая архитектура — docs/architecture/09-target-architecture.md Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока):С 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). Лучшее лечение — внести объёмный потолок, гейт схемы и закон раскрытия В САМУ таблицу слоёв и снять оговорку целиком. Разрушающее: True ⚠ Опровергатель поправил действие: Действие верно; предпочтительна названная автором «лучшая» ветка — внести объёмный потолок, гейт схемы и закон раскрытия В САМУ таблицу слоёв и снять оговорку целиком, иначе числительное будет протухать при каждом следующем лендинге. Если правится только текст — не заменять «двух» на «трёх» (то же протухнет), а писать «ряда» + ссылку на реестр нот.


N096 · MINOR · docs/architecture/09-target-architecture.md:178 · B-мёртвый-указатель

Срез: Целевая архитектура — docs/architecture/09-target-architecture.md Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): | 5 Качество | H5-no-in-loop-quality-signal, H14-inversion-guard (D37 §2г), H12-cross-boundary-coherence-untested | Почему протухло: Токен H14-inversion-guard в ледджере, на который §3 явно ссылается заголовком раздела, не встречается ни разу: реальный идентификатор — H14-D38.4-tail-inversion-guard. Из восемнадцати проверенных ID таблицы §3 это ЕДИНСТВЕННЫЙ, который не грепается; остальные семнадцать резолвятся точно. Улика — команда: grep -c 'H14-inversion-guard' docs/archive/architecture/08-sync-audit-ledger.md; grep -n '^### H14' docs/archive/architecture/08-sync-audit-ledger.md | cut -c1-110; grep -c 'H12-cross-boundary-coherence-untested' docs/archive/architecture/08-sync-audit-ledger.md**Улика — вывод:** 0 459:###H14-D38.4-tail-inversion-guard— **WORKAROUND_NOT_ROOT** / MEDIUM · верификатор: CONF 1 **Положительный контроль:** grep -c 'H12-cross-boundary-coherence-untested' → 1 в том же файле: греп по ледджеру работает и соседние ID из той же строки таблицы находит; ноль по H14-inversion-guard — отсутствие цели, не поломка инструмента. **Предложенное действие:** ЗаменитьH14-inversion-guardнаH14-D38.4-tail-inversion-guard` (точный заголовок ледджера, строка 459). Разрушающее: True ⚠ Опровергатель поправил действие: Действие верно и минимально-разрушительно (замена одного токена). Уточнение к сопроводительной записи: писать не «цель переименована», а «ссылка была битой с рождения (оба написания пришли коммитом 225abc0)» — иначе следующая сессия пойдёт искать несуществующее переименование.


N097 · MINOR · docs/architecture/10-prompt-architecture.md:8 · B-мёртвый-указатель

Срез: Архитектура промптов — docs/architecture/10-prompt-architecture.md Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): арх-ресета была размазана по 08-sync-audit-ledger (дорожка L4), 09-target-architecture (§0.1, §2 слой 2), D-логу (D30.2, D39 п.7, D39.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 //.md (pack-15), so a pair is a DIRECTORY, not a config entry. Drop the map (put the pair calibration in configs/pairs/.yaml); set prompt_override: only if this stage deliberately runs a variant", st.Name) book.go:378: func (b Book) LangPair() string { grep '^ prompts:' backend/configs/.yaml → пусто (ни один боевой конфиг карту не несёт) Положительный контроль: Пустой грep ^\s*prompts: по backend/configs/.yaml подстрахован положительным контролем в том же наборе файлов: grep -rn 'prompt_override' backend/configs/*.yaml → configs/pipeline-c1.yaml:67, configs/pipeline-c2.yaml:42, configs/pipeline-c2.yaml:43 — грep по этим yaml находит промпт-ключи, значит пустота по prompts: содержательна. Предложенное действие: В ячейке §1 заменить «конфиг pair-keyed prompts: {zh-ru: …}» на «пар-каталог prompts/<пара>/<роль>.md (конвенционный резолв, пак-15/D39.23; карта prompts: в конфиге РЕТАЙРНУТА и падает громко — internal/config/pipeline.go:939) + калибровка пары configs/pairs/<пара>.yaml», сохранив «Book.LangPair() + fail-loud на чужую пару» — обе половины проверены и живы. Разрушающее: True ⚠ Опровергатель поправил действие: Находка верна фактически, но действие СЛИШКОМ КРУПНОЕ, и это важно: шапка того же дока (строка 5, ⚠ СТАТУС-АПДЕЙТ 25.07) УЖЕ несёт правку дословно — «Pair-keyed промпты → конвенционный резолв prompts/<пара>/<роль>.md + конфиг-слой пары configs/pairs/ (пак-15, D39.23)», — а по норме «баннер прежде содержимого» читатель её видит первой. Полный перепис ячейки создаёт ТРЕТИЙ носитель того же факта в одном доке (шапка + §1 + §4 п.2), а это ровно тот механизм, из которого рождается следующее расхождение. Минимальная правка: пометить в ячейке форму как историю пака-1 и сослаться на шапку — в стиле, который док уже применяет к §2 («⚠ Пути таблицы устарели механически…»), например «(форма пака-1; РЕТАЙРНУТА паком-15 — см. шапку)». Файл-лайн pipeline.go:939 в док не вносить: анкер сгниёт; при нужде цитировать текст ошибки «the pair-keyed prompts: map is retired».


N099 · MINOR · docs/architecture/10-prompt-architecture.md:18 · B-мёртвый-указатель

Срез: Архитектура промптов — docs/architecture/10-prompt-architecture.md Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): | D-лог D30.2 (05:366 — «механизм реверстки — открытый вопрос») · аудит L1-*/H3 · research/19 §C · exp15 Q4 | Почему протухло: Строка 366 живого D-лога сегодня пустая и лежит внутри эры D39.135 (приёмка фронта S4, 15.08) — то есть на 250 строк мимо цели и в чужой эре. Цитируемый текст живёт на 05:116, в теле D30 п.2. Указатель дрейфанул от роста файла (1823 строки), автор не ошибался. Улика — команда: sed -n '366p' docs/architecture/05-decisions-log.md; grep -n 'реверстк' docs/architecture/05-decisions-log.md; grep -n '^## D30\.' docs/architecture/05-decisions-log.md | head -1; grep -n '^## D30\b' docs/architecture/05-decisions-log.md Улика — вывод: sed 366p → (пустая строка; ближайший заголовок выше — 05:365 «## D39.135 — ПРИЁМКА ФРОНТА S4 ПРИНЯТА И ЗАЛЕНДЕНА (267aa35) … (15.08)») grep реверстк → 116: 2. Reflow-вёрстка: снять «Сохраняй разбивку на абзацы» из zh→ru промптов (translator.md:9, editor.md:8 — дословно подтверждено) … Механизм реверстки (в промпте редактора vs отдельный пасс) — открытый вопрос пере-прогона grep '^## D30' → 111: ## D30. Приёмка exp12 + флип D1 (моно→билингв) … Положительный контроль: Отрицательный результат («на 366 цели нет») предъявлен вместе с положительным: тот же grep по отличительному токену «реверстк» в ТОМ ЖЕ файле даёт хит на строке 116 — инструмент в этой области находит искомое, пустота на 366 не артефакт грепа. Предложенное действие: Заменить 05:366 на 05:116 (тело D30, п.2). Ещё устойчивее — снять номер строки совсем и оставить «D-лог D30 п.2», как предписывает дисциплина чтения журнала (номер грепается: grep -n '^## D30' ): подномер D30.2 собственного тела не имеет, а номер строки будет дрейфовать и дальше. Разрушающее: False


N100 · MINOR · docs/architecture/10-prompt-architecture.md:33 · A-дрейф

Срез: Архитектура промптов — docs/architecture/10-prompt-architecture.md Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): Правила, сидящие В ТЕЛЕ промтов (кандидаты на извлечение в пакет пары после exp15): русское тире-оформление диалога; «пиши живым русским»; паратаксис→гипотаксис-блок; чэнъюй-политика; few-shot-примеры (структурные, по-русски). Почему протухло: Инвентарь пар-специфики в телах промтов неполон с 24.07: паком-13 в ОБА боевых промпта вписан блок «Единицы и приёмы (общие для zh→ru)» с зашитыми китайскими токенами и конверсионными таблицами (时辰 = 2 часа, 六成 = 60%, стихи — по смыслу), а 01.08 в editor.md добавлено правило авторского рефрена. Это самая явно данно-образная пар-специфика из всех перечисленных (числовые таблицы конверсии), и она уже имеет носителя-данные на детекторной стороне — langpack configs/langpacks/zh-ru/dc-checkers.txt — и отдельные repair-промпты repair/dc1_time_units.md / repair/dc1_fractional.md. В списке кандидатов на извлечение её нет, значит план извлечения недосчитывает свой главный лот. Улика — команда: sed -n '12,15p' backend/prompts/zh-ru/translator.md; sed -n '10p' backend/prompts/zh-ru/editor.md; git log -S'时辰' --format='%h %ad %s' --date=short -- backend/prompts/zh-ru/translator.md backend/prompts/translator.md; grep -rln '时辰' backend/configs backend/prompts Улика — вывод: translator.md:12-15: Единицы и приёмы (общие для zh→ru):

  • МЕРЫ переводи в привычные читателю единицы: китайский 时辰 = 2 часа (三个时辰 ≈ шесть часов, 半个时辰 ≈ час) … Доля 成 — это десятые (六成 = 60%, 六成六 = 66%), а НЕ десятичная дробь «6,6».
  • СТИХИ и названия классики передавай ПО СМЫСЛУ … editor.md:10: - Единицы и приёмы (общие для zh→ru): МЕРЫ приводи к привычным читателю единицам (时辰 = 2 часа …); СТИХИ и названия классики — по смыслу … git log -S时辰 → 2f91b04 2026-07-24 Land pack-13 release QA … grep -rln 时辰 → backend/configs/langpacks/zh-ru/dc-checkers.txt, backend/prompts/zh-ru/translator.md, backend/prompts/zh-ru/editor.md, backend/prompts/zh-ru/translator-banknote.md, backend/prompts/zh-ru/rep Другие носители того же факта: docs/architecture/09-target-architecture.md:73-76 — описывает содержимое тех же двух промптов («подстрочник + слабая однострочка про вёрстку» / «4 мандатами сразу — верность, стиль, глоссарий, дискурс-reflow») и блок единиц/мер тоже не знает: тот же пробел в живом арх-доке Предложенное действие: Дописать в список (аддитивно, без сноса существующих пунктов): «блок «Единицы и приёмы (общие для zh→ru)» — конверсии мер 时辰/成 и правило «стихи по смыслу», сидит В ОБОИХ боевых промптах (translator.md:12-15, editor.md:10, пак-13 от 24.07); у детекторной половины этого класса носитель-данные УЖЕ есть — configs/langpacks/zh-ru/dc-checkers.txt + prompts/zh-ru/repair/dc1_time_units.md/dc1_fractional.md, что делает его первым кандидатом на извлечение; авторский рефрен (editor.md:6, 01.08)». Разрушающее: False

N101 · MINOR · docs/architecture/10-prompt-architecture.md:41 · C-исполненное-как-открытое

Срез: Архитектура промптов — docs/architecture/10-prompt-architecture.md Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): Гейты на стройку: exp15 (какие правила реально несут) + research/18 §D-3 (форма пакета). Почему протухло: exp15 как гейт ПАЛ: эксперимент исполнен и залендён 25.07 в редакции REV.2 (D39.7), потрачено $12.79. §4 п.2 того же дока это уже фиксирует («exp15 ЗАВЕРШЁН») и называет новую форсинг-функцию — ja→ru-реплику; §1 (строка 17) и §3 (строка 41) остались с прежней рамкой и держат построенный гейт как несданный. Док спорит сам с собой в трёх строках. Улика — команда: sed -n '6,8p' docs/experiments/15-segmentation-empirics.md; sed -n '17p;41p;45p' docs/architecture/10-prompt-architecture.md | cut -c1-190 Улика — вывод: exp15:6-8: > СТАТУС (перевёрнут полигоном, пакет-5, 25.07.2026): ЭКСПЕРИМЕНТ ИСПОЛНЕН И ЗАЛЕНДЁН в редакции REV.2 (D39.7). Прежний статус-блок … описывал состояние ДО прогона и завис — снят. Факт: прогон состоялся, потрачено $12.79 из капа $15 doc10:17: … извлечение в структурированный пакет гейтится exp15 + research/18 §D-3 («форма пакета не разрешена») doc10:41: Гейты на стройку: exp15 (какие правила реально несут) + research/18 §D-3 (форма пакета). doc10:45: 2. Извлечение правил в пакет — exp15 ЗАВЕРШЁН; **данные-часть майнера ИЗВЛЕЧЕНА (D39.16 … Предложенное действие: В §3 и в ячейке §1 (строка 17) снять exp15 из списка невзятых гейтов и оставить единственный живой: «Гейт на стройку — research/18 §D-3 (форма пакета не разрешена); exp15 отработан 25.07 (D39.7), форсинг извлечения сегодня — ja→ru-реплика (§4 п.2)». Тем же касанием привести строку 33 («кандидаты на извлечение ПОСЛЕ exp15») к той же рамке. Разрушающее: True ⚠ Опровергатель поправил действие: Действие верное, добавить один якорь для принимающего: §D-3 — это docs/research/18-quality-levers.md:273 (в репо два файла 18-*: 18-quality-levers.md и 18-quality-levers-chatgpt.md; §D живёт в первом, во втором такой секции нет). Правку строки 33 автор назвал сам, она подтверждена (строка существует дословно).


N102 · MINOR · docs/architecture/12-go-style-notes.md:8 · C-исполненное-как-открытое

Срез: Заметки реализации и Go-нормы — docs/architecture/03-implementation-notes.md · docs/architecture/12-go-style-notes.md Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): Пар-скоупнутые модули (checkers_zh_ru и т.п.) легитимны как код за pair-гейтом, но их ДАННЫЕ (словники, руны, таблицы) подлежат выносу в langpack. Почему протухло: Норма как норма жива, но её единственный пример — мёртвый указатель, и он указывает ровно в обратную сторону. backend/internal/pipeline/checkers_zh_ru.go (295 строк) УДАЛЁН коммитом 952469b 24.07.2026 — в тот же день, которым датирован сам норматив; данные ушли в configs/langpacks/zh-ru/dc-checkers.txt, а чекеры стали пар-агностичными. То есть checkers_zh_ru — эталон ЗАВЕРШЁННОГО выноса, а док предъявляет его как открытый долг. Тот же класс указателя уже вычищали из кода: ресёрч 31.07 нашёл 6 живых ссылок на удалённый файл и назвал их вредными («рекламируют пар-модуль там, где код уже generic»), 02.08 их сняли («grep пуст») — а седьмая, докова, осталась. Улика — команда: cd /home/ubuntu/projects/textmachine-main && git show --stat --format='%h %ad' --date=short 952469b -- '*checkers_zh_ru*' | cat; git ls-tree -r --name-only HEAD | grep checkers_zh_ru; wc -l backend/configs/langpacks/zh-ru/dc-checkers.txt Улика — вывод: 952469b 2026-07-24 backend/internal/pipeline/checkers_zh_ru.go | 295 ----------------------- backend/internal/pipeline/checkers_zh_ru_test.go | 25 +- 2 files changed, 19 insertions(+), 301 deletions(-)

git ls-tree HEAD | grep checkers_zh_ru → backend/internal/checks/checkers_zh_ru_test.go (ТОЛЬКО тест-файл) 80 backend/configs/langpacks/zh-ru/dc-checkers.txt

docs/archive/reports/GENERALITY_RESEARCH_2026-07-31.md:144: «6 живых указателей на checkers_zh_ru.go — файла нет с 952469b (24.07, 7 дней назад…). Вредны именно для этой инвентаризации: рекламируют пар-модуль там, где код уже generic» docs/archive/reports/SMALLPACK_TAILS_2026-08-02.md:65: «6 указателей на checkers_zh_ru.go | с Положительный контроль: git ls-tree -r --name-only HEAD | grep checkers_zh_ru → backend/internal/checks/checkers_zh_ru_test.go (греп по этому токену в дереве HEAD что-то находит; значит отсутствие .go-файла — факт, а не опечатка в шаблоне) Предложенное действие: Заменить пример на живой: «Пар-скоупнутые модули легитимны как код за pair-гейтом, но их ДАННЫЕ (словники, руны, таблицы) подлежат выносу в langpack. ⚠ Прежний пример checkers_zh_ru.go снят: файл удалён 952469b (24.07), детект-паттерны живут в configs/langpacks/zh-ru/dc-checkers.txt, чекеры пар-агностичны. Живой остаток долга — строка 79 бэклога (остаточные Cyrillic-хардкоды пар-гейтед DC-чекеров).» Разрушающее: True ⚠ Опровергатель поправил действие: Правку делать, но без двух ошибок предложенного текста. (1) Не оформлять как «исполненное как открытое» — норма в силе; менять только пример: назвать checkers_zh_ru.go СНЯТЫМ образцом завершённого выноса (удалён 952469b 24.07, данные — configs/langpacks/zh-ru/dc-checkers.txt, 80 строк) и, если нужен живой пример, брать пар-гейченные DC-чекеры в internal/checks. (2) НЕ писать «живой остаток долга — строка 79 бэклога (остаточные Cyrillic-хардкоды)»: строка 79 — про Go-константу cjkScriptNames (плотность письма для серий), и её кириллические компоненты в ней же помечены ИСПОЛНЕННЫМИ (D39.78); носителем чистки мёртвых указателей на checkers_zh_ru.go была строка 89 (D39.72), и она ЗАКРЫТА — то есть остатка долга по этому предмету в трекере нет вовсе, доковый пример — последний хвост.


N103 · MINOR · docs/architecture/12-go-style-notes.md:16 · B-мёртвый-указатель

Срез: Заметки реализации и Go-нормы — docs/architecture/03-implementation-notes.md · docs/architecture/12-go-style-notes.md Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): Русским (target-языком) ОСТАЮТСЯ и не трогаются: wire/инъекц-промпты, которые читает модель (glossaryBlockHeader, маркер ⟨проверить⟩, gender-аннотации; …) Почему протухло: Идентификатора glossaryBlockHeader в дереве нет: константа с русским заголовком жила в internal/pipeline/memory.go:504 и удалена тем же паком-14 (952469b, 24.07) — русский wire-текст перестал быть Go-идентификатором и стал СТРОКОЙ ДАННЫХ ru\tglossary_header\t… в internal/lang/data/injection.txt; в Go осталось только поле InjectionTexts.GlossaryHeader (internal/lang/embedded.go:387), которое само по себе никакого русского не несёт. Правило неперевода wire-строк в силе, но названный носитель уехал: сессия, ищущая «где нельзя трогать русский», грепает glossaryBlockHeader и получает ноль, а настоящий охраняемый объект — injection.txt. Улика — команда: cd /home/ubuntu/projects/textmachine-main && grep -rn 'glossaryBlockHeader' --include=*.go backend/ ; echo '--- was ---'; git grep -n 'glossaryBlockHeader' 952469b^ -- backend/ | head -3; echo '--- now ---'; grep -n 'glossary_header' backend/internal/lang/data/injection.txt Улика — вывод: grep -rn 'glossaryBlockHeader' --include=.go backend/ → (пусто) --- was --- 952469b^:backend/internal/pipeline/memory.go:501:// glossaryBlockHeader introduces the injected glossary block. … 952469b^:backend/internal/pipeline/memory.go:504:const glossaryBlockHeader = "ГЛОССАРИЙ (используй эти утверждённые переводы имён и терминов последовательно; строки с пометкой ⟨проверить⟩ — неподтверждённые кандидаты):" --- now --- backend/internal/lang/data/injection.txt:6:ru\tglossary_header\tГЛОССАРИЙ (используй эти утверждённые переводы имён и терминов последовательно; строки с пометкой ⟨проверить⟩ — неподтверждённые кандидаты): Положительный контроль: grep -rno '[a-zA-Z][Gg]lossary[A-Za-z]' --include=.go backend/ | sort -u → RenderGlossaryBlock, GlossaryHeader, GlossaryInjection, glossaryLineTokens, … (37 идентификаторов). Инструмент в этой области находит десятки соседних имён; пустой результат по glossaryBlockHeader — отсутствие символа, а не промах грепа. Предложенное действие: Заменить glossaryBlockHeader на действующие носители: «…которые читает модель (target-данные инъекции internal/lang/data/injection.txt: glossary_header / editor_header / editor_unverified_header / unverified_marker / gender_*; в Go им отвечает lang.InjectionTexts, internal/lang/embedded.go:387)». Так правило начинает указывать на файл, правка которого действительно двигает провод. Разрушающее: True ⚠ Опровергатель поправил действие: Замену делать, но без предложенного якоря internal/lang/embedded.go:387: 387 — это ПОЛЕ GlossaryHeader, тип InjectionTexts объявлен на 386, и главное — file:line-якоря живых доков линтуются docs/scripts/counts.py --lint и гниют при каждой правке файла. Писать без номера строки: «…target-данные инъекции internal/lang/data/injection.txt (glossary_header · editor_header · editor_unverified_header · unverified_marker · gender_*); в Go им отвечает тип lang.InjectionTexts (internal/lang/embedded.go)». Оговорку про санкционированное снятие маркера (строка 134 / D39.104) сохранить — она к идентификатору не относится.


N104 · MINOR · docs/architecture/12-go-style-notes.md:17 · A-дрейф

Срез: Заметки реализации и Go-нормы — docs/architecture/03-implementation-notes.md · docs/architecture/12-go-style-notes.md Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): ни W0/W1/W1.5/W2 в идентификаторах, логах и комментариях — пишем waveDraft/waveEdit … в код и в операторский вывод они не возвращаются. Почему протухло: Фактический хвост нормы («в код … не возвращаются») сегодня ложен: в НЕтестовом Go живут 8 упоминаний, все в комментариях — cmd/tmctl/main.go:313, internal/config/pipeline.go:90,94,101,103,108, internal/config/book.go:84,85. Норма запрещает аббревиатуры именно «в идентификаторах, логах и КОММЕНТАРИЯХ», так что это её прямое нарушение, а не серая зона. Все восемь датированы 19.07 — днём самой директивы — и с тех пор ни разу не вычищены. В идентификаторах и в операторском выводе аббревиатур действительно нет (проверено): протух только хвост про «код целиком». Улика — команда: cd /home/ubuntu/projects/textmachine-main/backend && grep -rnE '\bW0\b|\bW1\b|\bW1\.5\b|\bW2\b' --include=*.go . | grep -v _test.go; git blame -L 313,313 --date=short -- cmd/tmctl/main.go | cat Улика — вывод: cmd/tmctl/main.go:313:// pre-flight the operator runs on the mined delta before the W1.5 reseed. Prints "OK" on a clean seed. internal/config/pipeline.go:90:// over the draft chunks (W1) and edit units (W2). It is a TRANSPORT axis … internal/config/pipeline.go:94:// to 1: a wave-STRUCTURED but sequential run (W1 all drafts → W1.5 → W2 all units), deterministic and safe; internal/config/pipeline.go:101:// Mining configures the W1.5 bank-mining stop (WS3 / R1): … internal/config/pipeline.go:103:// langpack_root): the miner then runs at the W1.5 boundary over the W1 drafts … internal/config/pipeline.go:108:// … Empty ContrastPath ⇒ W1.5 auto-continues. internal/config/book.go:84:// … Empty ⇒ no Другие носители того же факта: docs/glossary.md:39 — «Волны — waveDraft (черновик) → терминолог → waveEdit (редактура); имена только полные, аббревиатуры W0/W1 запрещены (решение владельца 19.07)». Носитель ВЕРЕН: несёт норму без ложного фактического хвоста, править его не нужно. Предложенное действие: Снять недоказуемый хвост и заменить его на состояние с ценой: «… — пишем waveDraft/waveEdit, «draft wave started». В идентификаторах и операторском выводе аббревиатур нет (проверено грепом); ⚠ в КОММЕНТАРИЯХ остались 8 хвостов от 19.07 — cmd/tmctl/main.go:313, internal/config/pipeline.go:90,94,101,103,108, internal/config/book.go:84,85; чистка комментария поведенчески нейтральна и голден не двигает — снимать попутно при правке этих файлов.» Разрушающее: True ⚠ Опровергатель поправил действие: Не вписывать в норматив предложенный список из восьми file:line — он неполон уже при рождении (пропущены internal/pipeline/bankmaterialize.go:98 и комментарии configs/pipeline-arm-mistral.yaml:3, configs/pipeline-arm-deepseek-pro.yaml:3) и попадает под линт якорей counts.py --lint, то есть гниёт с первой же правкой этих файлов. Правильное лечение двухчастное: (1) в доке снять недоказуемый описательный хвост, оставив НОРМУ и одну датированную фразу без якорей — «⚠ в комментариях остались хвосты от 19.07; чистка поведенчески нейтральна и голден не двигает — снимать попутно»; (2) сама чистка — зона БЭКЕНДА, значит нужна строка бэклога на свип комментариев (греп вести токеном W[012](\.5)? с учётом подчёркиваний, иначе snapshot_W2 снова уедет). Второй носитель docs/glossary.md:39 находка оценила верно — он несёт норму без ложного хвоста и правки не требует.


N105 · MINOR · docs/architecture/13-tech-debt-anchors.md:12 · E-второй-носитель

Срез: Реестр загейченных триггеров — docs/architecture/13-tech-debt-anchors.md (весь реестр, §Б-108 первым) Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): backend/internal/store/migrate.go:186 «translit_policy … field only in v1; validator = Phase 2» и :194 «ruby_reading … bridge to Polivanov dst validation, B6 Phase 2 — NOT a dst» Почему протухло: Один и тот же якорь ja-препа живёт в двух живых носителях, и копии разошлись: строка 81 бэклога уже исправлена на «migrate.go:187/194», а справочник — тот самый файл, куда бэклог отсылает за якорями, — застрял на :186. Реальные позиции: 187 и 195, то есть в справочнике ошибочны ОБА числа, а в трекере — одно. Это прямой контрпример объявленной норме файла «Актуализируется лендингами паков, которые трогают эти строки»: лендинг поправил трекер и не поправил справочник, ради которого трекер и держат тонким. Улика — команда: grep -n 'translit_policy TEXT' backend/internal/store/migrate.go; grep -n 'ruby_reading TEXT' backend/internal/store/migrate.go; grep -nE '^\| 81 \|' docs/PROGRESS.md | grep -o 'migrate.go:[0-9/]*' Улика — вывод: 187: translit_policy TEXT NOT NULL DEFAULT '', -- B5 western-name-via-katakana (field only in v1; validator = Phase 2) 195: ruby_reading TEXT NOT NULL DEFAULT '', -- ruby-seeded rows: the furigana reading (bridge to Polivanov dst validation, B6 Phase 2 — NOT a dst)

docs/PROGRESS.md строка 81: migrate.go:187/194 Другие носители того же факта: docs/PROGRESS.md:89 (строка бэклога 81 — вторая, частично исправленная копия якоря) Предложенное действие: Свести к одному носителю: в строке 81 бэклога оставить ссылку «якоря — §Срез 81/83/85 справочника», а числа держать только в справочнике (и там поправить 186→187, 194→195). Пока живут обе копии — при каждом лендинге, трогающем ja-преп, обязана обновляться ТА, куда отсылает вторая, иначе расхождение воспроизведётся. Разрушающее: True ⚠ Опровергатель поправил действие: Диагноз верен, лекарство — нет. Автор предлагает «числа держать только в справочнике», то есть сконцентрировать голые номера в одном носителе, — а именно голые номера и есть механизм гниения: в этом же файле 14 из 18 сверенных мной якорей уехали, и якорь memory.go:1048 из ЭТОЙ ЖЕ строки 81 уехал дважды (сегодня цель на 1077), причём автор его не заметил, хотя он лежит в тех же двух носителях. Правильное действие — не переносить копию, а сделать ОБЕ копии машинно-проверяемыми: гейт docs/scripts/counts.py --lint (D39.126, бэклог-строка 205) сверяет якорь по содержимому, если он записан в токен-форме, поэтому в справочнике и в строке 81 писать `backend/internal/store/migrate.go:187`=`translit_policy TEXT`, `…:195`=`ruby_reading TEXT` и `backend/internal/membank/memory.go:1077`=`KANA and HAN are deliberately NOT boundary-checked`. Тогда расхождение копий перестаёт быть вопросом дисциплины лендинга: следующий сдвиг кода покраснеет в обеих. Дедупликацию носителей (ссылка вместо чисел) делать можно, но она вторична и без токенов не спасает — сегодняшний единственный носитель тоже протух.


N106 · MINOR · docs/architecture/13-tech-debt-anchors.md:24 · B-мёртвый-указатель

Срез: Ранние арх-доки (кандидаты в архив): docs/architecture/01-decisions.md · 02-mvp-plan.md · 04-unhappy-paths.md · 06-memory-risk-registry.md Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): 02-mvp-plan.md:34 «пере-съём exp08 v3» как открытый Почему протухло: Якоря реестра техдолга в ДВА дока моего среза сдвинулись ровно на +6 строк и не были актуализированы: коммит d2cd30c (22.08) дописал ⚠-поправки в 02-mvp-plan.md (+7/1 = +6) и 06-memory-risk-registry.md (+6). Сегодня: «пере-съём exp08 v3» — 02-mvp-plan.md:40 (на :34 стоит строка про tmctl export); A7 — 06:38 (на :32 стоит A1), D2 — 06:67 (на :61 пустая строка), «Отложено в v1.1/Фазу 2 … морфо-гейт глагольного рода (C3)» — 06:111 (на :105 стоит «Обязательно закрыть/учесть до заморозки схемы (шаг 4):»). Улика — команда: cd /home/ubuntu/projects/textmachine-main && git show --numstat d2cd30c -- docs/architecture/02-mvp-plan.md docs/architecture/06-memory-risk-registry.md | tail -2; for n in 34 40; do printf '02:%s -> ' $n; sed -n "${n}p" docs/architecture/02-mvp-plan.md | cut -c1-60; done; for n in 32 38 61 67 105 111; do printf '06:%s -> ' $n; sed -n "${n}p" docs/architecture/06-memory-risk-registry.md | cut -c1-55; done Улика — вывод: 7 1 docs/architecture/02-mvp-plan.md 6 0 docs/architecture/06-memory-risk-registry.md 02:34 -> - Экспорт txt/epub + отчёт — конкретизирован D29.1: tmctl export 02:40 -> 2. Деньги: телеметрия точна … мягкий sanity-якорь ~$0.7/ранобэ (до-флиповый exp08; … пере-съём exp08 v3) 06:32 -> | A1 | Пустой/слабый retrieval трактуется как ошибка 06:38 -> | A7 | Эмбеддинг-порог как тихий гейт (Фаза 2) 06:61 -> (пустая строка) 06:67 -> | D2 | Whole-book RAG по резюме — недоказанное расширение 06:105 -> Обязательно закрыть/учесть до заморозки схемы (шаг 4): 06:111 -> 5. Отложено в v1.1/Фазу 2 (не блокирует v1-схему, но заложить поля/место): Другие носители того же факта: docs/architecture/13-tech-debt-anchors.md:64 («06-memory-risk-registry.md:105» — тот же +6), docs/architecture/13-tech-debt-anchors.md:88 («06-memory-risk-registry.md:32,61,105» — тот же +6) Предложенное действие: Одна арифметическая правка: во всех трёх местах 13-tech-debt-anchors.md прибавить 6 — 02-mvp-plan.md:34→:40; 06:32→:38, 06:61→:67, 06:105→:111. Урок на будущее: якоря в ⚠-баннируемые доки держать по цитате/секции, а не по номеру строки — любой новый баннер их снова сдвинет. Разрушающее: False


N107 · MINOR · docs/architecture/13-tech-debt-anchors.md:104 · E-второй-носитель

Срез: Реестр загейченных триггеров — docs/architecture/13-tech-debt-anchors.md (весь реестр, §Б-108 первым) Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): 84 (neuter отсутствует в enum memory.go:815-823 и в инъекционных текстах embedded.go:395-397; gender: neuter в сиде — тихий no-op без линта) Почему протухло: Факт про neuter лежит в этом файле ДВАЖДЫ, и копии разошлись ВНУТРИ одного документа: тело пункта (строки 70-72) несёт пере-сверку 23.08 «ЧЕТЫРЕ пункта из пяти ПРОТУХЛИ — носитель в коде ПОСТРОЕН», а «Сводка среза» тремя абзацами ниже по-прежнему утверждает обратное — «neuter отсутствует в enum … тихий no-op без линта». Оба цитируемых сводкой якоря мертвы: case "neuter" живёт на memory.go:848 (не 815-823), GenderNeuter — на embedded.go:398 (не 395-397). Читатель, который дойдёт только до сводки (а сводка — то, что читают быстро), унесёт опровергнутый факт. Улика — команда: sed -n '848p' backend/internal/membank/memory.go; sed -n '815,823p' backend/internal/membank/memory.go | head -3; sed -n '398p' backend/internal/lang/embedded.go; sed -n '395,397p' backend/internal/lang/embedded.go Улика — вывод: 848: case "neuter", "n": --- memory.go:815-823 (то, что там на самом деле): --- // containsWholeWord reports whether s contains sub as a whole word ... --- embedded.go:398: --- GenderNeuter string // " (ср. — …)" — a neuter entity/creature (row 84); "" for a target wi --- embedded.go:395-397: соседние поля, не GenderNeuter --- Другие носители того же факта: docs/architecture/13-tech-debt-anchors.md:70-72 (тело пункта «Строка 84» — противоположная, верная редакция) Предложенное действие: В «Сводке среза» (строка 104) заменить фрагмент про 84 на «84 — ЗАКРЫТА D39.69 §3 (neuter end-to-end построен; живой остаток — комментарий схемы migrate.go:184)» и убрать фрагмент «feed_cap: не построен (grep=0) … батчи в лексикографическом порядке src». Общее правило по месту: сводки среза не должны нести собственную фактуру — только ссылку на пункты, иначе они гниют независимо от тел, как здесь. Разрушающее: True ⚠ Опровергатель поправил действие: Действие верно, но НЕПОЛНО, и в неполном виде оставит документ наполовину ложным. Автор чинит feed_cap только в СВОДКЕ, а тот же протухший факт несёт ТЕЛО пункта на строках 98-102: «feed_cap НЕ построен … кандидаты сортированы ЛЕКСИКОГРАФИЧЕСКИ по src (terminologist.go:191; terminology.go:212), а Batch сохраняет этот порядок (terminology.go:917-919)». Замером: Batch теперь упорядочивает батчи по УБЫВАНИЮ частоты (terminology.go:1161-1164, гвоздь TestBatchValueOrder в series_test.go:199), а ратифицированная D39.69 §1 говорит дословно «value-порядок отреза бюджета (частота-desc — feed_cap ЗАКРЫТ этим, из строки 86 изъят)». Сверх того путь terminology.go в теле мёртв как ПУТЬ: backend/internal/pipeline/terminology.go не существует, пакет живёт в backend/internal/terminology/. Поэтому: править строки 98-102 и 104 ОДНИМ касанием — тело пометить «→ ЗАКРЫТО D39.69 §1 (value-порядок частота-desc, backend/internal/terminology/terminology.go, гвоздь TestBatchValueOrder)», сводку свести к ссылке на пункты. И общее правило автора («сводки не должны нести собственную фактуру») верно, но реализуемо тут дешевле: строка 162 показывает работающую альтернативу — ⚠-баннер о том, что якоря сводки не актуализируются.


N108 · MINOR · docs/architecture/14-api-contract/README.md:6 · B-мёртвый-указатель

Срез: Контракт API и его зеркала — docs/architecture/14-api-contract/README.md + docs/architecture/14-api-contract/openapi.yaml Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): СТАТУС: РАТИФИЦИРОВАН как контракт API v0 по 0.9.0 включительно (D39.169; перечень миноров и их провенанс — ниже по файлу, здесь НЕ дублируется) Почему протухло: D39.169 ратифицировала минор 0.8.0, а не 0.9.0; 0.9.0 принят и заленджен нотой D39.180 (31.08). Тело того же дока (строка 69) описывает 0.9.0 вовсе БЕЗ номера ноты — «ратифицирован №20 31.08 … заленджен №21 с паком P12», так что читатель, которому шапка дала D39.169, номера 0.9.0 не найдёт нигде. Улика — команда: grep -n '^## D39.169\|^## D39.180' docs/architecture/05-decisions-log.md | cut -c1-150; sed -n '69,71p' docs/architecture/14-api-contract/README.md | cut -c1-160 Улика — вывод: 1176:## D39.169 — ПЛАТФОРМЕННЫЙ ПАК P11 ПРИНЯТ И ЗАЛЕНДЖЕН + КОНТРАКТНЫЙ МИНОР 0.8.0: отзыв сессии гасит открытый поток, застрявшие деньги видны (29.08, оркестратор №19). 1677:## D39.180 — ПАК ПЛАТФОРМЫ P12 «ДОЛГИ ПОД НОГАМИ» ПРИНЯТ И ЗАЛЕНДЖЕН вместе с контрактным минором 0.9.0 (31.08, оркестратор №21).

**0.9.0 (ратифицирован №20 31.08 по пингу зоны ДО стройки, заленджен №21 с паком P12) — ВТОРАЯ граница пересчёта chapters_done и непрозрачный Book.shape_epoch, которым она себя называет; Предложенное действие: В шапке заменить «(D39.169; …)» на «(D39.180; …)» и в перечне миноров (строка 69) дописать номер ноты: «0.9.0 (D39.180, 31.08) — ратифицирован №20 по пингу зоны ДО стройки, заленджен №21 с паком P12». Разрушающее: False


N109 · MINOR · docs/architecture/14-api-contract/README.md:60 · B-мёртвый-указатель

Срез: Контракт API и его зеркала — docs/architecture/14-api-contract/README.md + docs/architecture/14-api-contract/openapi.yaml Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): /v0; вывод и провенанс — §2.19, отчёт сессии — docs/CONTRACT_MINOR_REPORT.md Почему протухло: Файла по этому пути нет: отчёт вынесен в архив коммитом 31b96d1 (27.08) и лежит как docs/archive/reports/CONTRACT_MINOR_REPORT_2026-08-27.md. Ссылка повторяется в теле дока (строка 738) и оба раза ведёт в никуда. Улика — команда: ls docs/CONTRACT_MINOR_REPORT.md; find /home/ubuntu/projects/textmachine-main -name 'CONTRACT_MINOR_REPORT*'; git show --stat 31b96d1 | grep CONTRACT Улика — вывод: ls: cannot access 'docs/CONTRACT_MINOR_REPORT.md': No such file or directory /home/ubuntu/projects/textmachine-main/docs/archive/reports/CONTRACT_MINOR_REPORT_2026-08-27.md .../reports/CONTRACT_MINOR_REPORT_2026-08-27.md} | 8 ++++++++ Положительный контроль: Тот же find по каталогу docs/ находит существующие доки (./docs/PROGRESS.md) и находит сам искомый отчёт по basename в архиве — значит инструмент в этой области видит, а ноль по docs/CONTRACT_MINOR_REPORT.md есть отсутствие файла, а не слепота поиска. Другие носители того же факта: docs/architecture/14-api-contract/README.md:738 — «подробный разбор и отвергнутые альтернативы — отчёт docs/CONTRACT_MINOR_REPORT.md» (тот же мёртвый путь) Предложенное действие: Обе ссылки (строки 60 и 738) переписать на docs/archive/reports/CONTRACT_MINOR_REPORT_2026-08-27.md и пометить ⚠-архивной, чтобы инструкции оттуда не исполнялись. Разрушающее: False


N110 · MINOR · docs/architecture/14-api-contract/README.md:990 · C-исполненное-как-открытое

Срез: Контракт API и его зеркала — docs/architecture/14-api-contract/README.md + docs/architecture/14-api-contract/openapi.yaml Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): | PATCH/DELETE /books/{id}, GET /runs/{id} | колонки есть | НЕ ПОСТРОЕНО (заведено 0.3.0) | вход P7 | Почему протухло: Носитель «вход P7» израсходован — пак P7 принят D39.153, и ⚠-абзац НАД самой таблицей это уже пишет («Носитель израсходован: пак P7 принят D39.153»). Факт «НЕ ПОСТРОЕНО» верен (роутер монтирует 15 маршрутов из 20 операций канона: нет updateBook/deleteBook/getRun/createExport/getExport), но строки живут без действующего носителя — ровно то нарушение §Б-21, которое таблица и заведена чинить. Тем же дефектом больны ещё три строки: resume (981), GET …/notes (988) и «Счёт сделанного» (1004). Улика — команда: grep -c 'operationId:' docs/architecture/14-api-contract/openapi.yaml; sed -n '/^var contractSurface/,/^}$/p' platform/internal/httpapi/v0.go | grep -c 'method:'; sed -n '/^var contractSurface/,/^}$/p' platform/internal/httpapi/v0.go | grep -o 'path: "[^"]*"' Улика — вывод: 20 15 path: "/capabilities" path: "/books" path: "/books/{bookId}" path: "/books/{bookId}/chapters" path: "/books/{bookId}/chapters/{chapterId}/units" path: "/books/{bookId}/notes" path: "/books/{bookId}/bank" path: "/books/{bookId}/bank/corrections" path: "/books/{bookId}/events" path: "/usage" path: "/books/{bookId}/run-options" path: "/books/{bookId}/runs" path: "/runs/{runId}/stop" path: "/runs/{runId}/resume" path: "/books" (POST) — ни одного PATCH/DELETE /books/{bookId}, ни GET /runs/{runId}, ни /exports Другие носители того же факта: docs/PROGRESS.md:3 — «канон обещает createExport/getExport, а платформа их НЕ монтирует (смонтировано 15 операций из 20; нет также getRun, updateBook, deleteBook)» — этот носитель ЖИВОЙ и совпадает с деревом, platform/BACKLOG.md П-17 — долг двери выдачи (по docs/PROGRESS.md:3) Предложенное действие: Заменить в четырёх строках таблицы израсходованный носитель «вход P7» на живой: для PATCH/DELETE /books/{id} и GET /runs/{id} — строка зонного platform/BACKLOG.md (или новая строка единого бэклога), для resume — «закрыто D39.162/PD-282 fixed» (см. отдельную находку), для остальных — действующий пак/ноту. Само правило §Б-21 при этом не трогать: оно и поймало дефект. Разрушающее: False


N111 · MINOR · docs/architecture/14-api-contract/README.md:998 · B-мёртвый-указатель

Срез: Контракт API и его зеркала — docs/architecture/14-api-contract/README.md + docs/architecture/14-api-contract/openapi.yaml Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): | Note.code как enum спеки | карта приложения А | не enum, пока не написаны фразы | строка 148 (фразы владельца) | Почему протухло: Строки 148 в едином бэклоге НЕТ — её удалил a023e39 решением владельца 21.08. Хуже: ТОТ ЖЕ документ на строке 1670 уже это знает и пишет «⚠ Прежний указатель «строка 148» мёртв: строку удалил a023e39 решением владельца», а действующим носителем называет строки 203/204 и делегирование фраз (D39.176 п.4, подписи владельца больше не ждём). То есть §3 спорит с Приложением А того же файла. Улика — команда: for n in 49 94 148 160 161 186 228; do printf 'строка %s: %s\n' $n "$(grep -c "^| $n |" docs/PROGRESS.md)"; done; git show --stat a023e39 | head -8 Улика — вывод: строка 49: 1 строка 94: 1 строка 148: 0 строка 160: 1 строка 161: 1 строка 186: 1 строка 228: 1

commit a023e39323 Date: Fri Aug 21 19:31:20 2026 Record three owner decisions: cut comment water rather than length, drop the appendix row that described a world the contract abolished, and put the complain-to-owner norm into the frontend hook docs/PROGRESS.md | 7 +++---- Положительный контроль: Та же форма грепа ^| N | по docs/PROGRESS.md находит шесть других номеров, названных этим же доком (49, 94, 160, 161, 186, 228) — инструмент строки бэклога видит; ноль даёт именно 148. Другие носители того же факта: docs/architecture/14-api-contract/README.md:1670 — второй носитель того же факта, и он УЖЕ верен («Прежний указатель «строка 148» мёртв»); расходятся именно две копии внутри одного дока Предложенное действие: В строке 998 заменить носитель «строка 148 (фразы владельца)» на тот, который сам док уже назвал в Приложении А п.3: «строка 203 (карта причина→код) + строка 204 (доставка словаря данными); фразы делегированы проекту, D39.176 п.4 — подписи владельца не ждём». Разрушающее: False


N112 · MINOR · docs/architecture/14-api-contract/README.md:1002 · B-мёртвый-указатель

Срез: Контракт API и его зеркала — docs/architecture/14-api-contract/README.md + docs/architecture/14-api-contract/openapi.yaml Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): заказано бэкенд-паку «тихая порча», docs/BACKEND_SILENT_HARM_SESSION_PROMPT.md §3.3 Почему протухло: Промт вынесен в архив: живой путь — docs/archive/prompts/BACKEND_SILENT_HARM_SESSION_PROMPT_2026-08-28.md. Дополнительно: половина того, на что ссылка ведёт (§3.3), в этой же клетке уже объявлена ИСПОЛНЕННОЙ («пере-снято 30.08: … ВТОРОЙ ЗАКРЫТ … D39.164 §3.3»), то есть указатель ведёт в архив за работой, которая закрыта нотой. Улика — команда: ls docs/BACKEND_SILENT_HARM_SESSION_PROMPT.md; ls docs/archive/prompts/ | grep SILENT Улика — вывод: ls: cannot access 'docs/BACKEND_SILENT_HARM_SESSION_PROMPT.md': No such file or directory BACKEND_SESSION_PROMPT_SILENT_REFUSALS.md BACKEND_SILENT_HARM_SESSION_PROMPT_2026-08-28.md Положительный контроль: Тот же ls в docs/archive/prompts/ возвращает два файла с искомым токеном SILENT — каталог читается, поиск работает; ноль по docs/BACKEND_SILENT_HARM_SESSION_PROMPT.md есть отсутствие файла. Предложенное действие: Заменить путь на docs/archive/prompts/BACKEND_SILENT_HARM_SESSION_PROMPT_2026-08-28.md (⚠ архив), а живым носителем оставшейся половины назвать строку бэклога 228 — она в этой же клетке уже указана и в трекере существует. Разрушающее: False


N113 · MINOR · docs/architecture/15-money-path.md:6 · C-исполненное-как-открытое

Срез: Денежный путь — docs/architecture/15-money-path.md Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): docs/architecture/14-api-contract/openapi.yaml лежит в «своей» зоне и при этом двигается прямо сейчас паком контракта 0.9.0. Почему протухло: Пак контракта 0.9.0 ЗАЛЕНДЖЕН 31.08 коммитом 0a680a3 (D39.180), файл в дереве чист, версия в нём — 0.9.0. Настоящее время «двигается прямо сейчас» ложно, и вместе с ним протух ЕДИНСТВЕННЫЙ пример, на котором шапка держит свою норму «стабильна не зона, а чистый файл», а также обоснование в §1: «⚠ номер не ставим: файл двигает пак контракта 0.9.0». По собственному правилу дока цель теперь чиста и годна под номерную форму с токеном. Улика — команда: git log --format='%h %ad %s' --date=short -1 -- docs/architecture/14-api-contract/openapi.yaml; git status --porcelain docs/architecture/14-api-contract/openapi.yaml; grep -n '^ version:' docs/architecture/14-api-contract/openapi.yaml Улика — вывод: 0a680a3 2026-08-31 Land platform pack P12 with contract minor 0.9.0: the bank-stop bypass is gone, a chapter counts by the pass current for it, and the boundary now judges prose too (git status --porcelain: пустой вывод — файл чист) 5: version: 0.9.0 Другие носители того же факта: docs/architecture/15-money-path.md:14 — второй экземпляр того же истёкшего обоснования («⚠ номер не ставим: файл двигает пак контракта 0.9.0») Предложенное действие: Перевести пример в прошедшее время: «…лежал в «своей» зоне и при этом двигался паком контракта 0.9.0 (заленджен 31.08, 0a680a3)»; в §1 (строка 14) заменить обоснование на нейтральное («контракт переверстывается минорами — греп-форма дешевле поддержки») или поставить номер с токеном, раз файл чист. Разрушающее: False


N114 · MINOR · docs/architecture/15-money-path.md:14 · A-дрейф

Срез: Денежный путь — docs/architecture/15-money-path.md Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): Деньги видимы с первого вызова: телеметрия, потолки, явное согласие на пере-оплату (CLAUDE.md, цель 5). Почему протухло: Из трёх опор канона, названных здесь, у двух в доке есть носители (телеметрия — леджер/status в §2, потолки — §2/§3), а у третьей — «явное согласие на пере-оплату» — нет ни одного: ни в §2, ни в карте §6 «Код денег движка» (там пять файлов и pipeline/rebill.go среди них нет). Механизм построен и ратифицирован: порог min($0.50, 5% × ProjectedBookUSD) с переопределением rebill_consent_usd (D20.2-Q2). Честно: сам пробел стоит с рождения дока (гейт заленджен 25.07, dad4b56/D39.31), то есть это НЕ чистый дрейф; но тема СДВИНУЛАСЬ 31.08 и док этого не застал: D39.182 §1(б) починил дефект, из-за которого гейт НЕ срабатывал на правке исходника на месте, а §3 той же ноты пере-определил базис порога («считается БЕЗ контура банк-ролей»). Улика — команда: grep -n 'rebillConsentFloorUSD\|rebillConsentShare\|rebill_consent_usd' backend/internal/pipeline/rebill.go backend/internal/config/book.go | head -5; sed -n '67p' docs/architecture/15-money-path.md Улика — вывод: backend/internal/pipeline/rebill.go:41: rebillConsentFloorUSD = 0.50 backend/internal/pipeline/rebill.go:42: rebillConsentShare = 0.05 backend/internal/pipeline/rebill.go:285:// rebill_consent_usd when it declares one, else the ratified min($0.50, 5% × ProjectedBookUSD) with backend/internal/config/book.go:324: bad("rebill_consent_usd must be ≥ 0 (0 = the ratified default min($0.50, 5%%×projected book cost)), got %v", b.RebillConsentUSD)

| Код денег движка | backend/internal/store/ledger.go · store/store.go · pipeline/stagerun.go · pipeline/status.go · config/book.go | Положительный контроль: ОТРИЦАТЕЛЬНОЕ: grep -in 'rebill|пере-оплат|согласи' docs/architecture/15-money-path.md → единственный хит — сама строка 14. ПОЛОЖИТЕЛЬНЫЙ КОНТРОЛЬ: тот же греп по 'ре-билл' находит строку 23 («не двигает ни снапшот, ни ре-билл») — тема доку не чужда, значит ноль по носителю гейта — факт, а не артефакт грепа. Предложенное действие: Добавить в §2 буллет: «Согласие на пере-оплату — движковый гейт. Прогон, который пере-покупает уже оплаченные юниты дороже порога, останавливается и требует явного согласия; порог по умолчанию ратифицирован D20.2-Q2 — min($0.50, 5% × ProjectedBookUSD) с полом $0.50 для книги без обработанных юнитов, книга переопределяет его rebill_consent_usd (backend/internal/pipeline/rebill.go, греп func (r *Runner) rebillConsentThreshold(; валидатор — backend/internal/config/book.go, греп rebill_consent_usd must be). ⚠ Порог считается БЕЗ контура банк-ролей (D39.182 §3): контур не пере-оплачивается.» И внести pipeline/rebill.go в строку «Код денег движка» карты §6. Разрушающее: False


N115 · MINOR · docs/architecture/16-events-emitter.md:3 · E-второй-носитель

Срез: Эмиттер · закон шва · онтология банка — docs/architecture/16-events-emitter.md · 17-seam-inbound-law.md · 18-bank-ontology.md Вердикт пере-суда: (не выносилось) Цитата (дословно из дока):ЭМИТТЕР ПОСТРОЕН И ПРИНЯТ (D39.131, 9cfe080, 14.08): events.jsonl (StreamVersion 1.1) Почему протухло: Движок с 31.08 (bb541a8) пишет StreamVersion = "1.2" — минор поднят под Finished.Volume (D39.181 п.2). 1.1 сегодня — это версия ЧИТАТЕЛЯ платформы, а не потока. Тот же факт лежит вторым носителем в platform/docs/STACK_DECISIONS.md:419 («движок, StreamVersion 1.1») и разошёлся с кодом там же. Улика — команда: grep -n 'const StreamVersion' backend/internal/runevents/runevents.go platform/internal/ingest/events.go; git log -S 'StreamVersion = "1.2"' --format='%h %ad %s' --date=short -- backend/internal/runevents/runevents.go; grep -n 'StreamVersion' platform/docs/STACK_DECISIONS.md Улика — вывод: backend/internal/runevents/runevents.go:49:const StreamVersion = "1.2" platform/internal/ingest/events.go:46:const StreamVersion = "1.1" bb541a8 2026-08-31 Land the disclosure law and its eleven instances: two of the three acceptance blockers were defects inside the cure itself platform/docs/STACK_DECISIONS.md:419:| events.jsonl (NDJSON эмиттера) | движок, StreamVersion 1.1 | append-only | internal/ingest/tai **Другие носители того же факта:** platform/docs/STACK_DECISIONS.md:419, docs/PROGRESS.md:205 (строка 243, arch-10 — та же находка удержана и не применена) **Предложенное действие:** В обоих носителях: «StreamVersion 1.2 (движок пишет; читатель платформы объявляет 1.1 — мажор один, минор читателя отстаёт намеренно, runevents.go:44-48)». В STACK_DECISIONS.md:419` — та же правка (зона платформы, пингом). Разрушающее: False


N116 · MINOR · docs/architecture/16-events-emitter.md:90 · C-исполненное-как-открытое

Срез: Эмиттер · закон шва · онтология банка — docs/architecture/16-events-emitter.md · 17-seam-inbound-law.md · 18-bank-ontology.md Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): PD-61 (открыт; два свойства ДО постройки)DEFECT_REGISTER.md:72 Почему протухло: PD-60 (строка 88, «открыт») и PD-61 (строка 90, «открыт») оба стоят в регистре как fixed(P6, дерево сессии). Шапка дока баннирует только «PD-113 открыт» — эти две подписи баннер НЕ покрывает, и строка 90 прямо называет пункт (а) «живым требованием к эмиттеру». Улика — команда: for p in PD-60 PD-61 PD-107 PD-113; do printf "%-7s: " $p; grep "^| $p |" platform/docs/DEFECT_REGISTER.md | awk -F'|' '{print $(NF-2)}'; done Улика — вывод: PD-60 : fixed(P6, дерево сессии) PD-61 : fixed(P6, дерево сессии) PD-107 : open PD-113 : fixed(P6, дерево сессии) Положительный контроль: Тот же разбор ячейки на PD-107 возвращает open — значит поле статуса читается, и fixed у PD-60/61 не артефакт парсинга. Предложенное действие: Расширить ⚠-баннер шапки: «упоминания „PD-113 / PD-60 / PD-61 открыт“ в теле ниже — историческая фактура эпохи стройки; все три закрыты P6». Один правкой снимаются обе подписи. Разрушающее: False


N117 · MINOR · docs/architecture/16-events-emitter.md:114 · B-мёртвый-указатель

Срез: Эмиттер · закон шва · онтология банка — docs/architecture/16-events-emitter.md · 17-seam-inbound-law.md · 18-bank-ontology.md Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): | 3 | D39.119 п.3 · D39.122 п.2(в) · D39.123 п.2(б,в), п.3 | там же :394, :416, :428, :430 | Почему протухло: Тела D39.119/122/123 уехали в слайс docs/archive/architecture/05-decisions-D39-106-123.md, где всего 210 строк — номера 394/416/428/430 там не существуют. В ЖИВОМ журнале решений те же номера пусты (394, 416) либо принадлежат чужому телу D39.139 (428, 430). Смягчающее: строка стоит в §6, который баннирован как «ОТРАБОТАННЫЙ ХЕНДОФФ» — поэтому minor, а не major. Улика — команда: for l in 394 416 428 430; do printf "%-4s| " $l; sed -n "${l}p" docs/architecture/05-decisions-log.md | cut -c1-60; done; wc -l docs/archive/architecture/05-decisions-D39-106-123.md; grep -n '^## D39\.\(119\|122\|123\) ' docs/architecture/05-decisions-log.md docs/archive/architecture/05-decisions-D39-106-123.md | cut -c1-70 Улика — вывод: 394 | 416 | 428 | (а) Моя зона — исправлено этим лендингом: ПТ-16/ПТ-18 держали 430 | (в) Зона фронта — пинг-аддендум в журнал: FRONTEND_SESSION_PR 210 docs/archive/architecture/05-decisions-D39-106-123.md docs/archive/architecture/05-decisions-D39-106-123.md:165:## D39.119 — КУРС ВЛАДЕЛЬЦА 08.08 docs/archive/architecture/05-decisions-D39-106-123.md:189:## D39.122 — ПАК «ДВИЖКОВЫЕ БЛОКЕРЫ docs/archive/architecture/05-decisions-D39-106-123.md:201:## D39.123 — ПАК ПЛАТФОРМЫ P4 «РАННЕР» Положительный контроль: Греп ^## D39\.(119|122|123) в живом журнале пуст, а в слайсе даёт три хита (165/189/201) — инструмент работает, ноты просто переехали. Это ровно та ловушка «пустой греп по слайсу подтвердил бы вакуумно», которую D39.156 п.5 уже разбирала на другом примере. Предложенное действие: Снять line-номера совсем (D39.126: номер грепается) — «грепать D39.119/D39.122/D39.123; тела — слайс docs/archive/architecture/05-decisions-D39-106-123.md». Это же требует строка бэклога 219 («line-якоря в тела D-лога обречены съезжать»). Разрушающее: False


N118 · MINOR · docs/glossary.md:7 · B-мёртвый-указатель

Срез: Глоссарий и продуктовые требования — docs/glossary.md · docs/product-requirements.md Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): «Оркестратор №N» — канонический счётчик преемственности роли (текущий N — в шапке ORCHESTRATOR_SESSION_PROMPT). Почему протухло: В файле docs/ORCHESTRATOR_SESSION_PROMPT.md (240 строк) символа «№» нет НИ РАЗУ: его шапка намеренно номер не держит — «Счётчик ролей ОДИН, номер выдаёт тот, кто пишет промт передачи (D39.112 п.6)». Текущий номер живёт в CURRENT-STATE docs/PROGRESS.md, который прямо объявляет себя единственным носителем («ОЧЕРЕДЬ №22 (единственный носитель — здесь)»). Улика — команда: grep -c '№' docs/ORCHESTRATOR_SESSION_PROMPT.md; wc -l docs/ORCHESTRATOR_SESSION_PROMPT.md; sed -n '3p' docs/ORCHESTRATOR_SESSION_PROMPT.md | cut -c1-90; grep -o 'ОЧЕРЕДЬ №22 (единственный носитель — здесь' docs/PROGRESS.md Улика — вывод: 0 240 docs/ORCHESTRATOR_SESSION_PROMPT.md

Счётчик ролей ОДИН, номер выдаёт тот, кто пишет промт передачи (D39.112 п.6); параллельный ОЧЕРЕДЬ №22 (единственный носитель — здесь Положительный контроль: Тот же grep по тому же шаблону на docs/PROGRESS.md даёт 57 хитов (grep -c '№' docs/PROGRESS.md → 57), а grep -c 'оркестратор\|Оркестратор' docs/ORCHESTRATOR_SESSION_PROMPT.md → 3, то есть инструмент читает именно этот файл и находит в нём текст. Ноль по «№» — свойство файла, а не опечатка команды. Другие носители того же факта: docs/PROGRESS.md:3 — CURRENT-STATE, «ОЧЕРЕДЬ №22 (единственный носитель — здесь; роль передана 01.09, №21 закрыт нотой передачи D39.184)» Предложенное действие: Заменить скобку на «(текущий N — в CURRENT-STATE docs/PROGRESS.md, единственный носитель; номер выдаёт автор промта передачи — D39.112 п.6)». Разрушающее: True


N119 · MINOR · docs/product-requirements.md:50 · B-мёртвый-указатель

Срез: Глоссарий и продуктовые требования — docs/glossary.md · docs/product-requirements.md Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): НЕ построена дверь платформы (createExport/getExport) — очередь №20 п.4, гейт: живой прогон п.3 Почему протухло: Сам факт ЖИВ (проверено кодом, см. positive_control), но указатель «очередь №20» умер дважды: роль передана №20→№21 (D39.177, 31.08) и №21→№22 (D39.184, 01.09), и CURRENT-STATE несёт «ОЧЕРЕДЬ №22». Номер очереди — движущаяся мишень, привязка к нему протухает каждой передачей роли. Улика — команда: grep -o 'ОЧЕРЕДЬ №22.\{0,60\}' docs/PROGRESS.md | head -1; grep -m1 '| D39.184 |' docs/architecture/05-decisions-index.md | cut -c1-95 Улика — вывод: ОЧЕРЕДЬ №22 (единственный носитель — здесь; роль передана 01.09, №21 закрыт нотой пере | D39.184 | 01.09 | Передача роли оркестратора №21 → №22. Смена приняла пак платформы P12 Положительный контроль: Отрицательная половина утверждения ПТ-44 проверена и ЖИВА: grep -rn 'createExport\|getExport' platform/ --include=*.go → 0 хитов в коде (только README/BACKLOG/архив). Положительный контроль на тот же инструмент: grep -n 'listBooks\|getBook' platform/internal/httpapi/v0.go → строки 75-76 с реально смонтированными ручками. Т.е. греп по зоне ручки находит, а createExport/getExport в ней нет. Другие носители того же факта: docs/PROGRESS.md:16 — «платформенный остаток несут очередь №20 п.4 и platform/BACKLOG.md П-17» (тот же мёртвый номер, не в моём срезе) Предложенное действие: Заменить «очередь №20 п.4» на «очередь CURRENT-STATE, п.4 «дверь выдачи» (docs/PROGRESS.md) + platform/BACKLOG.md П-17» — на носитель, а не на номер смены. Разрушающее: True ⚠ Опровергатель поправил действие: Уточнение к находке: п.4 НЕ умер — в сегодняшней очереди CURRENT-STATE пункт (4) по-прежнему «дверь выдачи — строго ПОСЛЕ (3)», уехал только НОМЕР очереди (№20 → №22). Хуже другое, и находка этого не заметила: в docs/PROGRESS.md существует ВТОРАЯ нумерованная очередь — блок «ОТКРЫТО НА ОРКЕСТРАТОРЕ №21» (строки 36-40), — и в её №20-редакции (85282f2) п.4 был PD-425, а не дверь выдачи. То есть читатель, идущий по «очередь №20 п.4», может сесть не на тот список. Поэтому замена на «очередь CURRENT-STATE, п.4 «дверь выдачи»» верна и нужна именно словом «дверь выдачи», а не номером. Ссылка на platform/BACKLOG.md П-17 тоже ПРАВОМЕРНА, хотя её голова читается как «ИСПОЛНЕНО P7»: хвост той же строки несёт «Не вошло и отложено в P8: updateBook/deleteBook/getRun, экспорт (createExport/getExport)», и CURRENT-STATE сам называет П-17 носителем этого долга.


N120 · MINOR · docs/product-requirements.md:57 · B-мёртвый-указатель

Срез: Глоссарий и продуктовые требования — docs/glossary.md · docs/product-requirements.md Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): механизм готов: tmctl export --pairs выравнивает пары побайтно по единицам редактуры (internal/pipeline/export.go:89-93) Почему протухло: Якорь РОДИЛСЯ верным 02.08 (fbefe33: тогда 89-93 были ровно комментарием «the --pairs column aligned byte-for-byte to how the edit wave concatenated the members») и сгнил дрейфом: сегодня 89-93 — поля структуры TotalUnits/PendingUnits/GhostRows/ConfigDrift, а механизм уехал на 144-152. Линтер якорей его НЕ ловит: форма без =токена сверяется только на существование файла. Улика — команда: sed -n '89,93p' backend/internal/pipeline/export.go; git show fbefe33:backend/internal/pipeline/export.go | sed -n '89,90p'; grep -n 'aligned byte-for-byte' backend/internal/pipeline/export.go Улика — вывод: TotalUnits int json:"total_units" PendingUnits int json:"pending_units" GhostRows int json:"ghost_rows,omitempty" // ConfigDrift is true when the CURRENT config renders a snapshot different ... --- fbefe33 (02.08) --- // F4: the $0 source manifest is authoritative. Under the wave model the export granularity is the EDIT // UNIT (the editor collapses a unit's member chunks into ONE final text) ... --- сегодня --- 147: // aligned byte-for-byte to how the edit wave concatenated the members) Положительный контроль: Положительный контроль инструмента: тот же grep находит механизм в том же файле сегодня — grep -n 'aligned byte-for-byte' backend/internal/pipeline/export.go147:; и python3 docs/scripts/counts.py --lint даёт «0 проблемных якорей», явно объявив «без токена — несверяемы: 721», т.е. слепота линтера подтверждена, а не предположена. Предложенное действие: Заменить якорь на форму с токеном, которую линтер сверяет: (backend/internal/pipeline/export.go=aligned byte-for-byte) — либо просто снять номера строк, как это уже сделано другими коммитами по файлам живых сессий (df4b064, 11e739d). Разрушающее: True ⚠ Опровергатель поправил действие: Действие верное по существу, но НЕ добавлять backend/ к пути: короткая зонно-относительная форма internal/pipeline/export.go — легитимная и поддержана самим линтером (LINT_PREFIXES_RAW включает "backend/", docs/scripts/counts.py:375). Менять надо только хвост: (internal/pipeline/export.go=aligned byte-for-byte) — тогда якорь переходит из класса «несверяемых 708» в класс «сверено по содержимому 137» и следующий переезд поймает гейт, а не ревизия.


N121 · MINOR · frontend/README.md:21 · A-дрейф

Срез: Доки замороженной зоны фронта — frontend/**/*.md (10 файлов, 5167 строк) Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): Маршрутов семь, и все снимаются (/showcase /scale /empty /loading /error /offline /partial) — маршрут выбирает мир фикстур. Почему протухло: Маршрутов ВОСЕМЬ: S4 завела /intake, и он есть в обоих замкнутых тестом списках. Приёмка S4 в том же дереве печатает «8 маршрутов, 11 сценариев». Улика — команда: sed -n '9,18p' frontend/src/api/scenarios.ts; sed -n '20,29p' frontend/scripts/shot.mjs; grep -o '8 маршрутов, 11 сценариев' frontend/docs/frontend-PROGRESS.md Улика — вывод: export const scenarios = ['showcase','scale','empty','intake','loading','error','offline','partial'] as const; const KNOWN_ROUTES = ['/showcase','/scale','/empty','/intake','/loading','/error','/offline','/partial']; 8 маршрутов, 11 сценариев Предложенное действие: «Маршрутов семь … /partial» → «Маршрутов восемь … /showcase /scale /empty /intake /loading /error /offline /partial». Разрушающее: True


N122 · MINOR · frontend/docs/BACKLOG.md:3 · B-мёртвый-указатель

Срез: Доки замороженной зоны фронта — frontend/**/*.md (10 файлов, 5167 строк) Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): они заходят строками единого бэклога через оркестратора (пример: строки 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 — проекция банка платформой) либо снять скобку с номерами, оставив механику «запросы к движку идут строками единого бэклога через оркестратора». Разрушающее: True ⚠ Опровергатель поправил действие: Предложенное действие («заменить пример на живой, например строку 169») ВРЕДНО и я бы его отклонил: живая строка закроется ровно так же, и шапка сгниёт снова — механизм рота встроен в замену. Норма D39.80 существует затем, чтобы исторический номер оставался цитируемым. Верное действие — минимальная приписка, снимающая ложное ожидание у читателя, идущего в живой бэклог: «(пример: строки 99102 … — все закрыты, тела в D39.122/D39.131)». Второй названный носитель (frontend-PROGRESS.md:249-250) — ДРУГОЙ и настоящий дефект класса C: он говорит «Ждёт движка» о закрытых строках, и вот его править нужно; он же единственный лежит в файле, куда оркестратор пишет.


N123 · MINOR · frontend/docs/BACKLOG.md:23 · B-мёртвый-указатель

Срез: Доки замороженной зоны фронта — frontend/**/.md (10 файлов, 5167 строк) Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): Туда же несколько токенов, которые ни разу не используются (--color-current-line, --color-hint). Почему протухло: Токена --color-hint в зоне больше нет вовсе — ни в tokens.css, ни в тестах, ни в коде; строка держит хвост на несуществующем токене. Вторая половина (--color-current-line, --font-mono) по-прежнему верна: оба лежат в именном списке reserved теста токенов. Улика — команда: grep -rn 'color-hint' frontend/src frontend/scripts; grep -rn 'color-current-line\|font-mono' frontend/src | head -6; grep -c '^ --color-' frontend/src/tokens/tokens.css Улика — вывод: (color-hint: ни одного хита в src/ и scripts/) src/tokens/tokens.css:22: --color-current-line: #152945; src/tokens/tokens.css:123: --font-mono: 'JetBrains Mono Variable', ui-monospace, monospace; src/tokens/tokens.test.ts:246:const reserved = ['--color-current-line', '--font-mono']; 22 (объявленных --color- токенов в tokens.css) Положительный контроль: Тот же греп по color-current-line и font-mono в frontend/src даёт хиты в tokens.css и tokens.test.ts, а grep -c '^ --color-' tokens.css = 22 — инструмент видит токены этого файла; ноль по color-hint — факт. Другие носители того же факта: frontend/src/tokens/tokens.test.ts:246 (живой список reserved — без --color-hint) Предложенное действие: Убрать --color-hint из перечня Ф-20, оставив --color-current-line и --font-mono (именно они стоят в списке reserved теста токенов). Разрушающее: True ⚠ Опровергатель поправил действие: Уточнение к формулировке: после снятия --color-hint в скобках остаётся ОДИН токен, поэтому «несколько токенов, которые ни разу не используются (--color-current-line, --color-hint)» должно стать «токен, который ни разу не используется (--color-current-line)» — иначе «несколько» повиснет над одним именем и получится новая мелкая неправда. --font-mono в этой скобке не перечисляют: он назван отдельным предложением выше.


N124 · MINOR · frontend/docs/FRONTEND_SESSION_PROMPT.md:2 · B-мёртвый-указатель

Срез: Доки замороженной зоны фронта — frontend/**/*.md (10 файлов, 5167 строк) Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): на него

так ссылаются S3_SESSION_PROMPT.md и frontend/README.md. Почему протухло: S3_SESSION_PROMPT.md в зоне не существует: промты S3/S3.5/S3.7 архивированы 14.08 в docs/archive/prompts/FRONTEND_S3*, зонные копии сняты. Довод баннера («файл живой, потому что на него ссылается S3-промт») опирается на исчезнувший носитель. Улика — команда: find /home/ubuntu/projects/textmachine-main -name 'S3*SESSION_PROMPT*' -not -path '*/node_modules/*'; ls docs/archive/prompts/ | grep -i FRONTEND Улика — вывод: (find: пусто) FRONTEND_S2_SESSION_PROMPT.md FRONTEND_S35_SESSION_PROMPT_2026-08-09.md FRONTEND_S37_SESSION_PROMPT_2026-08-10.md FRONTEND_S3_SESSION_PROMPT_2026-08-04.md Положительный контроль: find frontend/docs docs/archive/prompts -name 'SESSION_PROMPT' → 20+ файлов, включая frontend/docs/FRONTEND_SESSION_PROMPT.md и четыре FRONTEND_S* в архиве. Инструмент в этой области находит промты; ноль по S3*SESSION_PROMPT* — факт, а не опечатка. Другие носители того же факта: frontend/docs/frontend-PROGRESS.md:52 (S3_SESSION_PROMPT.md), frontend/docs/frontend-PROGRESS.md:80 (S35_SESSION_PROMPT.md «эта папка»), frontend/docs/frontend-PROGRESS.md:208 (S37_SESSION_PROMPT.md), frontend/docs/frontend-PROGRESS.md:147 (S4_SESSION_PROMPT.md «эта папка» — уехал в frontend/docs/archive/) Предложенное действие: «ссылаются S3_SESSION_PROMPT.md и frontend/README.md» → «ссылается frontend/README.md; промты S2S3.7 архивированы в docs/archive/prompts/FRONTEND_S*». Разрушающее: True ⚠ Опровергатель поправил действие: Действие верно, но ⚠ это НЕ повод трогать сам баннер как «протухший»: баннер — механизм, и он работает; ложна ровно одна ссылка внутри него. Правка минимальная и должна СОХРАНИТЬ довод («файл живой как носитель норм»), заменив мёртвое имя на архивное: «ссылается 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/».


N125 · MINOR · frontend/docs/STACK_DECISIONS.md:111 · B-мёртвый-указатель

Срез: Доки замороженной зоны фронта — frontend/**/*.md (10 файлов, 5167 строк) Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): | Браузер | @playwright/test 1.62.1 (точный пин) | Почему протухло: Зона зависит от пакета playwright 1.62.1, а не от @playwright/test: последнего нет ни в package.json, ни в дереве node_modules. Сессия, поставившая пин буквально, добавит вторую копию браузерного рантайма. Улика — команда: grep -n 'playwright' frontend/package.json; ls -d frontend/node_modules/@playwright; node -e "console.log(require('./frontend/node_modules/playwright/package.json').version)" Улика — вывод: 35: "@axe-core/playwright": "4.12.1", 53: "playwright": "1.62.1", ls: cannot access 'frontend/node_modules/@playwright': No such file or directory 1.62.1 Положительный контроль: ls -d frontend/node_modules/playwright → каталог есть, версия 1.62.1; grep -c '@playwright/test' frontend/package.json → 0 при том, что греп по playwright в том же файле даёт две строки. Инструмент в этой области находит пакеты — отсутствие @playwright/test не артефакт грепа. Другие носители того же факта: frontend/docs/FRONTEND_PLAN.md:122 Предложенное действие: В таблице §3 и в таблице пинов FRONTEND_PLAN.md:122 заменить @playwright/test на playwright (плюс @axe-core/playwright 4.12.1, который несёт axe-половину скриншот-цикла). Разрушающее: True ⚠ Опровергатель поправил действие: Действие верно; добавить причину, иначе следующая сессия «починит» обратно: тест-раннер @playwright/test зоне не нужен ПО ПОСТРОЕНИЮ — тесты гоняет vitest (шестой шаг check), а playwright работает библиотекой из scripts/shot.mjs. Формулировать как «playwright 1.62.1 (библиотека, не тест-раннер: тесты на vitest) + @axe-core/playwright 4.12.1». Второй носитель FRONTEND_PLAN.md:122 назван верно, я его прочитал.


N126 · MINOR · frontend/docs/STACK_DECISIONS.md:116 · E-второй-носитель

Срез: Доки замороженной зоны фронта — frontend//*.md (10 файлов, 5167 строк) Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): Нормативная поверхность — OpenAPI 3.1 в docs/api-contract/openapi.yaml: spectral линтует её шестым шагом npm run check Почему протухло: Шагов у check шесть, и spectral — ЧЕТВЁРТЫЙ. Тот же файл на строке 148 уже утверждает «шесть шагов», то есть носитель противоречит сам себе; вторая копия ошибки — FRONTEND_PLAN.md:124. Улика — команда: node -e "const p=require('./frontend/package.json');console.log(p.scripts.check.split('&&').map((s,i)=>(i+1)+': '+s.trim()).join('\n'))" Улика — вывод: 1: prettier --check . 2: eslint . --max-warnings 0 3: stylelint "src//*.css" 4: npm run contract 5: tsc --noEmit 6: vitest run Другие носители того же факта: frontend/docs/FRONTEND_PLAN.md:124, frontend/docs/STACK_DECISIONS.md:144-149 (там же верная редакция — «шесть шагов») Предложенное действие: В обоих носителях «шестым шагом» → «четвёртым из шести шагов». ⚠ Уже названо как ФС-6 в фикс-листе приёмки S4 (frontend-PROGRESS.md:469) — строка не исполнена. Разрушающее: True ⚠ Опровергатель поправил действие: Список носителей НЕПОЛОН: помимо STACK_DECISIONS.md:116 и FRONTEND_PLAN.md:124 та же ошибка стоит ТРЕТИЙ раз — frontend/docs/BACKLOG.md:16 (Ф-14: «spectral шестым шагом check, openapi-typescript генерит src/api/schema.ts»). Правя два, оставят третий. ⚠ И ещё: во всех трёх рядом стоит «нормативная поверхность — docs/api-contract/openapi.yaml» — а это на время фриза НЕВЕРНО (пинг №17, дописка: «зеркало = байт-копия временно НЕ действует, нормативен только канон docs/architecture/14-api-contract/»). Правку «четвёртым из шести» стоит вносить одним касанием с этой оговоркой, иначе исполнитель перечитает строку и унаследует вторую ложь.


N127 · MINOR · frontend/docs/frontend-PROGRESS.md:221 · C-исполненное-как-открытое

Срез: Доки замороженной зоны фронта — frontend//*.md (10 файлов, 5167 строк) Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): S4 (загрузка книги и запуск прогона на контракте 0.2.3, библиотека на масштабе, находки приёмки S3.7 — ждёт приёмки). Почему протухло: Приёмка S4 состоялась 15.08 и записана В ЭТОМ ЖЕ ФАЙЛЕ строкой 465 («ПРИНЯТО и залендено 267aa35, D39.135»). Два места одного файла говорят разное. Улика — команда: sed -n '220,221p' frontend/docs/frontend-PROGRESS.md; sed -n '465p' frontend/docs/frontend-PROGRESS.md Улика — вывод: 220: код зоны на английском; D39.129), S4 (загрузка книги и запуск прогона на контракте 0.2.3, 221: библиотека на масштабе, находки приёмки S3.7 — ждёт приёмки). 465:### 15.08 — приёмка S4 оркестратором №17: ПРИНЯТО и залендено (267aa35); контракт 0.2.3 в каноне (D39.135) Предложенное действие: «— ждёт приёмки» → «— ПРИНЯТ 15.08, D39.135; фикс-лист ФС-1…ФС-12 остаётся входом первого касания». Разрушающее: True ⚠ Опровергатель поправил действие: Единственная из пятнадцати находок, чей носитель — ЗОННЫЙ ЖУРНАЛ, куда оркестратор пишет по решению владельца (CLAUDE.md, таблица ролей). Её можно исполнить сейчас, не дожидаясь разморозки, — в отличие от остальных четырнадцати. Заодно поправить в той же строке 249-250 («Ждёт движка: манифест глав и пофазный прогресс — строки 99100, к S5 машиночитаемая таблица подписи — строка 101»): это тот же класс C в том же файле, строки закрыты D39.122, а машинная таблица с тех пор ещё и снесена D39.158.


N128 · MINOR · platform/deploy/README.md:428 · C-исполненное-как-открытое

Срез: Доки зоны платформы: platform/README.md · platform/deploy/README.md · platform/docs/{ENGINEERING_STANDARDS,PLATFORM_DIRECTION,STACK_DECISIONS}.md · platform/docs/p8-review/* · platform/docs/p9/* Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): ⚠ Выбирающего путь кода ещё нет: Supervisor.Workdir задаёт вызывающий, а вызывающий — воркер, Почему протухло: Воркер существует с пака P4: прогон спавнится в internal/runs/spawn.go, который задаёт Workdir: l.Workdir транзиентному юниту, а корень УЖЕ настройка — TM_PLATFORM_BOOKS_DIR (обязательный абсолютный путь), от которого каталог книги строится как filepath.Join(s.Cfg.BooksDir, id) и сохраняется колонкой books.workdir. То есть обещанное «когда он появится, корень становится настройкой» произошло. Заодно мёртв и указатель: «строка 103 единого бэклога» закрыта (D-нота «строки 103/135/165/102 закрыты»), а живой бэклог закрытых строк не хранит. Сегодня ingest.Supervisor вообще не имеет боевого вызывающего — он остался дев-путём, о чём этот же файл честно пишет строкой выше («Дев-путь супервизора с группой процессов остаётся дев-путём»). Улика — команда: grep -rn 'Workdir:' platform/internal/runs/spawn.go | head -2; grep -n 'dir := filepath.Join(s.Cfg.BooksDir, id)' platform/internal/books/books.go; grep -rn 'ingest.Supervisor\|Supervisor{' --include=*.go platform/ | grep -v _test | wc -l; printf 'row 103 in live backlog: %s\n' $(grep -c '^| 103 ' docs/PROGRESS.md) Улика — вывод: 168: Args: runner.TranslateArgs(l.Workdir, l.VerifyBank, s.Cfg.KeysFile, 170: Workdir: l.Workdir, 171: dir := filepath.Join(s.Cfg.BooksDir, id) 0 row 103 in live backlog: 0 Положительный контроль: grep -c "^| 175 " docs/PROGRESS.md → 1: шаблон грепа находит живые строки бэклога (175/202/224 = 1), значит ноль по 103 — отсутствие строки, а не отказ инструмента. Другие носители того же факта: platform/docs/PLATFORM_DIRECTION.md:101 (та же закрытая строка 103 как «носитель работы движка») Предложенное действие: Заменить абзац на действующий факт: корень библиотеки — настройка TM_PLATFORM_BOOKS_DIR (обязательный абсолютный путь), каталог книги = BooksDir/<book_id>, воркер спавна — internal/runs/spawn.go; ingest.Supervisor остался дев-путём. Ссылку на «строку 103» снять или заменить номером D-ноты, закрывшей её. Разрушающее: True ⚠ Опровергатель поправил действие: Действие принимается, с одной поправкой по существу: TM_PLATFORM_BOOKS_DIR не имеет дефолта — пустое значение означает «инстанс не принимает загрузок и POST /books вообще не монтируется» (config.go:159-166). Поэтому нельзя писать «её дефолт — этот каталог»: правильная формулировка — «/srv/textmachine это РЕКОМЕНДУЕМОЕ значение для юнита, а не дефолт кода; пусто = загрузки выключены». Иначе новая редакция заведёт ровно тот же класс дрейфа, что чинит.


N129 · MINOR · platform/docs/PLATFORM_DIRECTION.md:101 · B-мёртвый-указатель

Срез: Доки зоны платформы: platform/README.md · platform/deploy/README.md · platform/docs/{ENGINEERING_STANDARDS,PLATFORM_DIRECTION,STACK_DECISIONS}.md · platform/docs/p8-review/* · platform/docs/p9/* Вердикт пере-суда: ПОДТВЕРЖДЕНО Цитата (дословно из дока): Носитель работы движка — строка 103 единого бэклога (словарь событий); заводится Почему протухло: Указатели на строки единого бэклога, розданные по всему срезу, в шести случаях целят в строки, которых в живой таблице БОЛЬШЕ НЕТ: правило бэклога — «Закрытые строки здесь НЕ хранятся — они уходят в D-лог с номером решения» (docs/PROGRESS.md:59). Мертвы 100, 102, 103, 139, 145, 165, 174, 199, 200, 211. Часть из них цитируется как ПРОВЕНАНС (это терпимо), но три подаются как ЖИВОЙ носитель незакрытой работы и потому читаются как открытый долг: 101 («Носитель работы движка — строка 103», словарь денежных событий), 265 («Персист манифеста глав (строка 100) — несущий для латентности чтения» — read-модель построена и персистит), и deploy/README.md:430. Читатель, пошедший по номеру, находит пустоту и не может отличить «закрыто» от «потеряно». Улика — команда: for n in 100 102 103 139 145 165 174 199 200 211 175 202 224; do printf 'row %-4s: %s\n' $n $(grep -c "^| $n " docs/PROGRESS.md); done; sed -n '59p' docs/PROGRESS.md | cut -c1-70 Улика — вывод: row 100 : 0 row 102 : 0 row 103 : 0 row 139 : 0 row 145 : 0 row 165 : 0 row 174 : 0 row 199 : 0 row 200 : 0 row 211 : 0 row 175 : 1 row 202 : 1 row 224 : 1 Закрытые строки здесь НЕ хранятся — они уходят в D-лог с номером р Положительный контроль: Тот же греп на 175/202/224 даёт по 1 — шаблон работает и живые строки находит; ноль по остальным означает удалённую (закрытую) строку, а не сломанный инструмент. Другие носители того же факта: platform/docs/PLATFORM_DIRECTION.md:265 (строка 100), platform/deploy/README.md:430 (строка 103), :186 заголовок «строка 174», platform/docs/STACK_DECISIONS.md:246 (строка 165), :311 (строка 102), :414 (строка 199а), :348 (строка 200), :541 (строка 211), :545 (строка 145), :539/deploy:63 (строка 139) Предложенное действие: Пройти по десяти номерам: где строка закрыта, дописать рядом номер закрывшей D-ноты («строка 103, закрыта D39.xxx») — тогда указатель ведёт в D-лог, как того и требует правило бэклога; где номер подан как незакрытый долг (101, 265, deploy:430) — пере-написать по факту исполнения. Разрушающее: True ⚠ Опровергатель поправил действие: Действие верно по существу, координаты — нет: перед правкой пере-грепнуть носители, потому что шесть из семи номеров строк в other_carriers промахиваются. Действующие адреса: platform/docs/STACK_DECISIONS.md 267 (165), 317 (102), 357 (200), 414 (199), 547 (211), 554 (145), 560 (139); platform/deploy/README.md 64 (139), 179 (174), 430 (103); platform/docs/PLATFORM_DIRECTION.md 101 (103), 265 (100). Плюс замечание вне среза: тот же мёртвый номер несёт КОД — backend/cmd/tmctl/main.go:72 («run tmctl migrate (row 174)»); правка доков его не лечит, это пинг бэкенду, а не ход оркестратора.


N130 · MINOR · platform/docs/STACK_DECISIONS.md:566 · E-второй-носитель

Срез: Доки зоны платформы: platform/README.md · platform/deploy/README.md · platform/docs/{ENGINEERING_STANDARDS,PLATFORM_DIRECTION,STACK_DECISIONS}.md · platform/docs/p8-review/* · platform/docs/p9/* Вердикт пере-суда: (не выносилось) Цитата (дословно из дока): Админ-команды: tmplatformctl grant --user <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