169 KiB
Доказательная база пака «входная дверь шва» (движковая половина D39.156)
Бэкенд-сессия 24.08.2026. Промт архивирован — docs/archive/prompts/BACKEND_SEAM_PACK_SESSION_PROMPT_2026-08-27.md; записка-план — SEAM_PACK_PLAN.md.
Каждое число — с командой, которой получено; приёмка пере-раняет выборочно. Команды из backend/.
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 (bankTermID →
membank.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)
срабатывают примерно раз на 2–4 полных прогона: замерено 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 battery — EXIT=0 на чистом прогоне (skip: TestMinerFullBookParity,
TestHelperEventsRun, TestHelperKillLoop); make battery-stand без единого TM_*_DIR — EXIT=2,
красен ровно TestMinerFullBookParity, причина — отсутствующий eval/exp16/data/jieba_dict_general_zh.txt;
make lint — 0 issues.; go vet ./... && go vet -tags live ./... и gofmt -l . — чисто. Стенд-картина
совпала с опорной точкой оркестратора ⇒ критерий §4.6 выполнен буквально. Батареи дерева, которое
передавалось дальше, — §31 (дофикс), §43 строка §5 (фикс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 падений | оракулы: не паникует · всё-или-ничего · ни одно решение не теряется молча (каждый входной индекс ровно один раз в accepted∪rejected) · принятый результат грузится реальным загрузчиком |
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, ls → No 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, чек — §26 4.5.
10. Пинги — предметы ВНЕ карты пака, правок не делал
rubyToCandidatesмёртв в проде.grep -rn "rubyToCandidates" --include=*.go .→ 4 хита: определениеinternal/membank/memseed.go:301, два комментария и ЕДИНСТВЕННЫЙ вызов —memseed_test.go:204.internal/pipeline/seeding.go:36-38строитmanualSrcs(аргумент этой функции) и никуда не передаёт; Go молчит, потому что запись в map считается использованием. Следствие: строкSource:"ruby"в банке не возникает, а платформа держит для них ветку словаря (platform/internal/ingest/bank.gotermOrigin→OriginAnnotated).- Гейт
TestDispatchCommandsCoversTheSwitchслеп кcaseс КОНСТАНТОЙ — считает только*ast.BasicLit, поэтому команда, объявленная константой, невидима гейту, чей смысл в том, что дефект строки 176 «отрастал трижды». Обнаружено посадкой; я вернулся к литералу (конвенция файлаif cmd == "seed-lint"), поэтому дыра не эксплуатируется. redrive— платный путь без канала ключей аргументом. Промт: «флаг только уtranslate», сделано ровно так. Канал у него есть (конвенционный.env), и платформа его не зовёт (runner/engine.goстроит только translate/status/manifest/export). Развилка следующему паку.- Проекция берёт тот же лок, что и применение ⇒ «поправить термин при живом прогоне» даёт 12 даже на ПРЕВЬЮ, на всю длину прогона. Платформенный пак обязан это спроектировать, а не обнаружить.
- Пути
mined_delta/mined_rejectsопубликованы вstatus --json. Законно по п.1 закона, но эти файлы версионного конверта не несут, а опубликованный путь читается как приглашение. Единственная изгородь против их парсинга платформой — комментарий. Самое мягкое место в «формат знает ОДНА сторона». - Схема сида без
omitempty—yaml.Marshal(seed.File{})печатает все ключи, включая пустые. Уже существовало (miner_emit.goDeltaYAMLрендерит так же), глагол наследует. Смена тегов = правка формата данных (internal/seed/seed.goэто оговаривает). - Два флейка в
internal/store, пред-существующие.TestTheSeamIsNotChargedToTheStoreOperationBudgetиTestKillMinus9LosesAtMostOneCall; тесты НЕ трогал (D39.121: правка теста ради зелени недопустима). Оба меряют ВРЕМЯ (первый сознательно сжимаетopTimeout, второй гоняет helper против таймера SIGKILL); покрытие независимо подтверждает, чтоLockProject/Releaseэтими тестами не исполняются вовсе. ⚠ Первый A/B (3 раунда × 10) далkill92/30 на HEAD против 0/30 на дереве пака — это НЕ значит «пак улучшает»: на таком числе событий это шум (Фишер p≈0.5); решающее и достаточное — что флейк живёт на дереве, которого пак не касался. Окончательная атрибуция — 5-раундовый A/B в §31.
11. Два правила протокола, вынесенные инцидентами сессии
- Эталон самопроверки снимается ДО первого изменения, и харнесс мутаций обязан фингерпринтить цели.
Незавершённый прогон
mutate.pyоставил посадку M4 в дереве ~40 минут, выглядящей продакшн-кодом, и сторонний ревьюер снял с этого дерева замеры (он же собралtmctlиз незакоммиченного дерева, не сняв отпечаток, — на класс наступили обе стороны). Посадку поймал и правило теперь держит пинTestBankApplyRepeatIsAByteNoOp: он же документирует и форму инцидента, и довод (инод плюс рукописный комментарий в шапке — то, что пере-рендер уничтожает, а проверка содержимого не заметила бы). ⚠ Класс повторился следующей сессией ВНУТРИ самого харнесса — §25; отпечаток целей и каталога у инструмента сегодня свой — §16. - Одиночный прогон — не замер. Атрибуцию флейков я сперва вывел из одного прогона («зелено в изоляции ⇒ нагрузка»), затем другой одиночный прогон дал ровно обратное. Решает только чередующийся A/B (§10 п.7).
12. Попунктный чек промта
| пункт | статус |
|---|---|
§2 зона записи только backend/ + секция «Бэкенд» в docs/PROGRESS.md; не коммитить; чужое не трогать |
✅ git status подтверждает |
| §4.1 дефолты пустых ключей; гард ЯВНОГО пути не ослаблен; отсутствующий дефолт = «решений нет» | ✅ на момент пака — M1 + TestDeclaredAndUnreadableDecisionPathStillRefuses. ⚠ Механизм СНЯТ фикс2: объявляемых ключей больше нет, путь — чистая функция каталога и book_id (D39.158 п.5), оба пина сняты вместе с предметом (§40) |
§4.2 глагол $0; байтовый no-op; отзыв = ЗАМЕНА; всё-или-ничего; два решения по терму = отказ; конфликт с сидом С ИМЕНЕМ; проекция БЕЗ мутации; занятый лок РОВНО 12; flock неблокирующе; версия в ответе; depth явным полем |
✅ M2–M8 + TestDeclineWritesTheRejectAndWithdrawsTheApproval, TestOneCallDecidesATermOnce, TestDecisionOnASignedSeedTermIsRefusedByName, TestBankApplyDryRunWritesNothing, TestBankApplyDeclineThenApproveConverges; §3, §4 |
| §4.2 НЕ проектировать «глубину до черновика» | ✅ depth: edit_wave; TestTheRunReadsTheConventionalDelta доказывает Source:"mined" |
§4.3 --keys-file только у translate; деплой-файл ПЕРВЫМ; нечитаемый = отказ конфигом; ничего «на вырост» |
✅ M6 + TestKeysFileWinsOverTheConventionalEnv + TestAnUnreadableKeysFileRefusesWithTheConfigClass |
| §4.4 строгость сид-загрузчика + тест на unknown-key | ✅ 2 кейса в TestSeedLintCatchesDefects (gendr, termz) |
| §4.5 путь артефакта аддитивно + версия документа; НЕ тащить пере-именование банк-экспорта (ЛОМАЮЩЕЕ, окно — строка 161) | ✅ artifacts{} + status_version; пин rep.Artifacts.BankExport == r.bankExportPath() |
| §4.6 дефолт корпуса маркерным резолвом, env-переопределения старше | ✅ §1 |
§4.7 платных вызовов НОЛЬ; --keys-file фейковыми ключами; вердикт PLAUSIBLE |
✅ sha256 базы (§4) + TestTheKeysFileActuallyReachesTheProvider (сверяет Authorization на fake-провайдере, и БЕЗ флага тот же прогон отказывает конфигом — проверка не вакуумна); §7 |
| §5 батарея после каждой правки; красный тест не подгонять; посадки ВНЕ списка промта; посадка называет ПАКЕТ; живая проба в КОПИИ; артефакт-находок | ✅ 16 посадок, 8 сверх списка; флейки охарактеризованы и НЕ тронуты; §2, §3 |
| §5.1 последний абзац — план или обещание? сделать СЕЙЧАС | ✅ всё найденное ревью закрыто в этом дереве; единственное отложенное — §8, вне моих возможностей по деньгам |
§5.1 дифф ^func Test исполнением; интервальная самоверификация субагентом |
✅ §13; ревью гонялось в СЕРЕДИНЕ пака против явных критериев |
| §6 оси: шов (инвентарь ЧУЖОГО кода) · общность · деньги | ✅ читал artifacts.go/bank.go/engine.go/spawn.go/resync.go/exit.go сам; проверил, что ни один декодер платформы не строгий ⇒ аддитивные поля безопасны; Go не ветвится по паре/книге; деньги — sha256 базы |
| §8 число с командой · §9 эхо до работы · §10 «что НЕ удалось» · §11 развилки пингом | ✅ §1–§4 · выдано · §7 · §10 |
13. Дифф ^func Test — исполнением
Удалено: НОЛЬ — сводный счёт по трём пакам и обе команды в §40. Новых тест-функций пака шесть файлов
несут в себе, плюс TestTheRefusalTableIsTotal в существующем cmd/tmctl/exitcontract_test.go.
Тронуто в существующих файлах — шесть, ни у одной сигнатура не изменилась:
exitcontract_test.go— в список полосы добавленыexitSchemaMismatchиexitDecisionsRejected(иначе гейт молча перестал бы покрывать новый код) + тест тотальности;invocation_test.go— frozen-строка usage расширена именемbank-apply. Объявленное расширение контракта тем же механизмом, которым строка росла пять раз до меня (её собственный комментарий их перечисляет), и второй половине того же теста («usage обязана называть каждую dispatch-команду») иначе не удовлетворить;checks/labelharness_test.go,membank/labelharness_test.go,miner/miner_parity_test.go— §4.6: три копии резолвера заменены наstanddata(−120 строк, +62);membank/seedlint_test.go— два кейса unknown-key (§4.4).
Дофикс той же двери (сессия 25.08.2026)
Промт (16 пунктов §4) архивирован — docs/archive/prompts/BACKEND_SEAM_FIX_SESSION_PROMPT_2026-08-27.md;
записка-план — SEAM_FIX_PLAN.md. Секции 0–13 выше — улика приёмки, не тронуты.
14. Батареи
База ДО правок, на дереве пака: make battery → EXIT=2, красен ровно
TestKillMinus9LosesAtMostOneCall — один из двух названных промтом пред-существующих -race-флейков.
Флейк воспроизвёлся на дереве, которого дофикс ещё не касался: атрибуция своя, не наследованная.
make lint → 0 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 с, 391–392 МБ · без неё 14.46 / 17.27 / 17.39 с, 344–408 МБ.
По времени ⇒ ≈5 % (медиана 18.10 против 17.27).
⚠ Эти секунды НЕ сравнимы с таблицей §18, где тот же документ на 10 000 решений даёт 14.53 / 15.73 / 16.54 с на ТОМ ЖЕ бинаре с проверкой. Замеры сняты в разные моменты под разной внешней нагрузкой (параллельные сессии), и абсолютные числа между секциями складывать нельзя. ≈5 % выведены ТОЛЬКО из пары выше — она снята подряд, двумя бинарями из одного дерева, и внутренне согласована. По памяти пара с таблицей §18 сходится (391–392 МБ против 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>
| # | посадка (свойство) | пакет пина | что покраснело |
|---|---|---|---|
| A | лок двери — тот же flock, что держит ПРОГОН | pipeline |
TestBankApplyRefusesAProjectAN_RUNIsHolding |
| B | canonical_rewrite + preexisting_problems (2 правки в 1 файле) |
pipeline |
TestTheReportCarriesTheTwoFieldsThePackMitigatesItsRisksWith |
| C | класс отказа без номера уезжает в catch-all 19 | cmd/tmctl |
TestTheRefusalTableIsTotal |
| D | нормализация в REPLACEMENT-половине отказа | membank |
TestADeclineWithdrawsAnApprovalWrittenInAnotherOrthography |
| E1 | глубокая копия алиасов в withRubyAliases |
membank |
TestWithRubyAliasesDoesNotWriteIntoTheCallersRows |
| E2 | перенос note при промоушене |
membank |
TestPromotionCarriesTheOwnersNote +1 |
| E3 | рантайм-половина гарда ОБЪЯВЛЕННОГО пути | pipeline |
⚠ пере-выражена фикс2 («unreadable ≠ undecided»): TestAnAbsentDecisionFileIsUndecidedAndAnUnreadableOneIsAnError |
| F | абсолютность путей в artifacts{} |
pipeline |
TestTheReportedPathsAreAbsoluteEvenWhenTheConfigIsNOT |
| G | байтовый гейт changedDoc |
pipeline |
ВЫЖИВАЕТ — см. §17 |
| H | решения едут С КНИГОЙ, а не с project_db |
config |
TestTheDecisionFilesTravelWithTheBookAndNotWithTheDatabase |
| I | нормализация ДОКУМЕНТА (значение уже в файле) | membank |
TestAHandAuthoredNoteTheEngineCannotWriteIsRepairedNotRefused |
| J | равенство в renderProved (а не «просто разобралось») |
membank |
TestTheRenderGateRefusesBytesThatReadBackAsAnotherDocument |
| K | нормализация ЗАПРОСА (идемпотентность повтора) | membank |
TestARepeatOfADecisionWhoseTextIsPaddedIsStillAlreadyApplied |
| L | book_id — один элемент пути |
config |
TestBookIDIsRefusedWhenItIsNotAPathElement |
| M | четыре движковых пути попарно различны | config |
⚠ переименован фикс2: TestDeclaredKeysCannotLandOnTheDecisionFiles |
| N | гард флага — на ПРИСУТСТВИЕ, не на значение | cmd/tmctl |
TestAFlagIsRefusedForBEINGTHERE_NotForCarryingAValue |
| O | перевёрнутое окно глав | membank |
TestAReversedChapterWindowIsRefused |
| P | инертный отказ судится по НАБОРУ | membank |
TestApprovingATermAndDecliningItsAliasInOneCallIsRefused |
| Q | note не стирается умолчанием |
membank |
TestANoteIsNotErasedByAnOmission |
| R | кап документа решений | pipeline |
TestTheDecisionDocumentIsCapped |
| S | стоп между решением и записью не пишет | pipeline |
TestAStopRequestWritesNothing/cancelled_while_the_decision_is_being_computed |
| T | пред-существующая поломка — по СУБЪЕКТУ | membank |
TestRepairingOneOfTwoBrokenTermsIsAccepted |
| U | честность отчёта о непогасшем стопе | pipeline |
⚠ переименован фикс2: TestTheChainOwnerDecidesTheWholeMapAndTheRunGoesOn |
| V | конверт путей на поверхности потребителя | pipeline |
TestTheManifestCarriesTheArtifactEnvelope |
| W | кап на КОЛИЧЕСТВЕ решений (§29 п.1) | pipeline |
TestTheDecisionCOUNTIsCappedBecauseTheBYTESCannotBound |
| Y | субъект поломки без рендеринга (§29 п.2) | membank |
TestAFaultTheDoorHasNoChannelForCannotDeadlockIt +1 |
| Z | нечитаемая карта подписи ≠ «всё решено» (§29 п.6) | pipeline |
TestASignatureMapThatCannotBeReadDoesNotReportAsAllDecided |
| AB | побег «это был терм сам по себе» (§30 п.2) | membank |
TestADeclineIsRefusedWhenTheSurfaceGOESONFiringAsAnotherTermsAlias |
| AC | aliasOwner игнорирует статус (§30 п.3) |
membank |
TestDecliningTheAliasOfAnUNSIGNEDRowIsAccepted |
| AD | загрузчик отдаёт построенные записи при отказе (§30 п.1) | membank |
TestRepairingAnUnloadableDocumentIsNotRefusedOverWhatItUNMASKS |
| AE | позиционный субъект (§30, «плюс два») | membank |
TestTwoRecordsWithNoIdentityDoNotBlockEachOthersRepair |
| AF | довод отказа описывает всех, кого отвергает (§30 п.4) | cmd/tmctl |
TestTheKeysFileRefusalDescribesEveryoneItTurnsAway |
| AG | остановленный вызов не печатает, что применил (§34) | pipeline |
TestAStoppedCallDoesNotPRINTThatItApplied |
| AH | нечитаемая карта подписи ≠ «всё решено» (§34) | pipeline |
TestASignatureMapThatCannotBeReadDoesNotReportAsAllDecided |
| AI | отказ не предупреждает о перезаписи, которой не будет (§34) | pipeline |
TestARefusedCallDoesNotWarnAboutARewriteThatWillNotHappen |
| AJ | отказ по алиасу сида сужен до случая, где он инертен (§34) | membank |
TestDecliningASeedAliasIsAcceptedWhenTheDeltaHoldsThatRow |
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 с — в 3–5 раз медленнее. Проверкой раунд-трипа это НЕ объясняется (§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 }
undecided > 0 — факт, что стоп не погаснет. undecided == 0 — не обещание, и это написано в
комментарии типа явно. Пустой map значит «прогон до границы банка ещё не доходил». В режиме проекции
число — то, что БУДЕТ после применения (единственное чтение, которое может иметь превью).
Запинено там, где оно осмысленно, — в тесте цепи §4.12: частичный документ на карте из двух сурфейсов
даёт undecided=1, полный — undecided=0, и после него стоп действительно гаснет.
23. Пинги — предметы ВНЕ карты дофикса, правок не делал
exit 2при OOM читается платформой как успех (заказ §4.5 прямо называет это пингом). Движок при нехватке памяти выходитfatal error: out of memory+ код 2, аplatform/internal/ingest/exit.goчитает 2 как «команда СДЕЛАЛА работу». Класс общедвижковый. Кап §4.5 не даётbank-applyстать первым, кто до него доедет, но самого класса не снимает.- Каталог книги без права записи даёт
exit 1, и платформа спишет проблему прав ХОСТА в бюджет попыток КНИГИ. Класс движка корректен (см. §21); чинить надо чтение кода 1 на платформе. - Проекция отвечает «что применится», а не «почём». Рекомендация: не строить. Закон п.7 про
«почём ДО подтверждения» уже исполнен там, где есть деньги (rebill-смета,
status --json), а этот глагол $0 по построению — единственная его «цена» это пере-редактура, которая квотируется и гейтится в другом месте и другим согласием. Поле «$0» в отчёте было бы шумом. Решать оркестратору. - Карта подписи не удаляется после погасшего стопа (
os.Removeвmining.goне встречается). Пред-существующее. Потребитель, читающий файл как ответ на «есть ли что подписать», читает уже подписанную карту. Зафиксировано ЯВНО тестом цепи как текущее поведение; честный ответ на тот же вопрос —signature.undecided(§22). - Цена глагола Θ(N²) по построению (§18) — это то, почему кап на КОЛИЧЕСТВЕ стоит на 5 000, а не
выше. Лечится индексом по ключу терма в
applyOne/dropTermsи по нормализованной поверхности вaddReject/dropReject; вне заказа, не делал. Поднимать кап без этого нельзя. - Апстрим-дефект
yaml.v3(§15) лечится по-настоящему двумя способами, и оба вне пака: (а) тип скаляра схемы сMarshalYAML, возвращающим узел с явным стилем (проверено по исходнику: явный стиль проверяется РАНЬШЕ ветки блочного скаляра,encode.go:557-570); (б) переезд наgo.yaml.in/yaml/v4— там этот случай починен, но это релиз-кандидат и смена module path сразу в двух зонах. Мой обход несёт ссылку и характеризационный тест, а не «похоже, баг yaml». - Цена доказывающего рендера ≈5 % времени (§15); по памяти разница внутри разброса прогонов и НЕ
измерена. Плюс
notCanonicalтеперь тоже разбирает документ. Названо, чтобы следующий замер не приписал это чему-то другому.
24. Что НЕ доказано и что НЕ удалось
- Рантайм-вердикт по ПЛАТНОМУ пути остаётся PLAUSIBLE. Санкции на платные вызовы у дофикса нет, и
ни одного платного вызова не сделано (
sha256базы стенда не сдвинулся). Это ожидаемый исход, а не дефект отчёта. - Цепь §4.12 доказана на СИНТЕТИЧЕСКОМ контрасте, а не на книге стенда: банк-стоп на этой машине не
воспроизводится вовсе (
pipeline.yamlтребует jieba-контраста, которого клон не получает по построению — строка 123). Ограничение названо в комментарии самого теста: синтетика меняет, КАКИЕ кандидаты предложены, но не то, проходит ли подписанная строка фильтр подписи. Для вопроса «гаснет ли стоп» это корректно; для качества майнера — нет. - Расхождение по ВРЕМЕНИ с замером пака (3–5×) не объяснено (§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 — все шестнадцать
Комплектность сверена механически: grep -cE '^### 4\.' docs/archive/prompts/BACKEND_SEAM_FIX_SESSION_PROMPT_2026-08-27.md → 16.
| пункт | статус | чем закрыт |
|---|---|---|
| 4.1 ⛔ блокер: round-trip на ОБОИХ документах | ✅ сделано ШИРЕ заказа | нормализация документа + запроса, рендер ДОКАЗЫВАЕТ равенство (не только разбираемость — заказанная форма пропустила бы две тихие потери), байты проверены один раз и один раз записаны, фаззер пути ДВЕРИ, характеризационный тест на библиотеку. §15, §20; мутации I/J/K; живая проба §19 |
| 4.2 пред-существующие чинятся по одной | ✅ сделано; дважды углублено по ревью | вердикт загрузчика разложен по СУБЪЕКТАМ (membank/problems.go), ключ сравнения — субъект, который НЕ несёт рендеринга (§29 п.2), и проверки ПОД загрузчиком выполняются по обе стороны (§30 п.1). Мутации T, Y, AD (⚠ X снята вместе с правилом маски, каталог её больше не несёт); пины TestRepairingOneOfTwoBrokenTermsIsAccepted, TestAFaultTheDoorHasNoChannelForCannotDeadlockIt, TestRepairingAnUnloadableDocumentIsNotRefusedOverWhatItUNMASKS; живая проба §19 |
| 4.3 два ключа на один путь | ✅ сделано ШИРЕ на одну проверку | попарно различны четыре движковых пути (project_db, glossary_seed, mined_delta, mined_rejects) — project_db == mined_delta означало бы YAML поверх sqlite. Проверено на всех book.yaml репозитория и стенда (find . -name book.yaml | wc -l → 22): нарушителей ноль. Мутация M |
| 4.4 гард флага на ПРИСУТСТВИЕ | ✅ сделано | fs.Visit (единственное, что отвечает на этот вопрос), оба гарда, плюс пустое значение у присутствующего флага — тоже отказ. Мутация N |
| 4.5 кап документа + строка отказа полосой | ✅ сделано, ПЕРЕСМОТРЕНО по ревью | ДВА капа: 1 МиБ на чтение + 5 000 решений на применение. Первая редакция капила только байты и утверждала, что этим ограничено ВРЕМЯ — опровергнуто замером (§29). Класс 14 у обоих. Хвост про exit 2 — пингом. Мутации R, W |
| 4.6 конверт путей потребителю | ✅ сделано, компромисс назван | manifest --json несёт тот же artifacts{}; ВЫВОДИТСЯ на каждом чтении, поэтому версия документа не двигалась. Компромисс вслух: манифест семантически про дерево глав, путь артефакта там гость. Мутация V |
| 4.7 алиас судится по НАБОРУ | ✅ сделано; углублено по §30 п.2 | проверка перенесена ПОСЛЕ свёртки и судит РЕЗУЛЬТАТ — именно поэтому два отказа (терм + его алиас) остаются законными: к концу вызова алиасом никто не владеет. ⚠ Побега «своя поверхность проверяется по ВХОДНОМУ документу» в коде НЕТ — он был заведён и снят как вредный (§30 п.2), заводить заново не надо. Мутации P, AB |
| 4.8 перевёрнутое окно глав | ✅ сделано | отказ с цитатой окна; открытая верхняя граница законна. Мутация O |
| 4.9 честность про непогасший стоп | ✅ исполнено фактом, не предсказанием | signature{map,surfaces,undecided} по правилу самого прогона; undecided>0 — факт, undecided==0 — явно не обещание. Аргумент в §22. Мутация U |
| 4.10 SIGTERM | ✅ сделано; сомнение записки СНЯТО замером; довод в комментарии исправлен по ревью | ctx проверяется дважды, стоп не пишет ничего, exit 5. Несущий довод — НЕ длина непрерываемого участка (первая редакция назвала её «≈20 с, два порядка внутри stop grace»; замер §29 п.1 дал 308.2 с на 28 900 минимальных decline, которые байтовый кап пускает, — оценка неверна в 15 раз), а то, что записи идут последними и атомарно: убитый где угодно до них процесс не записал ничего. Мутация S (пин целится именно во вторую проверку) |
| 4.11 десять посадок, пин на каждую | ✅ 9 из 10 + доказательство по десятой | A–H, E1–E3 краснеют своими пинами; G недостижима за первым гейтом — доказано, не предположено (§17), вместо поведенческого пина запинен контракт функции |
| 4.12 цепь в репозитории | ✅ сделано | internal/pipeline/bankchain_test.go, $0 на фейковом провайдере, ОДИН проект от стопа до погасшего стопа; форма фикстуры (2 терма = 1 approve + 1 decline) проверяется как ПРЕМИСА; ограничение синтетики названо в комментарии; карта подписи после погасшего стопа зафиксирована явно |
| 4.13 девять мелочей | ✅ по каждой решение и довод | §21: починено 3, отклонено с аргументом 5, пингом 1 |
4.14 довод отказа --keys-file |
✅ сделано | текст называет redrive (платный, свой .env), backup/migrate (деплой-шаги) и $0-читающие поимённо. Развилку про канал ключей redrive НЕ решал |
| 4.15 три тавтологичных пина | ✅ сделано | вердикт Preexisting исполняется всеми четырьмя проверками (TestAPreExistingFaultOfEveryKindStaysReportedAndUnblocking); четвёртый оракул фаззера снят с тавтологии и стал НЕПОДВИЖНОЙ ТОЧКОЙ; TestDecisionByBankID сверяется с НЕЗАВИСИМО посчитанными константами + отдельный пин на инъективность, ради которой в TermID сделана длина-префикс |
| 4.16 платных вызовов ноль | ✅ | sha256 базы стенда не сдвинулся; рантайм по платному пути — PLAUSIBLE, как и ожидалось |
| §5 батарея после каждой правки · посадки СВЕРХ списка · посадка называет ПАКЕТ · живая проба в КОПИИ · артефакт-находок | ✅ | 36 посадок (26 сверх списка §4.11), пакет пина ≠ пакет правки записан в каталоге, проба в копии, эта секция |
§5.1 последний абзац — план или обещание? · дифф ^func Test исполнением · интервальная самоверификация субагентом · каждый клейм — с результатом инструмента ЭТОЙ сессии |
✅ | обещаний в отчёте нет: всё найденное закрыто в дереве; §27; ревью гонялось в СЕРЕДИНЕ работы против явных критериев; каждое число выше — с командой |
| §6 три оси | ✅ | round-trip формата (§15, §20) · слепота гейта (§16, §17) · общность: в Go, которую я тронул, нет ни одного литерала пары/письменности/орфографии (проверено грепом по девяти файлам); нормализация — strings.TrimSpace, определённый Юникодом, а не скриптом |
| §8 заявление = команда · §9 эхо до работы · §10 «что НЕ удалось» · §11 развилки | ✅ | §14–§27 · выдано до кода · §24 · §21–§23 |
27. Дифф ^func Test — исполнением
Удалено: НОЛЬ — сводный счёт и обе команды в §40; поимённая половина проверки прошла по семнадцати пинам, которые отчёт пака называет по имени (каждый существует ровно один раз).
Тронуто (тело изменено, сигнатура — нет), и вот почему это законно:
cmd/tmctl/exitcontract_test.go—TestTheRefusalTableIsTotalперестал держать ручной список и читает классы из ИСХОДНИКА обходомGenDecl/ValueSpec(⚠ не по*ast.BasicLitвcase, как соседний гейт диспетча: значенияRefusalClassобъявлены КОНСТАНТАМИ, и копирование той техники воспроизвело бы её собственную слепоту — это её же пинг);internal/membank/decisions_test.go—TestDecisionByBankIDсверяется с НЕЗАВИСИМО посчитанными константами;decisions_fuzz_test.go— оракул 4 переписан с тавтологии на неподвижную точку;internal/pipeline/bankdecisions_test.go,statusartifacts_test.go— сигнатураApplyBankDecisionsполучилаcontext.Context.
Новый файл: internal/pipeline/bankchain_test.go (цепь §4.12).
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() побайтно равен прежнему склеенному сообщению.
⚠ Унаследованное замечание, не дефект пака: всё, на чём эта дверь ключуется, проходит через
internal/text/norm.go, где таблица trad2simp и ветка катакана→хирагана живут В GO, а не в langpack'е.
Пара, которой в репо нет, деградирует до NFKC+lowercase — то есть не блокирована, но фолд Go-резидентный.
Пингом, не правкой: это шире двери.
31. Батареи после всех правок — и одна правка в самой батарее
Сняты ПОСЛЕ рефакторинга §33 и после всех правок по воркфлоу-ревью §34 — то есть описывают дерево,
которое передавалось: make lint → 0 issues. · go vet ./... + go vet -tags live ./... + оба
прохода -vettool → чисто · gofmt -l . → пусто · make battery → EXIT=0, полностью зелено,
включая оба названных промтом -race-флейка · make battery-stand → EXIT=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/TestManifestOfAnOlderDocumentVersionIsRefused — MINE 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 3–12 в моменты замера), и под
этой нагрузкой два прогона батареи подряд закончились 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, §43 4.5) —
грепать надо его. Тело 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, §26, §27, §31, §33) — второй копии этот отчёт намеренно не держит.
Два остатка были признаны вне полномочий пака, и оба закрылись позже: межкнижная гонка за один
mined_delta — снятием объявляемых ключей (D39.158 п.5; остаток «второй конфиг ТОЙ ЖЕ книги» заведён
строкой бэклога 222), запись карты подписи обычным os.WriteFile — атомарной записью с конвертом
(§43 4.10).
⚠ Вывод, который стоит унести дальше самого пака. Это было ТРЕТЬЕ ревью подряд, и каждое находило настоящие дефекты, которых не видели предыдущие: одиночный старший ревьюер — три (§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;
удалено за все три пака НОЛЬ, и это проверено дважды — грепом по диффу и поимённо по пинам, которые
отчёты паков называют по имени.
Переименовано (не удалено): TestTheStopClearsWhenTheOwnerDecidesTheWholeSignatureMap →
TestTheChainOwnerDecidesTheWholeMapAndTheRunGoesOn (замена по заказу §4.12, новая семантика; вторая цепь —
TestTheStopIsAFlagNotAGateOnDecisions — и есть пин старой формулы) · TestDecisionPathsDefaultByConventionOnAnEmptyKey
→ TestDecisionPathsAreAPureFunctionOfTheConvention; сняты вместе с предметом (§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. Попунктный чек §4 — все тринадцать
Комплектность сверена механически: grep -cE '^### 4\.' docs/archive/prompts/BACKEND_SEAM_FIX2_SESSION_PROMPT_2026-08-27.md → 13.
| пункт | статус | чем закрыт |
|---|---|---|
| 4.1 память «предъявленного стопом» | ✅ | store v16 bank_stop_presented (плоское множество поверхностей кластеров — Src+Aliases, INSERT OR IGNORE, без обрезки); предикат hasUnpresentedCluster — пересечение кластера с памятью, домен — ИТОГОВАЯ карта после потолков (обе секции); двусторонний остаток записан пином. Цена миграции названа (§37). Пины: цепь Б, stopflag_test.go, stopmemory_test.go, реверс-новизна; мутации FX1–FX3 |
| 4.2 порядок и момент записи | ✅ | карта (атомарно) → память, в момент СТОПА; отказ памяти НЕ валит стоп (ERROR-строка, названо §37); «подтверди карту» НЕ заведено; draft-only ветка названа. Развилка карта/SQLite-транзакция решена явно (направление потерь — доброкачественный повторный стоп) |
| 4.3 авто-провод при поднятом флаге | ✅ | «останавливаться ли» и «проводить ли» — два решения; авто-провод на ЛЮБОМ неостановившемся прогоне; вторая половина названа: остановившийся прогон авто-банк НЕ пишет (решение с доводом, §37). Мутация FX4; пины в WHATSurvives и цепи Б |
| 4.4 хвост за потолком | ✅ не лечился | пере-замер своей командой (2354/209/11/198, §38) — числа промта без первоисточника не цитируются; фильтр карты НЕ заведён; пинг в журнале |
| 4.5 единая точка фиксации | ✅ | вердикт (decisionVerdict) / исход (finishReport — единственный конструктор, свитч по applyOutcome, шесть исходов); «записано» — пост-состояние ПЕРЕЧИТАННЫХ файлов; пин TestEveryOutcomeStampsItsOwnMode + греп-инвариант (присваиваний вне конструктора нет — подтверждено интервальным ревью). Вердиктная половина сохранена (сильная форма отвергнута по букве промта) |
4.6 signature.undecided |
✅ | пере-документирован: НЕ отвечает на вопрос о стопе (в обе стороны), информационный счётчик для экрана подписи; память дверь НЕ читает (решение с доводом: ответ требовал бы следующего намайненного набора, которого нет ни у кого за $0); поле не удалено |
| 4.7 полоса в два яруса | ✅ | пол «нет отката, повтор безопасен»; клауза «ничего не произошло» — у пяти исходных классов; гардрейл «разрушительное — только на классе» буквой; класс write_incomplete (exit 15) один на оба исхода, различие — written_*; отказ по обоим капам печатает отчёт (контракт сведён к одному носителю). Мутации FX18–FX20 |
| 4.8 подготовка обоих файлов | ✅ | stage обоих (write+sync+chmod) → два rename → fsync каталога; заявлено НЕ шире сделанного (атомарность против среды, не против SIGKILL — довод в writeDecisionFiles, остаток в §41); отказ синка — класс 15 с оговоркой долговечности. Мутации FX16–FX17, пины staged/unsyncable |
| 4.9 Clean + конвенция | ✅ | (а) filepath.Clean абсолютных в resolve, репро приёмки закрыто пином; (б) оба ключа СНЯТЫ retired-отказом с лечением в тексте (прецедент adult:), пути — чистая функция каталога и book_id: МЕЖКНИЖНАЯ гонка (⛔-пинг прошлого пака) снята; воркфлоу-ревью показало ОСТАТОК той же оси — второй конфиг ТОЙ ЖЕ книги (один каталог, один book_id, другой объявленный project_db) пишет те же файлы под другим локом; остаток — пинг, не решение пака (§45). Мутации FX12–FX13 |
| 4.10 карта: атомарность и конверт | ✅ | writeFileAtomic + конверт map_version/map_id (sha256 содержимого), рендер ДОКАЗЫВАЕТ round-trip (RenderSignatureMap через renderProved); легаси-карта читается; карта НЕ накопитель (слияния нет). Мутации FX14–FX15; живьём — инод-пин |
| 4.11 слепые пятна | ✅ | все шесть строк таблицы закрыты посадкой+пином (normText-класс · субъект-класс · субъект-окно · абсолютность путей отчёта двери · границы обоих капов · граница окна); пин семантики §4.1 — цепь Б + два переписанных харнесс-теста (возврат старой формулы краснит ТРИ теста, вывод tmmutate по FX2 поимённо — §40). Вакуумный пин пойман посадкой ДО записи (§38) |
| 4.12 наследие — списком | ✅ | шапка mining.go · комментарий у loadMinedRejects · unsignedEngineSurfaces · docstring runBankMiningStop · сообщение стопа · renderSignatureStop + два стейл-хинта render.go · help --verify-bank · WaveSignatureStop/waverun · комментарий v16; новое состояние «дельта непустая, нового нет» — явная WARN-строка; цепной тест переписан (замена названа: TestTheStopIsAFlagNotAGateOnDecisions); пинги с ПОРЯДКОМ и миграционная нота — в журнале |
| 4.13 платных вызовов ноль | ✅ | фейковый провайдер + харнесс + копия стенда; sha256 стендовой базы не сдвинулся; рантайм по платному пути — PLAUSIBLE; «выигрыш по чекпоинтам» НЕ заявлен (механизма нет, §41) |
| §5 батарея/каталог/проба/артефакт | ✅ | батарея после каждой правки; make battery EXIT=0; стенд-батарея красна ровно на jieba-парити; каталог 36→56, NOTHING роняет гейт (свойство не тронуто); живая проба §39; эта база |
| §6 три оси | ✅ | семантика флажка — прогонами движка (цепи, харнесс), без кода платформы · точка фиксации — конструктор + греп + пин исходов · слепота гейта — 56 посадок / 0 неожиданных |
| §7–§11 | ✅ | план до кода (SEAM_FIX2_PLAN.md, механическая сверка 13) · эхо до работы · «что НЕ удалось» §41 · развилки решались по букве промта, интерпретаций против него нет |
44. Проверка ЗАКРЫТИЯ — отдельным агентом, не чинившим (§5.1)
Прогнана по презумпции «каждое заявленное закрытие ложно, пока не увидено исполнением»: все 13 строк §43
и все 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). - Клауза «ничего не произошло» у классов 10–14 сужена до «состояния КНИГИ»: пред-полётный бэкап
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.md → 6.
| пункт | статус | чем закрыт |
|---|---|---|
| 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 battery → EXIT=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 пере-сняты; найденные расхождения исправлены по месту выше.