textmachine/backend/docs/SEAM_PACK_FINDINGS.md

144 KiB
Raw Permalink Blame History

Доказательная база пака «входная дверь шва» (движковая половина D39.156)

Бэкенд-сессия 24.08.2026. Промт архивирован — docs/archive/prompts/BACKEND_SEAM_PACK_SESSION_PROMPT_2026-08-27.md; записка-план — SEAM_PACK_PLAN.md. Каждое число — с командой, которой получено; приёмка пере-раняет выборочно. Команды из backend/.

Карта чтения — что здесь ЖИВОЕ, а что улика закрытого пака. Четыре пака приняты и заленджены (D39.158), поэтому целиком файл читать не надо; входи по предмету: · живые остатки и PLAUSIBLE — §41, §24, §7, §9, хвосты §45 и §46 · · пинги, правок по которым НЕ делали — §10, §23, хвост §46 · · решения зоны с доводами — §21 (девять мелочей), §37 (модель флажка), §17 (выживший гейт) · · провенанс чисел — §18 (цена глагола), §29 п.1 (кап в количестве), §38 (хвост за потолком), §15 (цена рендера) · · правила протокола, вынесенные инцидентами — §11, §25, §35 · · что построено — §0, §28, §36 (состав трёх паков), §40 (счёт тестов). Попунктные чеки заказов (§12, §26, §43) вырезаны как прожитая приёмка — на их местах стоят адреса.

0. Состав пака

Новое: internal/membank/decisions.go (чистая логика решений) · internal/pipeline/bankdecisions.go (обвязка: лок, чтения, записи, отчёт) · cmd/tmctl/bankapply.go (CLI) · internal/seed/decode.go (строгие декодеры схемы) · internal/standdata/standdata.go (тест-резолв корпуса) + 6 тест-файлов и 2 фаззера. Тронуто: internal/config/book.go (конвенционные дефолты) · internal/pipeline/mining.go (дефолт-осведомлённые чтения) · internal/pipeline/status.go (status_version + artifacts{}) · internal/pipeline/export.go (export_version) · internal/pipeline/bankexport.go (bankTermIDmembank.TermID, одно определение на двоих) · internal/pipeline/refusal.go + cmd/tmctl/main.go (класс 14) · internal/store/store.go (+34 строки: LockProject) · internal/membank/memseed.go (строгий разбор) · internal/seed/seed.go (устав пакета) · cmd/tmctl/invocation.go, dotenv.go (--keys-file, --decisions).

1. Батареи

Батарея НЕ детерминированно зелена на этой машине, и это НЕ пак — читай до пере-рана, иначе красное будет отнесено на пак. Два пред-существующих -race-флейка в internal/store (§10 п.7) срабатывают примерно раз на 24 полных прогона: замерено go test ./... -race -count=1 ×4 → прогон 1 чист, прогон 2 — TestKillMinus9LosesAtMostOneCall (backend/internal/store/kill9_test.go:201=func TestKillMinus9LosesAtMostOneCall). Красное ИМЕННО в этих двух тестах ожидаемо; любое другое красное — регрессия. ⚠ Частота — из ЭТОГО замера, а не из §31: там ДРУГОЙ прогон (чередующийся A/B против HEAD, 5 раундов, обе стороны 5×FAIL), и он закрывает АТРИБУЦИЮ — «флейк пред-существует паку», — а не частоту.

На дереве пака: make batteryEXIT=0 на чистом прогоне (skip: TestMinerFullBookParity, TestHelperEventsRun, TestHelperKillLoop); make battery-stand без единого TM_*_DIREXIT=2, красен ровно TestMinerFullBookParity, причина — отсутствующий eval/exp16/data/jieba_dict_general_zh.txt; make lint0 issues.; go vet ./... && go vet -tags live ./... и gofmt -l . — чисто. Стенд-картина совпала с опорной точкой оркестратора ⇒ критерий §4.6 выполнен буквально. Батареи дерева, которое передавалось дальше, — §31 (дофикс), §38 (фикс2), §46 (фикс3).

2. Адверсариальные посадки: 16/16 краснеют

Харнесс сажает мутацию → гоняет тесты НАЗВАННОГО пакета → откатывает; он же снимает sha256 целей до прогона и проверяет после (TREE IS NOT PRISTINE). ⚠ Пакет пина ≠ пакет правки.

# посадка пакет пина что покраснело
M1 дефолт срабатывает на НЕпустом ключе config TestDeclaredDecisionPathIsNotDefaulted — ⚠ пин СНЯТ вместе с предметом (ключи retired, D39.158 п.5; §40)
M2 частичный результат переживает отказ (чистый слой) membank TestAllOrNothing +2
M3 частичная ЗАПИСЬ переживает отказ (обвязка) pipeline TestBankApplyRefusesAPartialSet
M4 повторный вызов пишет файл заново pipeline TestBankApplyRepeatIsAByteNoOp +2
M5 лок берётся БЛОКИРУЮЩЕ pipeline TestBankApplyRefusesABusyProject (по 10-сек. границе)
M6 читающий глагол принимает ключи cmd/tmctl TestOnlyTranslateAcceptsKeys
M7 занятый лок отказывает классом 10 вместо 12 pipeline TestBankApplyRefusesABusyProject
M8 пишется status: auto вместо approved (мина №1 строки 199) membank TestApproveWritesAnExplicitApprovedStatus +2
M9 решение, тронувшее только rejects, пере-рендерит и delta pipeline TestADeclineDoesNotRewriteTheDelta
M10 промоушен теряет алиасы строки банка membank TestPromotionKeepsTheBankRowAliases
M11 voice/address сида не сверяются с результатом membank TestDecliningACharacterAVoiceProfileNamesIsRefused
M12 два отказа по одной поверхности проходят как два терма membank TestTwoDeclinesOfOneSurfaceAreIllFormed
M13 пред-существующая поломка проглатывается молча membank TestAPreExistingCollisionDoesNotBlockAnUnrelatedDecision
M14 документ из одних комментариев снова не грузится seed TestDecodeAcceptsAnEmptyDocument
M15 ruby-чтения выброшены из судимого набора membank TestApprovingARubyReadingOfASeedTermIsRefused
M16 инертный отказ по алиасу дельты принимается membank TestDecliningAnAliasOfAnApprovedTermIsRefused

3. Живые пробы на реальной книге стенда

В КОПИИ вне рабочего дерева (cp -r books/gu-zhenren/minirun-verify2 <scratch>/live, пути пере-указаны на копию). В books/ не записано ничего.Те же пробы пере-гонялись после дофикса (§19) и после фикс2 (§39) — там картина СВЕЖЕЕ; здесь оставлено то, что позже не пере-снималось.

проба результат
занятый лок (flock держит сторонний процесс) exit=12, wall=0.02s
decline над рукописной дельтой md5 дельты не изменился, комментарии владельца целы
reject-файл ИЗ ОДНИХ КОММЕНТАРИЕВ exit=0 (до фикса R1 было exit=10, seed: EOF)
все seed/delta/signature/auto-bank стенда под строгим загрузчиком 21 из 21 чисто (независимо: 29/29 на более широкой выборке, включая книги dovodka/role-topology/tenant-panel); пере-снято после дофикса как 45 из 45 — §31

4. Замеры

что число вердикт
цена глагола (bank-apply --dry-run, /usr/bin/time) ⚠ числа пака (5 000 термов → 1.60с/133МБ · 20 000 → 9.08с/350МБ) не воспроизводятся, а по памяти занижены примерно вдвое — НЕ наследовать: пере-замер §18, худший случай §29 п.1 датум для таймаута платформенного воркера берётся из §18/§29, не отсюда. Что осталось верным: бюджет платформы на ОДИН вызов движка — 60с (platform/internal/runs/reconcile.go:280); реальная книга (кап майнера 200 за акт, сид стенда 58) — под секундой; цена — двойной прогон проверки документа, и он же не даёт пред-существующей поломке блокировать починку
контеншн (20 одновременных bank-apply) 2×0, 18×12, после гонки seed-lint OK лок арбитрирует чисто; два нуля законны — второй взял лок после первого и оказался no-op
деньги (sha256 базы до/после) 7ea61ccecada341b → идентичен глагол не сдвинул ни байта хранилища. Сильнее SELECT по ledger: не требует sqlite и покрывает всё хранилище
детерминизм (тот же документ в ДВУХ процессах, cmp) побайтово идентичны закрывает единственный реалистичный источник недетерминизма в Go — порядок обхода map
FuzzDecisionDocument 487 163 исполнения, 0 падений оракулы: не паникует · всё-или-ничего · ни одно решение не теряется молча (каждый входной индекс ровно один раз в acceptedrejected) · принятый результат грузится реальным загрузчиком
FuzzSeedDocumentRoundTrip 199 896 исполнений, 0 падений parse(render(A)) = A, рендер — неподвижная точка; на этом держится байтовая идемпотентность

Оба фаззера исполняются обычным go test по seed-корпусу (15 кейсов) ⇒ входят в make battery; корпус живёт в GOCACHE, в репозиторий не попадает.

5. Дефекты, найденные и закрытые

Все воспроизведены мной перед правкой, каждый под пином.

# дефект пин
R1 регрессия пака: документ ИЗ ОДНИХ КОММЕНТАРИЕВ перестал грузиться (seed: EOF) — гард проверял «пусто после TrimSpace», а байты комментария не пусты. Бил по glossary_seed КАЖДОЙ книги. Тестируется EOF от декодера, пре-чек снят M14
R2 стоил бы ДЕНЕГ: --decisions принимался ВСЕМИ командами и молча игнорировался (флаги в одном FlagSet, гард стоял только у --keys-file) ⇒ tmctl translate --decisions d.json разбирался чисто и запускал ПЛАТНЫЙ прогон. Класс: флаг, которого команда не исполняет, обязан отказывать, а не игнорироваться TestOnlyBankApplyAcceptsDecisions
R3 промоушен строки банка терял её АЛИАСЫ (кластер майнера); auto-bank строка затем дропается как key clash ⇒ терм молча сужается до своего src M10
R4 проверка «результат загрузится» не включала ruby-чтения: прогон грефтит kana-чтения на сид-термы как ФАЙРЯЩИЕ алиасы до проверки коллизий ⇒ approve на чтение сид-терма принимался и убивал прогон M15
R5 то же по voice/address: сид называет персонажа, чей терм живёт в дельте; отказ от терма принимался и валил следующий прогон M11
R6 отказ по поверхности, которая АЛИАС одобренного терма дельты, принимался как applied, а терм продолжал её рендерить M16
R7 пред-существующая поломка файлов проглатывалась молча: applied, exit 0 книге, чей следующий прогон уже обречён. Поле preexisting_problems — сообщается, НЕ отказывает M13
R8 решение, тронувшее только reject-лист, пере-рендеривало рукописную дельту побочно M9
R9 два ОТКАЗА по одной поверхности с разными sense проходили как два терма: второй затирал note первого, оба отчитывались applied M12
R10 отказ ОТКАЗУ по терму сида выдавал довод от approve («строка дельты уронила бы UNIQUE») — для decline это ложь, он строки не пишет TestDecliningASignedSeedSurfaceIsRefused (проверяет ТЕКСТ довода)

Не подтвердились (опровергнуты исполнением; НЕ поднимать заново): --keys-file не принимает redrive — механика верна, вывод «канала нет» ложен, конвенционный .env у него есть · canonical_rewrite предупреждает в том же вызове, что и выполняет — проекция это отдельный вызов, не пишущий ни байта · нормализатор на латинице удалил бы кейс-вариант — пред-существующая идентичность поверхности во всём движке, паком не введена · проекция молчит о пере-оценке редакторской волны — сумма квотируется цифрой и гейтится согласием в другом месте.

6. Изменение КОНТРАКТА, требующее ратификации

Заведён класс полосы 14 decisions_rejected. Отвергнутый по существу набор решений ≠ сломанный деплой: platform/internal/ingest/exit.go DeploymentFault относит 10 к «условию, общему для всех книг хоста» (ось D39.134: при 12 действует время, при 10 человек — но НЕ тот же), и по 10 набор решений ОДНОГО пользователя был бы списан на оператора. ⚠ Уточнение, снятое мной: DeploymentFault покрывает 10, 12 и 13 одинаково, а зовут её только пути интейка (readmodel.go:227, pgstore/books.go:568) — у bank-apply ноль вызывающих на платформе, то есть мис-классификация перспективная, а не живая. Старый читатель деградирует безопасно: Refused(14) истинно ⇒ OutcomeRefused. 10 сохранён для документа, не прочитавшегося КАК документ решений. Гейт тотальности таблицы: TestTheRefusalTableIsTotal (класс без номера тихо падал бы в catch-all 19).

7. Что НЕ доказано

  • Рантайм-вердикт по ПЛАТНОМУ пути — PLAUSIBLE. Доказано: ключ из --keys-file доезжает до заголовка (Authorization: Bearer fake-deploy-key), и БЕЗ флага тот же прогон отказывает конфигом с именем незаполненного env. НЕ доказано: что реальный провайдер примет ключ и что SaaS положит файл туда, куда укажет флаг — это платформенная половина строки 211, чужая зона.
  • Цепь целиком не гонялась — §8.
  • Живой translate на стендовой книге не гонялся: санкции на платные вызовы у пака нет.

8. Цепь целиком — что пак замерить не смог, и чем это закрыто позже

Центральное продуктовое утверждение пака как ОДНО исполнение: подписной стоп красный → bank-apply промотит все dst-несущие термы и отклоняет WHICH-only → резюм → стоп ГАСНЕТ. Части доказаны порознь (явный status: approved пишется — M8; результат грузится — sweep 21/21 и фаззер; TestTheRunReadsTheConventionalDelta доказывает, что прогон читает именно тот файл), но цепь целиком паком не гонялась ни разу.

Почему — проверено, не заявлено: банк-стоп на этой машине не воспроизводится вовсе (pipeline.yaml требует contrast_path: …/eval/exp16/data/jieba_dict_general_zh.txt, lsNo such file or directory — та же причина, по которой красен парити), и даже с контрастом до стопа надо дойти ОПЛАЧЕННОЙ черновой волной. Синтетический контраст = подделать замер.

Заказ приёмке снят исполнением дофикса: цепь построена в репозитории (internal/pipeline/bankchain_test.go, $0 на фейковом провайдере, ОДИН проект от стопа до погасшего стопа) — но на СИНТЕТИЧЕСКОМ контрасте, и ограничение названо в комментарии самого теста и в §24. Живой стенд стоп по-прежнему не воспроизводит (§41). Что при этом НЕ дефект: свежая пачка ≤200 термов реверс-секции на большой книге — кап режется ПОСЛЕ фильтров (§22; постоянный якорь этого факта — internal/miner/emissioncap_test.go).

9. Остаточные риски — решение оркестратора, не моё

  • Канонический пере-рендер необратим на уровне ДВИЖКА. Смягчено трижды: проекция сообщает canonical_rewrite ДО мутации · ДАННЫЕ переживают round-trip полностью (теряются форматирование и комментарии шапки) · на стенде восстановление УЖЕ есть — репозиторий книг ВЕРСИОНИРУЕТ эти файлы (git -C books ls-files | grep -c 'mined-delta\|mined-rejects'30, check-ignore → не игнорируется), откат через git -C books checkout; на SaaS каталог книги платформенный и снапшот — её сторона шва. Бэкап в движке не строил: механизм не заказан, носителя нет, существующий покрывает обе стороны. Владельцу: провенанс из комментариев шапки переносить в поле note:.
  • Размер документа решений не ограничензакрыто дофиксом: два капа (1 МиБ на чтение и 5 000 решений на применение, оба классом 14). Довод и замер худшего случая — §29 п.1.

10. Пинги — предметы ВНЕ карты пака, правок не делал

  1. rubyToCandidates мёртв в проде — пере-проверено 02.09. grep -rn "rubyToCandidates" --include=*.go . → 4 хита: определение, два комментария и ЕДИНСТВЕННЫЙ вызов — из теста (backend/internal/membank/memseed_test.go:204=cands := rubyToCandidates). ⚠ Адрес механизма из первой редакции («seeding.go:36-38 строит manualSrcs и никуда не передаёт») МЁРТВ: ruby-путь переехал, и сегодня прогон читает чтения в backend/internal/pipeline/bankmaterialize.go:84=RubyReadingsForBook(r.Book.BookID) и отдаёт их ТОЛЬКО в AttachRubyAliasesToManual — аргумента manualSrcs в проде не строит вообще НИКТО, то есть кандидатная половина просто не подключена. Следствие прежнее: строк Source:"ruby" в банке не возникает, а платформа держит для них ветку словаря (platform/internal/ingest/bank.go termOriginOriginAnnotated).
  2. Гейт TestDispatchCommandsCoversTheSwitch слеп к case с КОНСТАНТОЙ — считает только *ast.BasicLit, поэтому команда, объявленная константой, невидима гейту, чей смысл в том, что дефект строки 176 «отрастал трижды». Обнаружено посадкой; я вернулся к литералу (конвенция файла if cmd == "seed-lint"), поэтому дыра не эксплуатируется.
  3. redrive — платный путь без канала ключей аргументом. Промт: «флаг только у translate», сделано ровно так. Канал у него есть (конвенционный .env), и платформа его не зовёт (runner/engine.go строит только translate/status/manifest/export). Развилка следующему паку.
  4. Проекция берёт тот же лок, что и применение ⇒ «поправить термин при живом прогоне» даёт 12 даже на ПРЕВЬЮ, на всю длину прогона. Платформенный пак обязан это спроектировать, а не обнаружить.
  5. Пути mined_delta/mined_rejects опубликованы в status --json. Законно по п.1 закона, но эти файлы версионного конверта не несут, а опубликованный путь читается как приглашение. Единственная изгородь против их парсинга платформой — комментарий. Самое мягкое место в «формат знает ОДНА сторона».
  6. Схема сида без omitemptyyaml.Marshal(seed.File{}) печатает все ключи, включая пустые. Уже существовало (miner_emit.go DeltaYAML рендерит так же), глагол наследует. Смена тегов = правка формата данных (internal/seed/seed.go это оговаривает).
  7. Два флейка в internal/store, пред-существующие. TestTheSeamIsNotChargedToTheStoreOperationBudget и TestKillMinus9LosesAtMostOneCall; тесты НЕ трогал (D39.121: правка теста ради зелени недопустима). Оба меряют ВРЕМЯ (первый сознательно сжимает opTimeout, второй гоняет helper против таймера SIGKILL); покрытие независимо подтверждает, что LockProject/Release этими тестами не исполняются вовсе. ⚠ Первый A/B (3 раунда × 10) дал kill9 2/30 на HEAD против 0/30 на дереве пака — это НЕ значит «пак улучшает»: на таком числе событий это шум (Фишер p≈0.5); решающее и достаточное — что флейк живёт на дереве, которого пак не касался. Окончательная атрибуция — 5-раундовый A/B в §31.

11. Два правила протокола, вынесенные инцидентами сессии

  1. Эталон самопроверки снимается ДО первого изменения, и харнесс мутаций обязан фингерпринтить цели. Незавершённый прогон mutate.py оставил посадку M4 в дереве ~40 минут, выглядящей продакшн-кодом, и сторонний ревьюер снял с этого дерева замеры (он же собрал tmctl из незакоммиченного дерева, не сняв отпечаток, — на класс наступили обе стороны). Посадку поймал и правило теперь держит пин TestBankApplyRepeatIsAByteNoOp: он же документирует и форму инцидента, и довод (инод плюс рукописный комментарий в шапке — то, что пере-рендер уничтожает, а проверка содержимого не заметила бы). ⚠ Класс повторился следующей сессией ВНУТРИ самого харнесса — §25; отпечаток целей и каталога у инструмента сегодня свой — §16.
  2. Одиночный прогон — не замер. Атрибуцию флейков я сперва вывел из одного прогона («зелено в изоляции ⇒ нагрузка»), затем другой одиночный прогон дал ровно обратное. Решает только чередующийся A/B (§10 п.7).

12. Чек заказа и дифф тестов — сняты

Попунктный чек шестнадцати пунктов промта и пофайловый дифф ^func Test этого пака были прожитой приёмкой и вырезаны: сам заказ живёт в docs/archive/prompts/BACKEND_SEAM_PACK_SESSION_PROMPT_2026-08-27.md, доказательства по каждому пункту — в §1§11 выше, а сводный счёт тестов по всем трём пакам («удалено НОЛЬ», обе команды, поимённые переименования) — в §40.


Дофикс той же двери (сессия 25.08.2026)

Промт (16 пунктов §4) архивирован — docs/archive/prompts/BACKEND_SEAM_FIX_SESSION_PROMPT_2026-08-27.md; записка-план — SEAM_FIX_PLAN.md. Секции 012 выше — улика приёмки, не тронуты.

14. Батареи

База ДО правок, на дереве пака: make batteryEXIT=2, красен ровно TestKillMinus9LosesAtMostOneCall — один из двух названных промтом пред-существующих -race-флейков. Флейк воспроизвёлся на дереве, которого дофикс ещё не касался: атрибуция своя, не наследованная. make lint0 issues. Батареи ПОСЛЕ правок и обе стенд-строки — §31.

15. §4.1 — корень формата: замер, решение, цена

Триггер шире клейма промта и НЕ совпадает с поправкой записки прошлой сессии. Прогнал матрицу «значение поля → рендер → разбор» на обеих схемах (проба вне рабочего дерева, gopkg.in/yaml.v3 v3.0.1):

значение что рендерится round-trip
"\nтекст", "\n\nтекст", "\n\tx" |4- + строки не разбирается (did not find expected key)
"\n" |4+ разбирается, значение становится ""тихая потеря
"\n x" |4- + 10 пробелов разбирается, значение становится "x"тихая потеря
"текст\n", "a\nb", " x", "\tx", "\r\nx" скаляр/блок ок

Два следствия, оба несущие. (1) Клейм промта «бьёт по ЛЮБОМУ свободному текстовому полю» ВЕРЕН; поправка записки пака («dst и sense не ломаются») — НЕТ, ломается любое строковое поле обеих схем (проверено на src/dst/sense/note и на rejects[].note). (2) Заказанная проверка render → parse две тихие потери пропустила бы: документ разбирается, значение другое, дверь отчитывается applied. Инвариант обязан быть «прочиталось И совпало».

Механика — сверена с исходником библиотеки, а не выведена. Внутри элемента блочной последовательности эмиттер увеличивает отступ на жёсткую двойку, чтобы «пропустить индикатор - » (emitterc.go:239-241, комментарий там же), а индикатор отступа блочного скаляра печатает best_indent (emitterc.go:1848-1852). Расхождение — ровно ширина - . Апстрим починил это в линии-преемнике (yaml/go-yaml PR #150, ишью #65); v3.0.1 — то, что в go.mod, и оно не поддерживается.

Путь «починить рендер стилями yaml.Node» ЗАКРЫТ: (*yaml.Node).Encode внутри сам гоняет marshal→unmarshal и падает на том же входе — узел не построить (замерено). ⚠ Популярный обход enc.SetIndent(2) НЕ применён и применять его нельзя: на этом пине он делает документ разбираемым и молча теряет ведущий перевод строки (замерено: "\nне термин""не термин", "\n\necho a\necho b\n""\necho a\necho b\n"). Это обмен громкой ошибки на тихую правку слов владельца — ровно тот класс, который канон запрещает абсорбировать.

Что сделано. (а) seed.File.Normalize()/RejectFile.Normalize() — ОДНО правило на все текстовые поля обеих схем (TrimSpace, пустые коллекции → nil), применяется на РЕНДЕРЕ, не на чтении; (б) дверь так же нормализует свободный текст входящих решений — то же правило, которое она и загрузчик уже применяли к src/sense/dst; (в) RenderSeedFile/RenderRejectFile ДОКАЗЫВАЮТ байты: marshal → разбор настоящим строгим декодером → reflect.DeepEqual; провал — ошибка, никогда не байты; (г) байты, которые чистый слой проверил, он же и отдаёт наверх (ApplyResult.DeltaBytes) — второго рендерера в обвязке больше нет; (д) характеризационный тест на саму библиотеку (TestYamlV3CannotReadBackWhatItWritesForALeadingNewline) краснеет в день, когда дефект починят: обход перестаёт быть молчаливым.

(а) и (б) взаимно прикрывают друг друга — это замерено, а не предположено: посадка, снимающая одну из них, выживала, пока пин не был написан на случай, до которого другая не достаёт (I → пред-существующее значение В ФАЙЛЕ; K → идемпотентность повторного решения с пробельным текстом). Оба теперь краснеют.

Цена инварианта — замерена, не оценена. Два бинаря из одного дерева, второй с выключенной проверкой равенства, один документ на 10 000 решений, по 3 прогона (/usr/bin/time -f '%e %M'): с проверкой 16.52 / 18.10 / 18.30 с, 391392 МБ · без неё 14.46 / 17.27 / 17.39 с, 344408 МБ. По времени ⇒ ≈5 % (медиана 18.10 против 17.27).

Эти секунды НЕ сравнимы с таблицей §18, где тот же документ на 10 000 решений даёт 14.53 / 15.73 / 16.54 с на ТОМ ЖЕ бинаре с проверкой. Замеры сняты в разные моменты под разной внешней нагрузкой (параллельные сессии), и абсолютные числа между секциями складывать нельзя. ≈5 % выведены ТОЛЬКО из пары выше — она снята подряд, двумя бинарями из одного дерева, и внутренне согласована. По памяти пара с таблицей §18 сходится (391392 МБ против 391.7/391.9). По ПАМЯТИ разница НЕ измерена: диапазоны перекрываются, и максимум без проверки (408 МБ) выше любого прогона с ней. Основная цена глагола — НЕ проверка (см. §18).

16. §4.11 + свои посадки: 36 мутаций, 35 краснеют, 1 выживает осознанно

Харнесс — в репозитории: cmd/tmmutate + каталог cmd/tmmutate/mutations.json, у каждой записи названо СВОЙСТВО, за которое она отвечает. Три сессии подряд писали такой инструмент в скретчпад и теряли его вместе с сессией; каталог — это и есть память о том, что кто-то счёл достойным охраны. Прогон — на КОПИИ дерева (cp -a, равенство копии проверено побайтно), сам инструмент снимает отпечаток каждой цели и каталога целиком и краснеет на «TREE IS NOT PRISTINE». ⚠ Число записей в этой секции — снимок дофикса (36); каталог с тех пор рос каждым паком. Актуальное берётся командой python3 -c "import json;print(len(json.load(open('cmd/tmmutate/mutations.json'))))".

База ДО пинов: 20 из 24 выжили (⚠ снято на РАННЕЙ редакции каталога из 24 записей — исторический замер, а не команда, которую можно повторить) — то есть все десять слепых пятен §4.11 подтверждены исполнением, плюс новый код дофикса был не запинен. Итог после всех итераций: 36 посадок, 35 краснеют на СВОЁМ пине, одна (G) записана как аргументированный выживший (§17). Каталог поэтому не отчёт, а ГЕЙТ: исход, не совпавший с записанным, роняет прогон — в том числе неожиданно ПОЙМАННАЯ посадка, что значит «запись устарела, обнови её».

Две собственные посадки поймали два своих же ВАКУУМНЫХ пина: пин на позиционные субъекты держал строки без идентичности ПЕРВЫМИ, поэтому их позиции никогда не двигались; пин на полноту довода отказа искал имя команды там, где оно и так стоит — в обвинении «not by %q», а не в объяснении. Оба переписаны, оба теперь краснеют. Правило, подтверждённое этим дважды: сначала посадка, потом пин — пин, написанный первым, проверяет то, что и так верно.

$ cd <копия backend> && go run ./cmd/tmmutate -root . -logs <dir>

Пофайловая таблица «посадка → пакет пина → что покраснело» здесь НЕ дублируется: её живой и авторитетный носитель — сам каталог backend/cmd/tmmutate/mutations.json (у каждой записи id, why — свойство, за которое она отвечает, — и package), а имена пинов инструмент называет ПОИМЁННО в выводе прогона. Снимок в отчёте протух бы в первом же паке: записей было 36 на дату дофикса, 56 после фикс2, 60 после фикс3.

17. Единственная выжившая мутация — и почему пин на неё невозможен

G (байтовый гейт). Заказ §4.11(ж) просит поведенческий пин. Его нет и быть не может: гейт недостижим за первым, и это доказывается, а не предполагается.

deltaChanged = DeltaTouched && changedDoc(диск, next, пусто). DeltaTouched ставится только когда решение реально изменило документ, а next — канонический рендер УЖЕ ИЗМЕНЁННОГО документа. Чтобы changedDoc вернул false, байты на диске должны БЫТЬ этим рендером — но тогда документ, разобранный из них, уже равен результату, никакое решение ничего не меняло, и DeltaTouched был бы false. Нормализация щели не открывает: файл, чьи значения несут окружающие пробелы, в себя же не рендерится.

Диспозиция: гейт ОСТАВЛЕН как второй рубеж на случай, когда *Touched станет грубее сегодняшнего, и запинен как КОНТРАКТ ФУНКЦИИ (TestChangedDocIsTheSecondFenceAndItsContractIsPinnedDirectly), включая правило «отсутствующий файл + пустой документ — НЕ создавать». Снести его я не стал: изгородь, за которую я вижу не до конца, дешевле оставить, чем убрать.

18. Замеры — своей названной методикой

Методика: /usr/bin/time -f '%M %e', 3 прогона на размер, медиана, машина без нагрузки, bank-apply --dry-run (записи нет), документы из N approve с короткой заметкой.

N решений размер документа wall (мин/мед/макс) maxRSS (мин/макс)
1 000 82.9 КБ 0.57 / 0.58 / 0.74 с 51.9 / 52.1 МБ
5 000 423 КБ 4.91 / 5.18 / 5.36 с 171 / 215 МБ
10 000 848 КБ 14.53 / 15.73 / 16.54 с 391.7 / 391.9 МБ
20 000 1.72 МБ 46.72 / 52.16 / 52.20 с 740 / 762 МБ

Разночтение по памяти между паком и приёмкой закрыто в пользу приёмки. Пак дал 133/217/350 МБ на 5k/10k/20k, приёмка 199/395/798 МБ, мой замер — 203/392/745 МБ. Числа приёмки воспроизводятся, числа пака занижены примерно вдвое. По времени сойтись не удалось: пак дал 1.60/3.62/9.08 с, у меня 5.2/15.7/50 с — в 35 раз медленнее. Проверкой раунд-трипа это НЕ объясняется (§15: ≈5 %), бинарь пака я не пере-собирал, и объяснения у меня нет. Число, которое поедет в платформенный пак как датум для таймаута, — моё или приёмочное, не паковское.

Почему цена сверхлинейная — факт исходника, а не гипотеза: каждый пер-решенческий помощник сканирует весь документ (findTerm, dropTerms, addReject, aliasOwner, deltaHoldsSurface — все O(len(документа))), поэтому проход по N решениям Θ(N²) по построению. Чего я НЕ доказал: что именно это доминирует в замеренном времени. Единственная проба-индекс, которую я на это потратил, была написана неверно (пере-строение индекса на каждом добавлении) и дала обратный результат; дальше я не пошёл — это вне заказа. Ускорение — пингом (§23), не правкой.

⚠ Эта таблица снята на документах из approve с заметкой (≈85 байт на решение) и потому НЕ описывает худший случай, который допускает кап в БАЙТАХ. См. §29 п.1: минимальный decline — ≈35 байт, и 1 МиБ пускает 28 900 таких решений, то есть 308 секунд. Вывод «1 МиБ ≈ 12 000 решений ≈ 20 с» из первой редакции этого отчёта неверен, и кап пришлось делать двойным.

Деньги: sha256sum <project_db> до и после всех живых проб — 7ea61ccecada341b… идентичен. Ни один платный вызов не сделан и не запрашивался.

19. Живые пробы на КОПИИ книги стенда

cp -r books/gu-zhenren/minirun-verify2 <scratch>/live/book, все пути переписаны НА КОПИЮ (⚠ мёртвые абсолютные пути лежат в ТРЁХ файлах: book.yaml, pipeline.yaml и pairs/zh-ru.yaml — в последнем prompts_root). В books/ не записано ничего.

проба результат
немигрированный проект (схема 11 против 15) exit=13, текст называет tmctl migrate — ратифицированный класс 13 живьём
tmctl migrate на копии v11 -> v15, пре-миграционный бэкап, integrity_check green
блокер §4.1: approve с note, начинающейся с \n exit=0, changed=true; в файле note: проба ведущего перевода строки
второй вызов по той же книге (то, что раньше умирало) exit=0; seed-lint OK: 0 fail-louds, 0 shared-key collisions
проекция того же документа mode=projection, canonical_rewrite_delta=true, canonical_rewrite_rejects=false (§4.13 п.1 виден живьём: раньше горело из-за файла, который вызов не открывает), md5 обоих файлов не изменились
ручная строка владельца 陈博 после записи gender/decl/translit_policy целы (мерж, не замена)
повтор того же решения changed=false, already_applied, inode не изменился
§4.2: починка ОДНОЙ из двух битых строк exit=0 (было exit=14), отчёт называет обе оставшиеся поломки; вторая починка — тоже exit=0, файл грузится
§4.5: документ 2.16 МБ exit=14, «is over 1048576 bytes … split it», файлов не создано
§4.6: manifest --json и status --json несут ОДИН конверт artifacts{}, все четыре пути абсолютные
§4.3: два ключа на один путь exit=10, отказ называет оба ключа и путь
§4.4: --decisions= / --decisions "" / --keys-file= все три отказывают (было: проходили на ПЛАТНЫЙ путь)
§4.14: --keys-file на redrive довод теперь называет redrive, backup, migrate поимённо

20. Фаззеры

go test ./internal/membank/ -run '^$' -fuzz <F> -fuzztime=90s -parallel=2, по 90 с каждый:

фаззер исполнений падений
FuzzDecisionDocument (оракул 4 переписан, §4.15) 176 495 0
FuzzSeedDocumentRoundTrip 83 663 0
FuzzDecisionFieldsThroughTheDoor (новый, заказ §4.1) 32 713 0

Крашеров в дерево не записано (testdata/fuzz отсутствует).

Новый фаззер закрывает СТРУКТУРНУЮ слепоту старого, названную промтом: тот фаззит сырой YAML, то есть его корпус по построению состоит из строк, уже переживших YAML-разбор, а вход двери — JSON, на который YAML не накладывает ничего. Новый гоняет произвольные строки полей Decision через ApplyDecisions в рендер и обратно, и его оракул — «прочиталось И совпало», на ОБОИХ документах.

Оракул 4 старого фаззера снят с тавтологии (§4.15): он пере-выполнял то, что дверь уже сделала внутри. Теперь он проверяет свойство, которого не проверяет ни один одиночный вызов: результат — НЕПОДВИЖНАЯ ТОЧКА, повторное применение тех же решений к записанным БАЙТАМ даёт already_applied по всем и не трогает ни одного документа.

21. §4.13 — девять мелочей, по каждой решение и довод

предмет решение довод
canonical_rewrite — ИЛИ по двум файлам ПОЧИНЕНО поля стали пер-файловыми (canonical_rewrite_delta / ..._rejects), и каждое ложно для документа, которого вызов НЕ трогает. Общий флаг оставлен как ИЛИ — потребителю, которому нужно только поднять диалог, читать одно поле. Предупреждение про файл, который вызов не откроет, — предупреждение ни о чём
note нельзя ОЧИСТИТЬ ПОЧИНЕНА НЕСОГЛАСОВАННОСТЬ, поведение названо две половины двери расходились: повторный approve заметку СОХРАНЯЛ, повторный decline СТИРАЛ. Одно правило: отсутствующий note значит «не решено», как kind. Цена названа в комментарии Decision.Note: очистить заметку через дверь нельзя, только заменить другими словами. Стирание провенанса по умолчанию хуже — и оно делается по случайности, а не намеренно
book_id не проверяется на путевую безопасность ПОЧИНЕНО book_id — это ИМЯ ФАЙЛА для четырёх движковых файлов, два из которых теперь несут решения ВЛАДЕЛЬЦА. Требование: один элемент пути. Проверено на всех 22 book.yaml репозитория и стенда — нарушителей ноль, и платформенный bk_… проходит
каталог книги без права записи → exit 1 вне полосы ОТКЛОНЕНО, пинг воспроизведено: exit=1, store: open lock file: permission denied. RefuseStoreOpen относит в полосу только ErrLocked(12) и SchemaMismatch(13), всё прочее — 1, и так у ВСЕХ глаголов. Проблема прав хоста — это и есть инфраструктурный сбой; 1 здесь корректен. Спор не про класс движка, а про то, как платформа читает 1 (§23)
проекция создаёт <project_db>.lock ОТКЛОНЕНО, сказано вслух лок — арбитр (закон п.2), а проекция без него — проекция ни от чего. Инвариант «проекция не трогает ДОКУМЕНТЫ» держится; «не трогает ничего» никогда не было верно и теперь так и написано в шапке ApplyBankDecisions
порядок двух записей пессимален для decline ОТКЛОНЕНО, комментарий исправлен правильного порядка нет: delta-first безопасен для approve и небезопасен для decline, обратный — наоборот. Прежний комментарий утверждал больше, чем доказывал, и это исправлено. Настоящий довод в другом: вызов всё-или-ничего и пере-вычисляется целиком, поэтому повтор сходится из ЛЮБОЙ половины, и каждая половина оставляет терм НЕРЕШЁННЫМ, а не решённым неверно. Атомарность пары требует журнала на два файла — механизма, которого эта дверь не заслужила
artifacts.bank_export печатается безусловно ОТКЛОНЕНО, документировано поле именует МЕСТО, а не наличие; публиковать «существует ли» значит публиковать факт о моменте, который к чтению потребителя уже прошёл, и приглашать гонку check-then-open. Сказано в комментарии StatusArtifacts
aliasOwner — первое совпадение ОТКЛОНЕНО приёмка расхождения не воспроизвела. Проверка идёт по РЕЗУЛЬТАТУ (после dropTerms), а «своя поверхность» живёт внутри самого aliasOwner — отдельного побега быть не должно (§30 п.2). Остаток — только чьё имя окажется в тексте отказа; сам отказ наступает в любом случае, а такой документ уже ловится ApprovedSharedKeyCollisions
проекция отвечает «что», а не «почём» ПИНГ с рекомендацией (§23) это вопрос ДИСПОЗИЦИИ, как и сказано в заказе

22. §4.9 — заказ исполнен не предсказанием, а фактом

Дверь не может предсказать, погаснет ли стоп: следующий прогон пере-майнит источник, и его реверс-секция режется капом 200 ПОСЛЕ фильтров (reverseSectionTerms), то есть на большой книге приедет свежая карта. Множества кандидатов в этом процессе нет вовсе — считать его значит пере-ингестить и пере-нарезать книгу, а это то, что делает status дорогим, и не то, чем занимается $0-глагол решений.

Что дверь может видеть — карту подписи, которую написал прошлый прогон, и решён ли каждый её сурфейс, ПО ПРАВИЛУ САМОГО ПРОГОНА (ownerHandled(unsignedEngineSurfaces(...)), вызывается тот же код, не его копия). Отсюда поле отчёта:

"signature": { "map": "<путь>", "surfaces": 2, "undecided": 0 }

Пустой map значит «прогон до границы банка ещё не доходил». В режиме проекции число — то, что БУДЕТ после применения (единственное чтение, которое может иметь превью).

СТАРАЯ ФОРМУЛА ЭТОГО АБЗАЦА ОТМЕНЕНА, и вот действующая. Пак писал «undecided > 0 — факт, что стоп не погаснет»; под моделью флажка (D39.144, фикс2) поле не отвечает на вопрос о стопе НИ В ОДНУ СТОРОНУ, и это записано в самом коде: backend/internal/pipeline/bankdecisions.go:92=IT DOES NOT ANSWER «погаснет ли стоп». Причина — механическая: стоп срабатывает, когда СВЕЖЕ-намайненная карта следующего прогона держит кластер вне предъявленной памяти (hasUnpresentedCluster, internal/pipeline/mining.go), а этого набора ещё не существует ни у кого за $0. Что поле ЕСТЬ: информационный счётчик для экрана подписи — «о скольких поверхностях последней карты владелец ещё не высказался», считанный ТЕМ ЖЕ правилом, которым прогон сворачивает решения в банк (ownerHandled(unsignedEngineSurfaces(...))).

Пины сегодня — цепи internal/pipeline/bankchain_test.go (TestTheChainOwnerDecidesTheWholeMapAndTheRunGoesOn · TestTheStopIsAFlagNotAGateOnDecisions): решение по всей карте прогон НЕ останавливает, а стоп — флажок, а не гейт на решения.

23. Пинги — предметы ВНЕ карты дофикса, правок не делал

  1. exit 2 при OOM читается платформой как успех (заказ §4.5 прямо называет это пингом). Движок при нехватке памяти выходит fatal error: out of memory + код 2, а platform/internal/ingest/exit.go читает 2 как «команда СДЕЛАЛА работу». Класс общедвижковый. Кап §4.5 не даёт bank-apply стать первым, кто до него доедет, но самого класса не снимает.
  2. Каталог книги без права записи даёт exit 1, и платформа спишет проблему прав ХОСТА в бюджет попыток КНИГИ. Класс движка корректен (см. §21); чинить надо чтение кода 1 на платформе.
  3. Проекция отвечает «что применится», а не «почём». Рекомендация: не строить. Закон п.7 про «почём ДО подтверждения» уже исполнен там, где есть деньги (rebill-смета, status --json), а этот глагол $0 по построению — единственная его «цена» это пере-редактура, которая квотируется и гейтится в другом месте и другим согласием. Поле «$0» в отчёте было бы шумом. Решать оркестратору.
  4. Карта подписи не удаляется после погасшего стопа (os.Remove в mining.go не встречается). Пред-существующее. Потребитель, читающий файл как ответ на «есть ли что подписать», читает уже подписанную карту. Зафиксировано ЯВНО тестом цепи как текущее поведение; честный ответ на тот же вопрос — signature.undecided (§22).
  5. Цена глагола Θ(N²) по построению (§18) — это то, почему кап на КОЛИЧЕСТВЕ стоит на 5 000, а не выше. Лечится индексом по ключу терма в applyOne/dropTerms и по нормализованной поверхности в addReject/dropReject; вне заказа, не делал. Поднимать кап без этого нельзя.
  6. Апстрим-дефект yaml.v3 (§15) лечится по-настоящему двумя способами, и оба вне пака: (а) тип скаляра схемы с MarshalYAML, возвращающим узел с явным стилем (проверено по исходнику: явный стиль проверяется РАНЬШЕ ветки блочного скаляра, encode.go:557-570); (б) переезд на go.yaml.in/yaml/v4 — там этот случай починен, но это релиз-кандидат и смена module path сразу в двух зонах. Мой обход несёт ссылку и характеризационный тест, а не «похоже, баг yaml».
  7. Цена доказывающего рендера ≈5 % времени (§15); по памяти разница внутри разброса прогонов и НЕ измерена. Плюс notCanonical теперь тоже разбирает документ. Названо, чтобы следующий замер не приписал это чему-то другому.

24. Что НЕ доказано и что НЕ удалось

  • Рантайм-вердикт по ПЛАТНОМУ пути остаётся PLAUSIBLE. Санкции на платные вызовы у дофикса нет, и ни одного платного вызова не сделано (sha256 базы стенда не сдвинулся). Это ожидаемый исход, а не дефект отчёта.
  • Цепь §4.12 доказана на СИНТЕТИЧЕСКОМ контрасте, а не на книге стенда: банк-стоп на этой машине не воспроизводится вовсе (pipeline.yaml требует jieba-контраста, которого клон не получает по построению — строка 123). Ограничение названо в комментарии самого теста: синтетика меняет, КАКИЕ кандидаты предложены, но не то, проходит ли подписанная строка фильтр подписи. Для вопроса «гаснет ли стоп» это корректно; для качества майнера — нет.
  • Расхождение по ВРЕМЕНИ с замером пака (35×) не объяснено (§18). Проверкой раунд-трипа оно не объясняется, бинарь пака я не пере-собирал.
  • Что доминирует в сверхлинейной цене — не измерено (§18): Θ(N²) следует из исходника, но какая доля замеренного времени приходится на неё, я не установил; единственная проба была написана неверно.
  • Мутация G не имеет поведенческого пина и не может его иметь (§17) — вместо него запинен контракт функции.
  • Два пред-существующих -race-флейка в internal/store не тронуты (D39.121). Атрибуция закрыта СВОИМ A/B против HEAD (§31), а не унаследована.
  • Вклад дофикса во время internal/pipeline под -race не измерен (§31): мои собственные тесты этого пакета — 1.9 % его времени, остальное между базой 474 с и позднейшими 558 с (оба под нагрузкой, см. §31) я не атрибутирую, потому что внешняя нагрузка между замерами не контролировалась.

25. Правило протокола, вынесенное инцидентом ЭТОЙ сессии

Инструмент, который сторожит от подсадки, сам подсадил. Первая версия tmmutate читала-правила-писала файл ОДИН РАЗ НА ПРАВКУ: у мутации B две правки в ОДНОМ файле, «нетронутая» копия второй снималась с уже пропатченного файла, и sha256-самопроверка сравнивала пропатченное с пропатченным же хешем — проходила. ⚠ Цена: дерево копии осталось мутированным, и два полных раунда измерений после этого шли по мутированному коду — несколько пинов отчитались как поймавшие мутации, к которым не имели отношения. Вскрыто ровно тем, чему учит §11 п.2: один подозрительный «RED», пере-проверенный изолированным прогоном, не воспроизвёлся.

Починено дважды: одно чтение и одна запись на файл (порядок объявлен контрактом в комментарии), плюс второй независимый рубеж — отпечаток ВСЕХ файлов каталога, снятый на заведомо чистом дереве и пере-проверяемый ПЕРЕД каждой мутацией и после последней. ⚠ Равенство копии рабочему дереву проверяется КОМАНДОЙ (diff -rq --exclude=bin плюс sha256sum по всем *.go/*.json), а не записанной константой: дерево с тех пор менялось, и повторяемо равенство двух сторон, а не число.

Общая форма правила, шире этого инструмента: самопроверка, чей эталон снят ПОСЛЕ первого изменения, не проверяет ничего. Эталон снимается один раз, на состоянии, которое известно как чистое.

26. Чек заказа и дифф тестов дофикса — сняты

Попунктный чек шестнадцати пунктов §4 и пофайловый дифф ^func Test дофикса вырезаны как прожитая приёмка. Адреса: заказ — docs/archive/prompts/BACKEND_SEAM_FIX_SESSION_PROMPT_2026-08-27.md (комплектность сверялась механически: grep -cE '^### 4\.' <промт> → 16); доказательства по каждому пункту — §15§25 выше; сводный счёт тестов и переименования — §40; каталог посадок — backend/cmd/tmmutate/mutations.json.

⚠ D39.158 называет «вход §26» — читатель, пришедший по этой ссылке, попал верно: адреса выше.

28. Состав дофикса

Новое: internal/seed/normalize.go (канонический вид документа) · internal/membank/problems.go (вердикт загрузчика по субъектам) · cmd/tmmutate/ (харнесс мутаций + каталог посадок; счёт записей на дату дофикса и команда актуального счёта — §16) · internal/pipeline/bankchain_test.go. Тронуто: internal/membank/decisions.go (доказывающий рендер, нормализация запроса, набор-осознанный алиас, перевёрнутое окно, байты результата, note не стирается) · memseed.go, memvoice.go (вердикт как список субъектов; Error() побайтно прежний) · internal/pipeline/bankdecisions.go (кап, контекст, пер-файловый canonical_rewrite, signature{}, байты из чистого слоя) · mining.go (signatureMapPath и rejectSurfaces — одно определение на двоих) · status.go, manifest.go (единственный производитель конверта путей; конверт на манифесте) · internal/config/book.go (попарно различные пути, book_id как элемент пути) · cmd/tmctl/invocation.go (гарды на присутствие, довод отказа), bankapply.go, main.go (контекст) · Makefile (пер-пакетный таймаут назван явно — §31, это единственная правка в самой батарее и она ничего не ослабляет) + семь тест-файлов.

29. Ревью старшей моделью: три дефекта, каждый воспроизведён МНОЙ и починен

Ревью запрашивалось по решениям, где заказ оставлял форму на сессии. Каждую находку сессия воспроизвела сама прежде, чем чинить — чужой результат, взятый на веру, ровно так же становится нормой. У двух подтверждённых решений ревью дало довод СИЛЬНЕЕ, и довод заменён в комментариях кода: у конверта путей на манифесте — что бамп версии выбросил бы все сохранённые сайдкары и пере-нарезал каждую книгу ради аддитивного поля (компромисс назван вслух: манифест семантически про дерево глав, путь артефакта там гость); у SIGTERM — атомарность и порядок записей вместо длины непрерываемого участка.

1. Кап в БАЙТАХ не ограничивает ВРЕМЯ — мой довод был неверен

Расчёт «1 МиБ ≈ 12 000 решений ≈ 20 с» брал ≈85 байт на решение из документа с заметками. Минимальное законное решение — {"action":"decline","src":"t1"}, ≈35 байт. Пере-мерено тем же методом (/usr/bin/time -f '%e %M', документы из минимальных decline):

N размер wall maxRSS
5 000 174 КБ 11.32 с 40 МБ
12 000 421 КБ 57.96 с 70 МБ
20 000 709 КБ 153.5 с 112 МБ
28 900 1 029 369 Б — ПОД капом 308.2 с 133 МБ

То есть байтовый кап пускал документ, который платформа убивает по своему 60-секундному бюджету на КАЖДОЙ попытке, навсегда, — а строка отказа «split it», ради которой класс 14 и заводился, не печаталась никогда. Это тот самый вечный ретрай, который полоса отказов существует чтобы превращать в действие. Починено: второй кап — на КОЛИЧЕСТВЕ решений, 5 000, тот же класс 14 (11 с в худшей замеренной форме, пятикратный запас внутри бюджета платформы). Байтовый кап остался пред-разборным сторожем и больше ничего про время не утверждает. Пин TestTheDecisionCOUNTIsCappedBecauseTheBYTESCannotBound, мутация W.

2. Субъект поломки нёс РЕНДЕРИНГ — тупик на оси, у которой нет двери

Три проверки из четырёх называют dst в тексте сообщения (человеку он нужен), а dst — ровно то, что решение меняет. Ключом по тексту поломка про терм выглядела НОВОЙ в тот момент, когда дверь меняла рендеринг этого терма: строка дельты с gender: badvalue (ось, у которой в Decision НЕТ поля вообще) плюс решение, меняющее dst того же терма, давали exit=14 — одна и та же поломка по обе стороны под двумя разными рендерингами, и починить её через дверь нельзя вовсе. Это §4.2 на уровень ниже. Починено: subjectOf(class, src, sense, since, until) — субъект строится ТОЛЬКО из класса поломки и идентичности терма, никогда из поля, которое дверь переписывает; экспортированные []string-обёртки не изменились, ни один чужой вызов не тронут. Пин TestAFaultTheDoorHasNoChannelForCannotDeadlockIt, мутация Y.

3. И то же самое ещё уровнем выше: поломки, которые ЗАГРУЗЧИК прячет

Три проверки живут ПОД загрузчиком банка и на документе, который не грузится, не выполняются вовсе. Значит, как только решение чинит загружаемость, всё, что они впервые увидели, читается как новое — и владельца отвергают за починку того, что мешало всему. ⚠ Первая починка была ПРАВИЛОМ МАСКИ и оказалась ХУЖЕ дефекта: правило снято целиком, итоговое лечение и его довод — §30 п.1.

4. Латентная потеря данных на пути nil-байтов

Если рендер ТЕКУЩЕГО документа падает и это пред-существующая поломка, она сообщается, а не отвергает (и это правильно) — но тогда res.DeltaBytes == nil, а обвязка считала DeltaTouched && changedDoc(raw, nil, …) → true и записала бы файл нулевой длины поверх всех одобрений владельца. До этого входа не дошёл ни ревьюер, ни сессия (130+ подобранных значений после нормализации прошли round-trip); но renderProved существует именно потому, что множество ломающих значений не известно целиком, и незащищённый nil противоречит его собственной предпосылке. Починено одной строкой: nil-байты не пишутся никогда. Поведенческого пина нет по той же причине, по которой его нет у G, — вход недостижим; инвариант чистого слоя (accepted ⇒ DeltaBytes != nil) держит FuzzDecisionFieldsThroughTheDoor.

5. Три комментария несли неверные доводы — исправлены

В этом проекте комментарий — контракт, по которому действует следующая сессия, поэтому неверный ДОВОД при верном коде опаснее опечатки. Один из трёх стоит того, чтобы стоять и здесь: «путь yaml.Node закрыт» преувеличивало — закрыт Encode, а РУЧНАЯ сборка узла с явным стилем работает и отвергнута по ЦЕНЕ (второй рендерер всей схемы), а не по невозможности (§23 п.6).

6. Ещё две правки по ревью

Нечитаемая карта подписи возвращалась как {surfaces:0, undecided:0} — побайтно то же, что «всё решено», единственный ответ, который это поле не должно давать по случайности. Заведено поле unreadable; пин TestASignatureMapThatCannotBeReadDoesNotReportAsAllDecided, мутация Z. ⚠ Здесь закрыта только ошибка РАЗБОРА; ошибку ЧТЕНИЯ (chmod 000) закрыл §34. У tmmutate опасное значение было ДЕФОЛТНЫМ (-root . правит живое дерево). Флаг стал обязательным; в шапке названо и то, что отпечаток снимается на старте и потому сертифицирует только дерево, которое было чистым в тот момент.

30. Интервальная самоверификация субагентом: ещё пять дефектов, все воспроизведены и починены

Ревью гонялось В СЕРЕДИНЕ работы против явных критериев, read-only, в копии; дерево под ним двигалось, поэтому его вердикты пере-исполнены на финальном снимке. Каждый дефект сессия воспроизвела своим прогоном прежде, чем чинить; воспроизведение каждого теперь живёт в его пине, а не здесь.

1. Правило маски проглатывало ливлок, который вносил САМ вызов

Собственная починка §29 п.3 («если до вызова документ не грузился, а после грузится, эти поломки сообщаются и не отвергают ничего») оказалась ХУЖЕ дефекта. На входе «битая строка + Hero→Odin с алиасом Shared» и решениях «починить битую строку + одобрить Shared→Dva» дверь диагностировала детерминированный ливлок матчера (firing key "shared" is shared by different terms), записала документ и отчиталась exit 0, назвав СВОЮ поломку чужой. Комментарий правила («состояние после такого вызова никогда не хуже, чем до») был ложью ровно здесь: до — банк не грузился (громкий стоп), после — грузится и инжектит ливлок. И два опубликованных поля (Preexisting, preexisting_problems, оба обещающие «этот вызов их не вызывал») становились ложными утверждениями.

Починено иначе и без исключений: правило маски снято целиком; вместо него ParseBankSeed возвращает записи, которые он ПОСТРОИЛ, даже когда отказывает. Тогда три проверки под загрузчиком выполняются и на «до», и на «после», сравнение по субъекту работает как везде, и оба случая решаются одним правилом: починка загружаемости больше не отвергается (поломка видна с обеих сторон), а внесённый ливлок отвергается (его субъекта на «до» нет). Пины — обе половины TestRepairingAnUnloadableDocumentIsNotRefusedOverWhatItUNMASKS, мутация AD.

2. Побег §4.7 принимал инертный отказ

ownsSurface(in.Delta, …) пропускал проверку, когда поверхность была термом дельты сама по себе: на дельте Nick→Nik + Hero→Nik2 (alias Nick) отказ Nick принимался, строка Nick снималась, а Hero ПРОДОЛЖАЛ фарить Nick — ровно тот вред, ради которого §4.7 и заказан. Замерено дополнительно: во всех остальных входах побег — no-op (после dropTerms алиасом никто не владеет), то есть он менял ответ только во вредную сторону, и зеркальный случай, ради которого он якобы существовал, работает без него. Починено: побег снят — ⚠ ownsSurface в коде НЕТ, и заводить заново не надо. Пин TestADeclineIsRefusedWhenTheSurfaceGOESONFiringAsAnotherTermsAlias, мутация AB.

3. И то же правило ОТВЕРГАЛО законный отказ, с доводом, который неверен

aliasOwner не смотрел на СТАТУС: на дельте Hero (status: auto, alias Nick) отказ Nick отвергался доводом «"Nick" is an ALIAS of the approved term … already excluded from proposals». Обе половины довода ложны — строка не approved, а unsignedEngineSurfaces выбрасывает mined-строки со статусом ≠ approved, то есть её поверхности из предложений НЕ исключены и отказ как раз осмыслен. status: auto в дельте — не угол, а ровно то состояние, о котором говорит первая мина формата. Починено: aliasOwner судит только подписанные строки (пустой статус = approved, как дефолтит загрузчик). Пин TestDecliningTheAliasOfAnUNSIGNEDRowIsAccepted, мутация AC.

4. §4.14 был исполнен неполно — и тем же способом, против которого заказан

Довод отказа --keys-file не описывал bank-apply — глагол, который этот пак и добавил. А тест, написанный ЗАКРЫТЬ §4.14, перечислял четыре команды ручным списком — ровно слабость, против которой поднят §4.11(в), воспроизведённая в тесте про неё же. Починено: довод называет все девять; тест перечисляет из dispatchCommands и требует, чтобы имя каждой команды встречалось в её собственном отказе. Мутация AF.

5. Гейт тотальности читал ОДИН файл и одну форму объявления

refusalClassesFromSource разбирал refusal.go и требовал явного vs.Type == RefusalClass. Класс, объявленный в соседнем файле пакета или конверсией RefusalX = RefusalClass("x"), был ему невидим — та же слепота, ради снятия которой гейт и переписан. Починено: parser.ParseDir по всему пакету и обе формы объявления.

Плюс два, которые ревью нашло, а я закрыл превентивно

Позиционные субъекты («record N has no identity») не были запинены вовсе — добавлен TestTwoRecordsWithNoIdentityDoNotBlockEachOthersRepair и мутация AE. Фаззер двери возвращался рано на отказе и потому не видел бы возврата блокер-класса: оракул отказа усилен — отказ обязан не выдать байтов, не отчитаться о принятых решениях и не изменить ни один документ.

Что ревью подтвердило исполнением (не переделывал)

Нормализация идемпотентна на 50-строчном адверсариальном корпусе и КОПИРУЕТ (проверено мутацией выхода при неподвижном входе) · ложного провала renderProved найти не удалось (400 000 случайных строк по алфавиту управляющих/пунктуации/CJK, невалидный UTF-8, NUL, C0/C1, одиночные суррогаты) · оба капа не off-by-one · после второй проверки контекста ничего не пишется, defer lock.Release() стоит сразу за захватом · общности не нарушено ни одной новой строкой · TestDecisionByBankID и пин на инъективность действительно независимы (все три константы пере-считаны в Python по спецификации из комментария TermID, а не из его же выхода — backend/internal/membank/decisions_test.go:348=Computed outside Go, from the spec in TermID's own comment) · SeedProblems.Error() побайтно равен прежнему склеенному сообщению.

Общность — проверена грепом по девяти тронутым файлам: в Go, которую дофикс тронул, нет ни одного литерала пары/письменности/орфографии; нормализация — strings.TrimSpace, определённый Юникодом, а не скриптом.

Унаследованное замечание, не дефект пака: всё, на чём эта дверь ключуется, проходит через internal/text/norm.go, где таблица trad2simp и ветка катакана→хирагана живут В GO, а не в langpack'е. Пара, которой в репо нет, деградирует до NFKC+lowercase — то есть не блокирована, но фолд Go-резидентный. Пингом, не правкой: это шире двери.

31. Батареи после всех правок — и одна правка в самой батарее

Сняты ПОСЛЕ рефакторинга §33 и после всех правок по воркфлоу-ревью §34 — то есть описывают дерево, которое передавалось: make lint0 issues. · go vet ./... + go vet -tags live ./... + оба прохода -vettool → чисто · gofmt -l . → пусто · make batteryEXIT=0, полностью зелено, включая оба названных промтом -race-флейка · make battery-standEXIT=2, красен ровно TestMinerFullBookParity (нет jieba-контраста eval/exp16/data/jieba_dict_general_zh.txt — он лежит по внутрирепозиторному пути, но вне git по построению). Больше ничего.

ОДНО утверждение о детерминизме, чтобы приёмка не гадала. Два store-теста (TestKillMinus9LosesAtMostOneCall, TestTheSeamIsNotChargedToTheStoreOperationBudget) ведут себя так: в ПОЛНОЙ батарее на ненагруженной машине проходят; под нагрузкой параллельных сессий падают; а запущенные ИЗОЛИРОВАННО через -run падают детерминированно и на обеих сторонах A/B — они сознательно сжимают opTimeout, и в одиночку сжатого времени не хватает. То есть: красное в этих двух — ожидаемо и НЕ пак; зелёное в них — тоже нормальный исход; красное в любом ДРУГОМ тесте — регрессия.

Корпус под строгим загрузчиком — 45 из 45 чисто, двумя командами:

$ for f in $(find books backend -name "*seed*.yaml" -o -name "*mined-delta*.yaml" \
      -o -name "*auto-bank*.yaml" -o -name "*mined-signature*.yaml" | grep -v '\.bak'); do
      tmctl seed-lint --seed "$f" >/dev/null || echo "FAIL $f"; done            # → 30 OK / 0 FAIL
$ find books backend -name "*mined-rejects*.yaml" | wc -l                        # → 15
$ … | while read f; do python3 -c 'import sys,yaml;d=yaml.safe_load(open(sys.argv[1]))or{};
      assert set(d)<={"rejects"};
      [__import__("sys").exit(1) for r in (d.get("rejects") or []) if set(r)-{"src","note"}]' "$f" \
      || echo "FAIL $f"; done                                                    # → 15 OK / 0 FAIL

Атрибуция красного — ЗАМЕРОМ против HEAD, а не «промт сказал»

git worktree add --detach <scratch> HEAD — дерево БЕЗ пака вообще (internal/membank/decisions.go там нет). Чередующийся A/B, 5 раундов, -race -count=1: два названных store-теста — MINE 5×FAIL · HEAD 5×FAIL; pipeline/TestManifestOfAnOlderDocumentVersionIsRefusedMINE 5×ok · HEAD 5×ok (в стенд-батарее он падал ЧИСТО от нагрузки). Изолированный прогон обеих сторон даёт БУКВАЛЬНО одинаковый вывод: migrate_test.go:41: store: apply migration 9: context deadline exceeded. Атрибуция закрыта — ни пак, ни дофикс тут ни при чём, и это СВОЙ замер, а не унаследованное утверждение. Тесты не тронуты (D39.121).

⚠ Одна правка В САМОЙ батарее, и вот замер, из-за которого она сделана

Makefile: go test ./... -race -count=1… -timeout=20m (в обеих целях).

Почему. Go по умолчанию даёт пакету 10 минут. internal/pipeline под -race идёт 557.7 с (/usr/bin/time, машина с ТРЕМЯ параллельными агент-сессиями, load average 312 в моменты замера), и под этой нагрузкой два прогона батареи подряд закончились panic: test timed out after 10m0s по всему пакету — это читается как падение и падением не является. ⚠ На действительно свободной машине приёмка намерила 92.8/97.6 с: разброс ×6 даёт сама нагрузка, оба замера настоящие, и довод в Makefile поэтому записан диапазоном с условиями — без слова idle и без привязки к машине.

Это НЕ подгонка гейта под зелень: таймаут — не утверждение. Ни один тест не ослаблен, ни один не удалён; те же самые тесты просто доходят до конца. Пакет, который действительно ЗАВИС, всё так же падает — на двадцатой минуте вместо десятой.

Чего НЕ доказано: сколько из 557.7 с добавил дофикс. База в начале сессии на нетронутом дереве пака дала 474.2 с, но внешняя нагрузка между двумя замерами не контролировалась, поэтому разница НЕ атрибутируется. Что измерено точно: все одиннадцать новых тестов этого пакета стоят 10.5 с под -race из 557.7 с, то есть 1.9 % — не они.

32. Записка предыдущей сессии — попунктно, включая одно ОПРОВЕРЖЕНИЕ

Записка бэкенд-сессии пака (жила в хвосте её промта, ныне архивированного) пройдена целиком и уехала в работу почти вся: онбординг в код, три ловушки стенда (мёртвые абсолютные пути в трёх файлах, схема v11, нет jieba — все три сработали ровно как описано), правило «сначала посадка, потом пин», требование пере-мерить память своей командой и проверить красное в internal/store ЗАМЕРОМ против HEAD. Проверка против HEAD была не лишней: она дала более сильный результат, чем ожидала записка (§31).

Одно место оказалось ВРЕДНЫМ. Записка «поправляла» промт: «dst и sense с ведущим \n round-trip НЕ ломают». Опровергнуто замером §15: ломается любое строковое поле обеих схем. Прав был ПРОМТ, а не записка, — и если бы сессия поверила записке, фикс был бы заплаткой на одно поле.

Вывод для оркестратора: записка не помечена «проверено исполнением» построчно, а её самый уверенный абзац оказался неверным. Полезен ФОРМАТ такой записки, а не её авторитет.

33. Рефакторинг по итогам вопроса о качестве Go-кода

Две точки входа двери были вдвое длиннее всего остального в тех самых файлах, которые они представляют, и обе разобраны на именованные фазы — чистое извлечение, без изменения поведения:

ApplyDecisions      → resolveAll · refuseSeedConflicts · foldAccepted · refuseInertDeclines · refuseNewFaults
ApplyBankDecisions  → openDecisionRequest · readBookState · decisionReport · changedFiles · writeDecisionFiles

⚠ Фаза decisionReport позже заменена единым конструктором исходов finishReport (фикс2, §36§37) — грепать надо его. Тело ApplyDecisions стало последовательностью из пяти судящих фаз, из которых ровно одна что-то меняет (foldAccepted): это и есть контракт двери, теперь видимый в самом коде, а не только в комментарии.

Считать длину функции надо по ТЕЛУ (от ^func до первой строки ровно }), а не от ^func до следующего ^func: второе правило на методах даёт мусор, и именно им была получена несуществующая «норма репозитория в 75 строк» — такой нормы в проекте НЕТ. Замерено по ТЕЛУ: две точки входа — 92 и 90 строк против 42 у следующей по длине функции тех же двух файлов; максимумы пакетов совсем другие — 218 (Status, internal/pipeline) и 105 (loadVoiceSections, internal/membank); после разбора самая длинная функция обоих файлов — 44 (signatureState).

И вот чем доказательство эквивалентности ОКАЗАЛОСЬ слабее, чем здесь было написано. Первая редакция утверждала «весь каталог пере-прогнан, как записано». На деле рефакторинг сломал ДВЕ записи каталога — они ссылались на переменную, которую он убрал из области видимости, — а инструмент считал провал СБОРКИ красным, то есть две записи не проверяли ничего, а отчёт называл тесты, которые не могли отработать. Нашло воркфлоу-ревью; обе записи пере-выражены, у инструмента появился третий вердикт — §34.

34. Воркфлоу-ревью дофикса: шесть осей, все находки воспроизведены и закрыты

Третье и последнее ревью дофикса — воркфлоу, а не одиночный агент (форма — §45). Полосы выбраны по тому, чего не касались два предыдущих ревью: гонка двух bank-apply · последствия новых полей для платформы (read-only) · эквивалентность только что сделанного рефакторинга · сам cmd/tmmutate (он теперь в репозитории, значит продакшн-код) · честность хендофф-документов · общность и безопасность данных. Каждая находка перепроверена своим прогоном прежде, чем чинить; правки доводов в комментариях и правки в самом этом документе живут по месту, здесь — только то, что осталось фактом.

Главное: две посадки каталога НЕ КОМПИЛИРОВАЛИСЬ — и это внёс мой же рефакторинг

P-alias-judged-on-the-set и AB-alias-escape патчат тело refuseInertDeclines, ссылаясь на in.Delta, которое рефакторинг §33 убрал из области видимости, — а инструмент считал их КРАСНЫМИ, потому что red был буквально terr != nil, и провал СБОРКИ выглядел так же, как поймавший пин.

Починено на двух уровнях. Обе записи пере-выражены под новую сигнатуру и краснеют на своих пинах поимённо; у инструмента появился ТРЕТИЙ вердикт (NOTHING — ни один тест не назвал себя, гейт падает) плюс обязательная зелёная базовая линия пакета ДО первой посадки. ⚠ Оба механизма и провенанс инцидента живут в самом инструменте — cmd/tmmutate/main.go, комментарии «THE PACKAGE MUST BE GREEN BEFORE ANYTHING IS PLANTED» и «NOT a red …»; второй копии этот отчёт не держит.

Продуктовые дефекты — все воспроизведены мной

что как это выглядело починка
остановленный вызов ПЕЧАТАЛ, что применил на отмене контекста отчёт уходил в stdout с mode:"apply", changed:true и state:"applied" — при нуле записанных байт. Отличал их только код выхода mode:"stopped", changed:false, и accepted[] очищается так же, как на пути отказа (это поле потребитель перебирает). Пин TestAStoppedCallDoesNotPRINTThatItApplied, мутация AG бьёт по всем трём полям
нечитаемая карта подписи ≠ «всё решено» фикс §29 п.6 закрыл ошибку РАЗБОРА, но не ошибку ЧТЕНИЯ: chmod 000 возвращал нулевое состояние — побайтно то же, что «нечего подписывать». Тест назывался ровно про это свойство и его не покрывал os.IsNotExist отделён от прочих ошибок чтения; пин расширен случаем chmod 000, мутация AH
отказ предупреждал о перезаписи, которой не будет откат «всё-или-ничего» не сбрасывает *Touched, поэтому canonical_rewrite горел на ОТВЕРГНУТОМ вызове. Диалог «сейчас будут уничтожены комментарии» для вызова, который ничего не пишет, — так настоящее предупреждение перестают читать поля обнуляются при отказе; пин TestARefusedCallDoesNotWarnAboutARewriteThatWillNotHappen, мутация AI
отказ по алиасу сида говорил неправду «declining it would change nothing» — но если дельта несёт СВОЮ строку с этой поверхностью, отказ её сбрасывает и чинит ровно ту коллизию, которую тот же отчёт печатает в preexisting_problems. А предписанное «remove it from glossary_seed» строку дельты не убрало бы. Воспроизведено отказ сужен до случая, где его собственная фраза верна; пин TestDecliningASeedAliasIsAcceptedWhenTheDeltaHoldsThatRow, мутация AJ

Что ревью ПОДТВЕРДИЛО (и это тоже результат)

Рефакторинг §33 — эквивалентен: порядок фаз, мутация rs[i].ok через слайс и место отката проверены отдельной полосой, расхождений нет. Платформа не ломается: аддитивные поля отчёта, конверт artifacts{} на манифесте без бампа версии, код 14 и размерные капы — прослежены до её кода, дефектов нет. Исправлены при этом два комментария, утверждавших про чужую зону неверное, и вот верное: код 5 платформа пишет как stopped (и только при зафиксированном намерении остановиться, иначе failed), а отчёт двери печатается НЕ в каждом исходе — четыре отказа (10, 12, 13 и 14 до чтения книги) не печатают ничего, как и два не-отказных случая (5 и 1).

35. Проверка ЗАКРЫТИЯ — отдельным воркфлоу, и она нашла, что закрыто не всё

На вопрос «все замечания исправлены?» сессия не отвечала по памяти: независимые агенты прошли по всем пережившим находкам §34 и по живому дереву решали КАЖДУЮ — закрыта / не закрыта / частично / отклонена осознанно. Закрыто оказалось не всё; все остатки доделаны, и их результат живёт не здесь, а в исправленных по месту секциях (§15, §16, §18, §31, §33) — второй копии этот отчёт намеренно не держит.

Два остатка были признаны вне полномочий пака, и оба закрылись позже: межкнижная гонка за один mined_delta — снятием объявляемых ключей (D39.158 п.5; остаток «второй конфиг ТОЙ ЖЕ книги» заведён строкой бэклога 222), запись карты подписи обычным os.WriteFile — атомарной записью с конвертом (§36, §41).

Вывод, который стоит унести дальше самого пака. Это было ТРЕТЬЕ ревью подряд, и каждое находило настоящие дефекты, которых не видели предыдущие: одиночный старший ревьюер — три (§29), интервальное — пять (§30), воркфлоу — двадцать шесть, проверка закрытия — ещё десять остатков (из 28 её пунктов: 16 закрыто, 2 отклонено осознанно, 7 частично, 3 не закрыто). Ни одно «все замечания исправлены» в этой сессии не оказалось верным при проверке. Вывод не «ревью бесполезны», а обратный: закрытие надо ПРОВЕРЯТЬ отдельно от починки, и не тем, кто чинил.


ДОФИКС-2 (13 пунктов §4; промт архивирован — docs/archive/prompts/BACKEND_SEAM_FIX2_SESSION_PROMPT_2026-08-27.md) — секции 36+

36. Состав фикс2-пака

Новое: internal/store/stopmemory.go + миграция v16 (bank_stop_presented) · internal/seed/signaturemap.go (конверт карты: map_version + map_id от содержимого) · internal/pipeline/bankreport_test.go · internal/pipeline/stopflag_test.go · internal/store/stopmemory_test.go · internal/seed/normalize_test.go. Тронуто: internal/pipeline/mining.go (предикат новизны · разделение «стоп»/«авто-провод» · запись памяти · атомарная запись карты с конвертом · снос формул старой модели) · internal/pipeline/bankdecisions.go (вердикт/исход, finishReport — единственная точка фиксации; staged-запись обоих файлов + fsync каталога; кап-отчёты; written_delta/written_rejects; пере-док SignatureState) · internal/pipeline/refusal.go (два яруса полосы + класс write_incomplete) · internal/pipeline/artifact.go (staged-вариант + syncDir) · internal/config/book.go (filepath.Clean абсолютных + retired-отказ ключей mined_delta/mined_rejects) · internal/membank/decisions.go (RenderSignatureMap с доказательством; версия отчёта → v2) · internal/miner/miner_emit.go (DeltaFile) · cmd/tmctl/main.go (exit 15), bankapply.go, render.go · internal/pipeline/waverun.go (док стопа) · тесты: bankchain_test.go (переписан под модель флажка, две цепи), miningstop_join_test.go (два теста переписаны с старой семантики), internal/config/decisionpaths_test.go (переписан под retired-модель), internal/membank/decisions_test.go (+3 пина) · cmd/tmmutate/mutations.json (36 → 56 записей: 20 новых FX* + 6 ре-якорено после рефакторинга).

37. Решения §4.1§4.3, каждое с доводом — и названные остатки

  • Память — плоское множество нормализованных поверхностей всех предъявленных кластеров (Src + Aliases), таблица bank_stop_presented (book_id, surface). Кластерность живёт в ПРЕДИКАТЕ (пересечение), не в схеме: для вопроса «есть ли кластер вне памяти» объединение множеств поверхностей эквивалентно хранению кластеров, а строк меньше и мигрировать нечего. Рост — только вширь (INSERT OR IGNORE), обрезки нет по букве промта.
  • Двусторонний остаток кластерной памяти записан пином, а не только словами (TestAClusterSharingAnAliasWithTheMemoryReadsAsPresented): мягкая сторона — лишний стоп на новом написании; вредная — новая сущность, делящая поверхность-алиас с виденным кластером, предъявлена НЕ будет (поедет редактору авто-строкой). Обе — принятые остатки решения, смена трейда = новое решение.
  • Порядок записи: карта (АТОМАРНО, §4.10 сделан первым) → память, в момент стопа. Отказ записи памяти НЕ валит состоявшийся стоп: карта на диске, пауза настоящая; цена — один доброкачественный повторный стоп (ERROR-строка в логе называет это). Валить прогон значило бы превратить законную паузу в аварию из-за бухгалтерской строки. Между файлом и SQLite общей транзакции нет — принятый остаток, направление потерь безопасное (повторный стоп, не молчаливая потеря).
  • Остановившийся прогон авто-банк НЕ пишет — названное решение (§4.3 ⚠): стоп — граница владельца, и запись банка на ней меняла бы состояние, которое владелец как раз рассматривает; возобновление без решений доводит авто-провод на следующем прогоне (сходится, проверено цепью Б, прогон 3). Чекпоинты терминолога делают оба варианта $0-эквивалентными — выбран тот, что сохраняет артефактный набор стопа прежним.
  • Черновая волна (draft-only): карта пишется, стопа нет, памяти нет (по букве §4.2); авто-провод теперь исполняется и при поднятом флаге (раньше — нет; это изменение поведения, названо здесь).
  • Флаг-поднят-нового-нет — новое состояние: WARN-строка «--verify-bank is raised and the delta is non-empty, but every cluster... already presented» + авто-провод + полноценное продолжение. Молчание больше не спутать с «майнинг не сконфигурирован».
  • Миграция: v15→v16 — новая таблица CREATE TABLE IF NOT EXISTS (дешёвая форма из промта; ALTER не трогали, долг 49а не задет). ⚠ Голова схемы двигается: OpenReadOnly отказывает классом 13 всем непере-мигрированным книгам до tmctl migrate — порядок деплоя «migrate первым» это покрывает (D39.134 п.3), живьём проверено на копии стенда (v11 → v16, пре-миграционный бэкап, integrity green). После апгрейда память пуста → ОДИН доброкачественный стоп на первом verify-прогоне каждой книги — миграционная нота в журнал.

38. Заявление = команда: замеры фикс2

  • §4.4 пере-замер своей командой (числа промта 2110/442/117/325 первоисточника в дереве не имеют — не цитирую). Первоисточник ТЕПЕРЬ В ДЕРЕВЕ (упрёк проверки закрытия принят): постоянный якорь-пин internal/miner/emissioncap_test.go (TestTheEmissionCapPrecedesTheFiltersAndTheTailIsUnreachable) — тот же корпус (фамилии langpack × имена, 600 имён по 6 вхождений в 6 разных рамках), утверждает ТЕКУЩЕЕ поведение и краснеет в день сдвига потолка или порядка фильтров, приводя двигающего к пингу (прецедент — токенование якоря пинга живым фактом, коммит 0f4c556). Замер той сессии: ranked=2354 · годных во всём алфавите=209 · годных в окне потолка=11 · невидимых постоянно=198; отклонение ВСЕХ 11 годных окна (9 эмитированных + алиасы) → второй прогон: emitted=0, fresh_surfaces=0 — ни один из 198 не поднялся. Структурная причина — miner_emit.go (ranked = ranked[:emitRankCap] ДО emissionEligible, сид- и реджект-скипы тратят слоты ВНУТРИ окна). Картина промта подтверждена на моих числах; заказ — не лечить, пинг в журнале.
  • Каталог мутаций: go run ./cmd/tmmutate -root <копия>56 записей, 0 неожиданных исходов (55 RED + записанный выживший G-byte-gate). Все 20 новых FX-посадок краснеют СВОИМИ пинами (в выводе инструмента поимённо); ре-якорены после рефакторинга: E3 (пере-выражена: предмет «declared» умер с §4.9(б), новая — «unreadable ≠ undecided»), H, S, Z, AH, AG, AI.
  • Пины §4.11, проверенные посадкой ДО записи в каталог (правило «сначала посадка, потом пин»): посадка subjectOf → class вскрыла, что мой первый «межтермовый» пин держится гардом shared-key (его класс-строка несёт идентичность) и для этой мутации ВАКУУМЕН — довод пина переписан честно, мутацию ловит оконный пин (TestANewFaultIsNotMaskedByTheSameTermsFaultInAnotherWindow), что и записано в каталоге.
  • Батарея: go build ./... && go vet ./... && go test ./... -count=1 — зелёная целиком; make lint — 0 issues; make battery — EXIT=0. Замер времени не публикую: прогоны шли под параллельной нагрузкой (агент-ревью и мутационный харнесс), а время под нагрузкой этот проект уже дважды обжигало (§31).

39. Живые пробы фикс2 — на КОПИИ книги стенда (minirun-verify2), в books/ не записано ничего

Копия вне дерева; мёртвые пути переправлены в трёх файлах (book.yaml, pipeline.yaml, pairs/zh-ru.yaml); две строки снятых ключей удалены в копии (сам стенд — зона оркестратора, он в курсе). jieba-контраста на машине нет — стоп/цепь кроются харнессом (miningstop_join_test.go, bankchain_test.go), как и в прошлый раз.

проба результат
немигрированный проект (v11 против v16) exit=13, текст называет tmctl migrate
tmctl migrate v11 -> v16, пре-миграционный бэкап, integrity green
approve+decline одним документом exit=0, отчёт v2: mode=apply, written_delta=true, written_rejects=true, оба файла по конвенции, note с ведущим \n записана и читается
повтор того же документа exit=0, changed=false, оба already_applied, written_*=false
проекция exit=0, mode=projection, written_*=false
решение о ПОДПИСАННОМ сид-терме exit=14, mode=refused, причина называет сид и цену (base snapshot)
снятый ключ mined_delta: в book.yaml exit=10, отказ называет retired + конвенцию + лечение
документ на 5001 решение exit=14 И отчёт на stdout: mode=refused, rejected[0].index=-1, signature.unreadable=true
запись в несписываемый каталог (книга без базы, лок пред-создан) exit=15, mode=write_incomplete, written_*={false,false}, accepted=[]; после chmod повтор ТОГО ЖЕ документа → exit=0
абсолютный project_db со вставкой /./ на конвенционный путь дельты exit=10, отказ называет оба ключа
sha256 стендовой базы не сдвинулся; git -C books status чист

40. Дифф ^func Test — исполнением

$ git diff -U0 -- . | grep -cE "^-func (Test|Fuzz)"                       → 0   # удалено: НОЛЬ
$ git ls-files --others --exclude-standard -- . | grep '_test.go$' \
    | xargs grep -h '^func Test' | wc -l                                  → 117  # было 97: +20 тест-функций (фаззеров по-прежнему 3)

Сводно по трём пакам: пак оставил 59 тест-функций + 2 фаззера, дофикс добавил 38 + 1, фикс2 — ещё 20; удалено за все три пака НОЛЬ, и это проверено дважды — грепом по диффу и поимённо по пинам, которые отчёты паков называют по имени. Переименовано (не удалено): TestTheStopClearsWhenTheOwnerDecidesTheWholeSignatureMapTestTheChainOwnerDecidesTheWholeMapAndTheRunGoesOn (замена по заказу §4.12, новая семантика; вторая цепь — TestTheStopIsAFlagNotAGateOnDecisions — и есть пин старой формулы) · TestDecisionPathsDefaultByConventionOnAnEmptyKeyTestDecisionPathsAreAPureFunctionOfTheConvention; сняты вместе с предметом (§4.9(б), снятие заказано промтом, не подгонка): TestDeclaredDecisionPathIsNotDefaulted, TestDeclaredAndUnreadableDecisionPathStillRefuses, TestADeclaredDecisionFileThatVANISHESIsStillAnError (→ TestAnAbsentDecisionFileIsUndecidedAndAnUnreadableOneIsAnError), TestTwoKeysOnOnePathAreRefused (→ TestDeclaredKeysCannotLandOnTheDecisionFiles); переписаны под модель флажка (старая семантика была их премиссой): TestMiningStopWHATSurvivesResume, TestTerminologistResumeIsFree. ⚠ Промт §4.11 говорил «семантика §4.1 не краснит НИ ОДИН тест» — на деле три теста краснели, потому что пинили СТАРУЮ семантику как премиссу; их переписывание — часть заказа §4.12, и теперь возврат старой формулы краснит ТРИ теста — TestTheStopIsAFlagNotAGateOnDecisions, TestMiningStopWHATSurvivesResume, TestTerminologistResumeIsFree (вывод tmmutate по FX2 поимённо).

41. Что НЕ удалось и что осталось PLAUSIBLE (§10)

  • Атомарность ПАРЫ против смерти процесса не построена и не заявлена: окно между двумя rename остаётся; закрывает его только журнал на два файла — механизм, которого дверь не заслужила (довод в writeDecisionFiles). Покрытие остатка — сходимость повтора (приёмка мерила; жива и в моей пробе: retry после exit 15 сходится) и то, что убитый процесс отчёта не печатает.
  • Долговечность fsync каталога проверена только на ОТКАЗЕ открытия (chmod 0333): что rename переживает реальное падение хоста после syncDir — PLAUSIBLE, живого крэша не было и не будет за $0.
  • exit 15 живьём воспроизведён только формой «не легло ничего»: половинная посадка (delta лег, rejects нет) на живом CLI требует отказа ВТОРОГО rename при успехе первого — среда одного каталога такого не даёт без root; половина покрыта юнитом TestHalfLandedIsNamedPerFile (rename на непустой каталог) и конвергентностью.
  • Стоп на живом стенде не воспроизводится (нет jieba-контраста на машине — то же ограничение, что у прошлых паков): семантика флажка целиком доказана харнессом miningstop_join_test.go и цепями bankchain_test.go на фейковом провайдере; рантайм-вердикт по ПЛАТНОМУ пути — PLAUSIBLE, как и заказано §4.13.
  • «Один лишний стоп» вредной стороны кластерной памяти (новая сущность на общем алиасе молча не предъявляется) в интеграции не воспроизведён — у фикстуры нет двух сущностей с общим прозвищем; запинен на юнит-уровне предиката (stopflag_test.go) как ПРИНЯТЫЙ трейд с направлением потерь.
  • Гипотеза §4.13 о пере-оплате терминолога при подписании механизмом не подтверждена и выигрышем не заявляется: память предъявленного не трогает ни состав кандидатов, ни ключ чекпоинта (хеш отрендеренных сообщений); TestTerminologistResumeIsFree держит $0-резюм в обеих семантиках.
  • Поимённой разметки новизны в карте подписи НЕТ: оператор видит РАЗМЕР карты, а не число новых термов (баннер стопа больше не выдаёт одно за другое — renderSignatureStop, §45). Пер-термная новизна — продуктовая поверхность, не этот пак.

42. Интервальная самоверификация субагентом (§5.1): четыре находки, все minor

Ревью шло против явных критериев, адверсариально, с исполнением (точечные тесты + посадки в своей копии). Код сошёлся по всем; находки — класс «довод/покрытие», ровно тот, что предыдущая сессия назвала своим худшим. Три из них — неверные доводы в докстроках при верном коде (отменённая механика «промотируй/отклони, потом re-run» у renderSignatureStop; предикат v16 в migrate.go, читавшийся как «любая поверхность вне памяти», тогда как код стреляет только на кластере, чья ВСЯ поверхность вне; описание стоп-окна в bankapply.go), все переписаны по месту. ⚠ Третья правка сама внесла ложь про «pre-lock check» и была исправлена воркфлоу-ревью: ОБЕ проверки контекста в bankapply.go пост-локовые (§45). Две находки с живым остатком:

# находка диспозиция
4а грань домена §4.1 «предикат по итоговой карте, не по пулу ДО потолка» не краснится ни одной посадкой: вечный стоп невоспроизводим в фикстурах (2 терма при потолке 200), а одно-строчной мутации не существует — карта, предикат и память читают ОДИН список mined после потолков, расхождение требует сознательной перестройки аргументированный не-пин по прецеденту G: сама структура (один список) и есть защита; записано здесь, чтобы следующая сессия не считала свип дырявым
4б новизна из ОБРАТНОЙ секции не была покрыта: в стоп-фикстурах terminology-гейт выключен и reverse пуста закрыта поведенческим пином TestNoveltyFromTheReverseSectionAloneTripsTheFlag: run 1 с пред-отклонённой banknote-only поверхностью (карта без неё), run 2 со снятым отказом — стоп обязан сработать на новизне, пришедшей ТОЛЬКО через обратную дверь

Что ревью подтвердило исполнением (не переделывал): предикат/карта/память — один список после обеих секций и потолков · все шесть исходов отчёта проходят через finishReport; исходные поля присваиваются ровно в ДВУХ конструкторах — finishReport и до-локовом capRefusalReport (у капа bookState ещё не существует) · TestTheRefusalTableIsTotal невакуумен для класса 15 (AST-словарь + сверка длины таблицы) · retired-ключи ловятся полями, а не голым strict-decode · конверт карты читают все движковые читатели, DecodeFile на карту не наведён · дверь память не читает · в новом Go-коде ветвлений по паре/книге нет.

43. Чек заказа фикс2 — снят

Попунктный чек тринадцати пунктов §4 фикс2 вырезан как прожитая приёмка. Адреса: заказ — docs/archive/prompts/BACKEND_SEAM_FIX2_SESSION_PROMPT_2026-08-27.md (комплектность сверялась механически: grep -cE '^### 4\.' <промт> → 13); решения с доводами и названные остатки — §37; замеры — §38; живые пробы — §39; дифф тестов и переименования — §40; что НЕ удалось — §41; сужения воркфлоу-ревью — §45.

⚠ D39.158 называет «вход §43» — читатель, пришедший по этой ссылке, попал верно: адреса выше.

44. Проверка ЗАКРЫТИЯ — отдельным агентом, не чинившим (§5.1)

Прогнана по презумпции «каждое заявленное закрытие ложно, пока не увидено исполнением»: все 13 пунктов заказа фикс2 (§36§41, §45) и все 4 диспозиции §42 подтверждены в дереве (13 точечных тестовых прогонов + 3 полных пакета — зелёные; носители по file:line; реверс-пин признан невакуумным с названным механизмом падения). Расхождения нашлись и здесь (⚠-вывод §35 держится и на этой сессии), все закрыты по месту — правки видны в §38, §40 и §42. Одно стоит того, чтобы его запомнили: пере-замер §4.4 жил только в скратчпаде — то есть был «числом без первоисточника» ровно того класса, за который заказ корил чужие числа; первоисточник положен в дерево постоянным якорем-пином internal/miner/emissioncap_test.go.

45. Воркфлоу-ревью фикс2: две кодовые находки и три остатка, которые оно сузило

Форма: шесть параллельных полос-искателей под антипаттерны проекта (неверные доводы при верном коде · вакуумные пины · семантика флажка глубоким чтением · дверь/отчёт глубоким чтением · общность движка · шов/контракт), каждая находка — через трёх адверсариальных скептиков с разными линзами (корректность трассой · достижимость · «не принятый ли уже остаток»), выживание — большинство. Все выжившие находки закрыты; правки доводов в комментариях и документах живут по месту, здесь — то, что осталось фактом.

Кодовые (2), обе закрыты правкой + пином + посадкой:

находка закрытие
tmctl translate --dry-run молча игнорировался и запускал ПЛАТНЫЙ прогон — третье написание денежной дыры, которую это же дерево уже закрывало для --decisions=/--keys-file=; пред-существует фикс2, нигде не был записан гард присутствия: --dry-run принимают только bank-apply и redrive, остальным — громкий отказ с ценой в тексте; пин TestDryRunIsRefusedWhereNothingProjects; посадка FX21
Повтор после exit 15 из-за отказа syncDir сходился в exit 0, так и не пере-доказав долговечность — retry по контракту класса шёл byte-no-op-веткой мимо единственного места с синком no-op-ветка теперь пере-доказывает долговечность: не открывающийся на синк каталог держит класс 15 (written_*={false,false}, текст называет непроверенное) до выздоровления среды; пин TestARetryAfterAFailedDirectorySyncReProvesDurability; посадка FX22

Что сужено и осталось живым:

  • Якорь-пин §4.4 НЕ краснел при сдвиге потолка — премиссы не фиксировали значение 200 (150/300/400 проходили зелёными), вопреки трём носителям клейма. Значение потолка теперь запинено премиссой с текстом, ведущим двигающего к пингу; проверено: смена 200 → красный.
  • Довод арбитра сужен: «межкнижная гонка недостижима по построению» была шире правды — остался второй конфиг ТОЙ ЖЕ книги (один каталог/book_id, другой объявленный project_db): пишет те же файлы под другим локом, мимо flock живого прогона (ревью воспроизвело живьём). Межкнижная версия снята; остаток заведён строкой бэклога 222 (D39.158 п.9).
  • Клауза «ничего не произошло» у классов 1014 сужена до «состояния КНИГИ»: пред-полётный бэкап translate пишется ДО отказа 12/13. И preexisting_problems: [] в кап-отчёте ≠ «проверено чисто» — оговорено на самом поле: при отказе по капу проверка не выполнялась.
  • Баннер стопа выдавал РАЗМЕР карты за число НОВЫХ термов — под моделью флажка карта намеренно смешивает новое и ранее предъявленное-нерешённое, поэтому счётчик не имеет права называться «new». WaveSignatureStop.Error переписан, а renderSignatureStop печатает нейтральное backend/cmd/tmctl/render.go:171=(%d term(s) in the map); довод стоит рядом с кодом (backend/cmd/tmctl/render.go:169=must not be labelled «new»). ⚠ Поимённой разметки новизны в карте НЕТ — продуктовая поверхность, не этот пак; сюда и ссылается §41.

ФИКС3 (6 пунктов; промт архивирован — docs/archive/prompts/BACKEND_SEAM_FIX3_SESSION_PROMPT_2026-08-27.md) — секция 46

46. Фикс-лист приёмки дофикса-2: четыре пина, один снос, зачистка двух контрактов

Комплектность: grep -cE '^### 4\.' docs/archive/prompts/BACKEND_SEAM_FIX3_SESSION_PROMPT_2026-08-27.md6.

пункт статус чем закрыт
4.1 пин «карта → память» мутация приёмки ВОСПРОИЗВЕДЕНА до пина (отказ записи карты → WarnContext: батарея зелёная во всех трёх кандидатных пакетах — pipeline, tmctl, store). Пин приёмом приёмки: путь карты занят непустым каталогом → TestTheMemoryIsNotWrittenWhenTheMapCannotBe требует ошибку прогона (не стоп и не успех) и ПУСТУЮ память книги. Проверен посадкой: красный на мутации, зелёный на дереве. Каталог: FX23
4.2 отчёт 15 мерит по диску утверждение панели ВОСПРОИЗВЕДЕНО (docsReRead → docsFromResult на write_incomplete: вся батарея зелёная — существующий пин кормит конструктор пустыми состояниями, источники неразличимы). Пин — брат stopped-пина: карта с нерешённой поверхностью + документ, решающий её + «не легло ничего» → signature.undecided обязан быть 1 (диск), не 0 (намерение): TestAWriteFailedCallMeasuresTheSignatureAgainstTheDiskNotTheIntent. Проверен посадкой. Каталог: FX24
4.3 старый пол в двух носителях runner.go (openRunner) и cmd/tmctl/panic_exit_test.go приведены к двух-ярусной букве: пол полосы + «nothing was written» как клауза классов, с явной оговоркой про класс 15. Контрольный греп: grep -rn "nothing was written" --include='*.go' .два хита, оба — клаузы КЛАССОВ, не пола: ErrDecisionsRefused (класс 14, там она истинна целиком) и сообщение отказа синка каталога («nothing was written BY THIS CALL…», класс 15 — пер-вызовная клауза с оговоркой)
4.4 отменённая формула грепом греп-формы: re-?fires? the stop · stop clears only · clears? once every · promoted or rejected · stop.*forever · livelock рядом с decline · переспраш. Найдено и переписано восемь носителей (пять из списка промта + DecodeRejects-док + док и оракул TestMiningStopRejectClearsDelta): правда из ноты — отказ исключает сущность из ЭМИССИИ (терм не возвращается в карту и в авто-банк; про стоп отказ не говорит ничего — стоп это вопрос памяти). Тест реджектов НЕ вакуумен под памятью (его второй проект СВЕЖИЙ — память пуста, продолжение решенческое), и это теперь сказано его доком + явным оракулом lastMinedCount == 0. Контрольный греп после зачистки: остались только носители НОВОЙ модели (память/re-fire on what it already presented) и история row-199 в цепях
4.5 снос .bank-stop.json свой греп читателей по backend/platform/frontend/eval ДО сноса: в коде читателя нет ни в одной зоне (у платформы — только упоминания в её ЖУРНАЛАХ, пинг ниже). Снесено: писатель writeBankStopTableJSON, clearBankStopTableJSON (+ его вызов на not-configured-пути), пустая перезапись на empty-delta, bankStopTableJSONPath, bankStopTableVersion, оба DTO, emptyIfNil/emptyVariantsIfNil, json-теги BankStopVariant, поле WaveSignatureStop.TableJSONPath, строка CLI «Machine table:», лог-поле table_json, четыре теста таблицы (предмет снесён заказом — не подгонка). .bank-stop.txt НЕ тронут (читатель — оператор). Контрольный греп `bank-stop.json
4.6 якорь свежести банк-экспорта утверждение подтверждено, признак заведён подтверждение: at ехал ТОЛЬКО в лог-строке (exportBank), при отказе записи артефакт молча оставался прошлым — в документе отличить нечем. Форма: аддитивные поля as_of (метка границы) + run_id (тот же id, что стрим events.jsonl; эмиттер авторитетен — он может пере-минтить id; пустой = «идентичности нет», не «текущий»). Версия tm-bank-v1 не двигалась (добавление аддитивно, по букве промта). Пин TestTheBankExportCarriesItsFreshnessAnchor: последняя граница прогона · смена границы двигает якорь · без идентичности — пусто. Детерминизм не задет: id приходит из контекста/эмиттера, не из часов

Что НЕ удалось / остатки (§10): якорь §4.6 отличает прогоны по run_id и границы по as_of, но две записи ОДНОЙ границы одного прогона (не бывает сегодня: каждая граница пишется однажды) были бы неразличимы — принято, механизм счётчика был бы новым механизмом против буквы промта. Снос §4.5 оставляет упоминания .bank-stop.json в ЖУРНАЛАХ платформы (их зона) — пинг, не правка.

Пинги этого пака (в журнал): платформенные доки (platform-PROGRESS.md, STACK_DECISIONS.md) называют .bank-stop.json атомарно-читаемым сайдкаром — файла больше нет, упоминания устареют при лендинге · потребители банк-экспорта получают as_of/run_id — аддитивно, ломать нечего.

Прогоны итогового дерева (§5): make batteryEXIT=0 (fmt+vet+тесты+линт) · make battery-stand → красен ровно TestMinerFullBookParity (jieba вне git — ожидаемая картина промта) · каталог мутаций go run ./cmd/tmmutate -root <копия>60 записей, 0 неожиданных исходов (перегнан ПОСЛЕ форматирования — якоря каталога сверены с финальными байтами). Дифф тестов: снесены четыре теста машинной таблицы (предмет снесён заказом §4.5), добавлены четыре пина (§4.1, §4.2, §4.6, оракул lastMinedCount в тесте реджектов); git diff -U0 -- . | grep -cE "^-func (Test|Fuzz)"4 — ровно четыре снесённых TestBankStopMachineTable* — снос легитимен: предмет снесён заказом §4.5, других удалений диффа нет.

Проверка закрытия (не-чинившим): все шесть пунктов подтверждены исполнением — обе мутации (FX23, FX24) посажены им в своей копии и убиты пинами, греп-оси §6 пере-сняты; найденные расхождения исправлены по месту выше.