1157 lines
144 KiB
Markdown
1157 lines
144 KiB
Markdown
# Доказательная база пака «входная дверь шва» (движковая половина D39.156)
|
||
|
||
Бэкенд-сессия 24.08.2026. Промт архивирован — `docs/archive/prompts/BACKEND_SEAM_PACK_SESSION_PROMPT_2026-08-27.md`; записка-план — `SEAM_PACK_PLAN.md`.
|
||
**Каждое число — с командой, которой получено;** приёмка пере-раняет выборочно. Команды из `backend/`.
|
||
|
||
**Карта чтения — что здесь ЖИВОЕ, а что улика закрытого пака.** Четыре пака приняты и заленджены
|
||
(D39.158), поэтому целиком файл читать не надо; входи по предмету:
|
||
· **живые остатки и PLAUSIBLE** — §41, §24, §7, §9, хвосты §45 и §46 ·
|
||
· **пинги, правок по которым НЕ делали** — §10, §23, хвост §46 ·
|
||
· **решения зоны с доводами** — §21 (девять мелочей), §37 (модель флажка), §17 (выживший гейт) ·
|
||
· **провенанс чисел** — §18 (цена глагола), §29 п.1 (кап в количестве), §38 (хвост за потолком), §15 (цена рендера) ·
|
||
· **правила протокола, вынесенные инцидентами** — §11, §25, §35 ·
|
||
· **что построено** — §0, §28, §36 (состав трёх паков), §40 (счёт тестов).
|
||
Попунктные чеки заказов (§12, §26, §43) вырезаны как прожитая приёмка — на их местах стоят адреса.
|
||
|
||
## 0. Состав пака
|
||
|
||
**Новое:** `internal/membank/decisions.go` (чистая логика решений) · `internal/pipeline/bankdecisions.go`
|
||
(обвязка: лок, чтения, записи, отчёт) · `cmd/tmctl/bankapply.go` (CLI) · `internal/seed/decode.go`
|
||
(строгие декодеры схемы) · `internal/standdata/standdata.go` (тест-резолв корпуса) + 6 тест-файлов и
|
||
2 фаззера.
|
||
**Тронуто:** `internal/config/book.go` (конвенционные дефолты) · `internal/pipeline/mining.go`
|
||
(дефолт-осведомлённые чтения) · `internal/pipeline/status.go` (`status_version` + `artifacts{}`) ·
|
||
`internal/pipeline/export.go` (`export_version`) · `internal/pipeline/bankexport.go` (`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 (дофикс), §38 (фикс2), §46 (фикс3).
|
||
|
||
## 2. Адверсариальные посадки: 16/16 краснеют
|
||
|
||
Харнесс сажает мутацию → гоняет тесты НАЗВАННОГО пакета → откатывает; он же снимает sha256 целей до
|
||
прогона и проверяет после (`TREE IS NOT PRISTINE`). ⚠ Пакет пина ≠ пакет правки.
|
||
|
||
| # | посадка | пакет пина | что покраснело |
|
||
|---|---|---|---|
|
||
| M1 | дефолт срабатывает на НЕпустом ключе | `config` | `TestDeclaredDecisionPathIsNotDefaulted` — ⚠ пин СНЯТ вместе с предметом (ключи retired, D39.158 п.5; §40) |
|
||
| M2 | частичный результат переживает отказ (чистый слой) | `membank` | `TestAllOrNothing` +2 |
|
||
| M3 | частичная ЗАПИСЬ переживает отказ (обвязка) | `pipeline` | `TestBankApplyRefusesAPartialSet` |
|
||
| M4 | повторный вызов пишет файл заново | `pipeline` | `TestBankApplyRepeatIsAByteNoOp` +2 |
|
||
| M5 | лок берётся БЛОКИРУЮЩЕ | `pipeline` | `TestBankApplyRefusesABusyProject` (по 10-сек. границе) |
|
||
| M6 | читающий глагол принимает ключи | `cmd/tmctl` | `TestOnlyTranslateAcceptsKeys` |
|
||
| M7 | занятый лок отказывает классом 10 вместо 12 | `pipeline` | `TestBankApplyRefusesABusyProject` |
|
||
| M8 | пишется `status: auto` вместо `approved` (мина №1 строки 199) | `membank` | `TestApproveWritesAnExplicitApprovedStatus` +2 |
|
||
| M9 | решение, тронувшее только rejects, пере-рендерит и delta | `pipeline` | `TestADeclineDoesNotRewriteTheDelta` |
|
||
| M10 | промоушен теряет алиасы строки банка | `membank` | `TestPromotionKeepsTheBankRowAliases` |
|
||
| M11 | voice/address сида не сверяются с результатом | `membank` | `TestDecliningACharacterAVoiceProfileNamesIsRefused` |
|
||
| M12 | два отказа по одной поверхности проходят как два терма | `membank` | `TestTwoDeclinesOfOneSurfaceAreIllFormed` |
|
||
| M13 | пред-существующая поломка проглатывается молча | `membank` | `TestAPreExistingCollisionDoesNotBlockAnUnrelatedDecision` |
|
||
| M14 | документ из одних комментариев снова не грузится | `seed` | `TestDecodeAcceptsAnEmptyDocument` |
|
||
| M15 | ruby-чтения выброшены из судимого набора | `membank` | `TestApprovingARubyReadingOfASeedTermIsRefused` |
|
||
| M16 | инертный отказ по алиасу дельты принимается | `membank` | `TestDecliningAnAliasOfAnApprovedTermIsRefused` |
|
||
|
||
## 3. Живые пробы на реальной книге стенда
|
||
|
||
В КОПИИ вне рабочего дерева (`cp -r books/gu-zhenren/minirun-verify2 <scratch>/live`, пути пере-указаны
|
||
на копию). **В `books/` не записано ничего.** ⚠ Те же пробы пере-гонялись после дофикса (§19) и после
|
||
фикс2 (§39) — там картина СВЕЖЕЕ; здесь оставлено то, что позже не пере-снималось.
|
||
|
||
| проба | результат |
|
||
|---|---|
|
||
| занятый лок (flock держит сторонний процесс) | `exit=12`, `wall=0.02s` |
|
||
| `decline` над рукописной дельтой | md5 дельты не изменился, комментарии владельца целы |
|
||
| reject-файл ИЗ ОДНИХ КОММЕНТАРИЕВ | `exit=0` (до фикса R1 было `exit=10`, `seed: EOF`) |
|
||
| все seed/delta/signature/auto-bank стенда под строгим загрузчиком | 21 из 21 чисто (независимо: 29/29 на более широкой выборке, включая книги dovodka/role-topology/tenant-panel); пере-снято после дофикса как 45 из 45 — §31 |
|
||
|
||
## 4. Замеры
|
||
|
||
| что | число | вердикт |
|
||
|---|---|---|
|
||
| **цена глагола** (`bank-apply --dry-run`, `/usr/bin/time`) | ⚠ числа пака (5 000 термов → 1.60с/133МБ · 20 000 → 9.08с/350МБ) **не воспроизводятся, а по памяти занижены примерно вдвое — НЕ наследовать**: пере-замер §18, худший случай §29 п.1 | датум для таймаута платформенного воркера берётся из §18/§29, не отсюда. Что осталось верным: бюджет платформы на ОДИН вызов движка — 60с (`platform/internal/runs/reconcile.go:280`); реальная книга (кап майнера 200 за акт, сид стенда 58) — под секундой; цена — двойной прогон проверки документа, и он же не даёт пред-существующей поломке блокировать починку |
|
||
| **контеншн** (20 одновременных `bank-apply`) | `2×0`, `18×12`, после гонки `seed-lint OK` | лок арбитрирует чисто; два нуля законны — второй взял лок после первого и оказался no-op |
|
||
| **деньги** (sha256 базы до/после) | `7ea61ccecada341b` → идентичен | глагол не сдвинул ни байта хранилища. Сильнее SELECT по ledger: не требует sqlite и покрывает всё хранилище |
|
||
| **детерминизм** (тот же документ в ДВУХ процессах, `cmp`) | побайтово идентичны | закрывает единственный реалистичный источник недетерминизма в Go — порядок обхода map |
|
||
| **`FuzzDecisionDocument`** | **487 163 исполнения, 0 падений** | оракулы: не паникует · всё-или-ничего · **ни одно решение не теряется молча** (каждый входной индекс ровно один раз в 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.
|
||
|
||
## 10. Пинги — предметы ВНЕ карты пака, правок не делал
|
||
|
||
1. **`rubyToCandidates` мёртв в проде — пере-проверено 02.09.** `grep -rn "rubyToCandidates" --include=*.go .`
|
||
→ 4 хита: определение, два комментария и ЕДИНСТВЕННЫЙ вызов — из теста
|
||
(`backend/internal/membank/memseed_test.go:204=cands := rubyToCandidates`). ⚠ Адрес механизма из
|
||
первой редакции («`seeding.go:36-38` строит `manualSrcs` и никуда не передаёт») МЁРТВ: ruby-путь
|
||
переехал, и сегодня прогон читает чтения в
|
||
`backend/internal/pipeline/bankmaterialize.go:84=RubyReadingsForBook(r.Book.BookID)` и отдаёт их
|
||
ТОЛЬКО в `AttachRubyAliasesToManual` — аргумента `manualSrcs` в проде не строит вообще НИКТО, то
|
||
есть кандидатная половина просто не подключена. Следствие прежнее: строк `Source:"ruby"` в банке не
|
||
возникает, а платформа держит для них ветку словаря
|
||
(`platform/internal/ingest/bank.go` `termOrigin` → `OriginAnnotated`).
|
||
2. **Гейт `TestDispatchCommandsCoversTheSwitch` слеп к `case` с КОНСТАНТОЙ** — считает только
|
||
`*ast.BasicLit`, поэтому команда, объявленная константой, невидима гейту, чей смысл в том, что дефект
|
||
строки 176 «отрастал трижды». Обнаружено посадкой; я вернулся к литералу (конвенция файла
|
||
`if cmd == "seed-lint"`), поэтому дыра не эксплуатируется.
|
||
3. **`redrive` — платный путь без канала ключей аргументом.** Промт: «флаг только у `translate`», сделано
|
||
ровно так. Канал у него есть (конвенционный `.env`), и платформа его не зовёт
|
||
(`runner/engine.go` строит только translate/status/manifest/export). Развилка следующему паку.
|
||
4. **Проекция берёт тот же лок**, что и применение ⇒ «поправить термин при живом прогоне» даёт 12 даже
|
||
на ПРЕВЬЮ, на всю длину прогона. Платформенный пак обязан это спроектировать, а не обнаружить.
|
||
5. **Пути `mined_delta`/`mined_rejects` опубликованы в `status --json`.** Законно по п.1 закона, но эти
|
||
файлы версионного конверта не несут, а опубликованный путь читается как приглашение. Единственная
|
||
изгородь против их парсинга платформой — комментарий. Самое мягкое место в «формат знает ОДНА сторона».
|
||
6. **Схема сида без `omitempty`** — `yaml.Marshal(seed.File{})` печатает все ключи, включая пустые. Уже
|
||
существовало (`miner_emit.go` `DeltaYAML` рендерит так же), глагол наследует. Смена тегов = правка
|
||
формата данных (`internal/seed/seed.go` это оговаривает).
|
||
7. **Два флейка в `internal/store`, пред-существующие.** `TestTheSeamIsNotChargedToTheStoreOperationBudget`
|
||
и `TestKillMinus9LosesAtMostOneCall`; тесты НЕ трогал (D39.121: правка теста ради зелени недопустима).
|
||
Оба меряют ВРЕМЯ (первый сознательно сжимает `opTimeout`, второй гоняет helper против таймера SIGKILL);
|
||
покрытие независимо подтверждает, что `LockProject`/`Release` этими тестами не исполняются вовсе.
|
||
⚠ Первый A/B (3 раунда × 10) дал `kill9` 2/30 на HEAD против 0/30 на дереве пака — **это НЕ значит
|
||
«пак улучшает»**: на таком числе событий это шум (Фишер p≈0.5); решающее и достаточное — что флейк
|
||
живёт на дереве, которого пак не касался. Окончательная атрибуция — 5-раундовый A/B в §31.
|
||
|
||
## 11. Два правила протокола, вынесенные инцидентами сессии
|
||
|
||
1. **Эталон самопроверки снимается ДО первого изменения, и харнесс мутаций обязан фингерпринтить цели.**
|
||
Незавершённый прогон `mutate.py` оставил посадку M4 в дереве ~40 минут, выглядящей продакшн-кодом, и
|
||
сторонний ревьюер снял с этого дерева замеры (он же собрал `tmctl` из незакоммиченного дерева, не
|
||
сняв отпечаток, — на класс наступили обе стороны). Посадку поймал и правило теперь держит пин
|
||
`TestBankApplyRepeatIsAByteNoOp`: он же документирует и форму инцидента, и довод (инод плюс
|
||
рукописный комментарий в шапке — то, что пере-рендер уничтожает, а проверка содержимого не заметила
|
||
бы). ⚠ Класс повторился следующей сессией ВНУТРИ самого харнесса — §25; отпечаток целей и каталога у
|
||
инструмента сегодня свой — §16.
|
||
2. **Одиночный прогон — не замер.** Атрибуцию флейков я сперва вывел из одного прогона («зелено в
|
||
изоляции ⇒ нагрузка»), затем другой одиночный прогон дал ровно обратное. Решает только чередующийся
|
||
A/B (§10 п.7).
|
||
|
||
## 12. Чек заказа и дифф тестов — сняты
|
||
|
||
Попунктный чек шестнадцати пунктов промта и пофайловый дифф `^func Test` этого пака были прожитой
|
||
приёмкой и вырезаны: сам заказ живёт в `docs/archive/prompts/BACKEND_SEAM_PACK_SESSION_PROMPT_2026-08-27.md`,
|
||
доказательства по каждому пункту — в §1–§11 выше, а сводный счёт тестов по всем трём пакам («удалено
|
||
НОЛЬ», обе команды, поимённые переименования) — в §40.
|
||
|
||
---
|
||
|
||
# Дофикс той же двери (сессия 25.08.2026)
|
||
|
||
Промт (16 пунктов §4) архивирован — `docs/archive/prompts/BACKEND_SEAM_FIX_SESSION_PROMPT_2026-08-27.md`;
|
||
записка-план — `SEAM_FIX_PLAN.md`. Секции 0–12 выше — улика приёмки, не тронуты.
|
||
|
||
## 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>
|
||
```
|
||
|
||
**Пофайловая таблица «посадка → пакет пина → что покраснело» здесь НЕ дублируется:** её живой и
|
||
авторитетный носитель — сам каталог `backend/cmd/tmmutate/mutations.json` (у каждой записи `id`, `why` —
|
||
свойство, за которое она отвечает, — и `package`), а имена пинов инструмент называет ПОИМЁННО в выводе
|
||
прогона. Снимок в отчёте протух бы в первом же паке: записей было 36 на дату дофикса, 56 после фикс2,
|
||
60 после фикс3.
|
||
|
||
## 17. Единственная выжившая мутация — и почему пин на неё невозможен
|
||
|
||
**G (байтовый гейт).** Заказ §4.11(ж) просит поведенческий пин. Его нет и быть не может: гейт
|
||
**недостижим за первым**, и это доказывается, а не предполагается.
|
||
|
||
`deltaChanged = DeltaTouched && changedDoc(диск, next, пусто)`. `DeltaTouched` ставится только когда
|
||
решение реально изменило документ, а `next` — канонический рендер УЖЕ ИЗМЕНЁННОГО документа. Чтобы
|
||
`changedDoc` вернул false, байты на диске должны БЫТЬ этим рендером — но тогда документ, разобранный из
|
||
них, уже равен результату, никакое решение ничего не меняло, и `DeltaTouched` был бы false. Нормализация
|
||
щели не открывает: файл, чьи значения несут окружающие пробелы, в себя же не рендерится.
|
||
|
||
**Диспозиция:** гейт ОСТАВЛЕН как второй рубеж на случай, когда `*Touched` станет грубее сегодняшнего, и
|
||
запинен как КОНТРАКТ ФУНКЦИИ (`TestChangedDocIsTheSecondFenceAndItsContractIsPinnedDirectly`), включая
|
||
правило «отсутствующий файл + пустой документ — НЕ создавать». Снести его я не стал: изгородь, за которую
|
||
я вижу не до конца, дешевле оставить, чем убрать.
|
||
|
||
## 18. Замеры — своей названной методикой
|
||
|
||
**Методика:** `/usr/bin/time -f '%M %e'`, 3 прогона на размер, медиана, машина без нагрузки,
|
||
`bank-apply --dry-run` (записи нет), документы из N `approve` с короткой заметкой.
|
||
|
||
| N решений | размер документа | wall (мин/мед/макс) | maxRSS (мин/макс) |
|
||
|---|---|---|---|
|
||
| 1 000 | 82.9 КБ | 0.57 / 0.58 / 0.74 с | 51.9 / 52.1 МБ |
|
||
| 5 000 | 423 КБ | 4.91 / 5.18 / 5.36 с | 171 / 215 МБ |
|
||
| 10 000 | 848 КБ | 14.53 / 15.73 / 16.54 с | 391.7 / 391.9 МБ |
|
||
| 20 000 | 1.72 МБ | 46.72 / 52.16 / 52.20 с | 740 / 762 МБ |
|
||
|
||
**Разночтение по памяти между паком и приёмкой закрыто в пользу приёмки.** Пак дал 133/217/350 МБ на
|
||
5k/10k/20k, приёмка 199/395/798 МБ, мой замер — 203/392/745 МБ. Числа приёмки воспроизводятся, числа пака
|
||
занижены примерно вдвое. **По времени сойтись не удалось:** пак дал 1.60/3.62/9.08 с, у меня 5.2/15.7/50 с
|
||
— в 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(...))`, вызывается тот же код,
|
||
не его копия). Отсюда поле отчёта:
|
||
|
||
```json
|
||
"signature": { "map": "<путь>", "surfaces": 2, "undecided": 0 }
|
||
```
|
||
|
||
Пустой `map` значит «прогон до границы банка ещё не доходил». В режиме проекции число — то, что БУДЕТ
|
||
после применения (единственное чтение, которое может иметь превью).
|
||
|
||
⚠ **СТАРАЯ ФОРМУЛА ЭТОГО АБЗАЦА ОТМЕНЕНА, и вот действующая.** Пак писал «`undecided > 0` — факт, что
|
||
стоп не погаснет»; под моделью флажка (D39.144, фикс2) поле **не отвечает на вопрос о стопе НИ В ОДНУ
|
||
СТОРОНУ**, и это записано в самом коде:
|
||
`backend/internal/pipeline/bankdecisions.go:92=IT DOES NOT ANSWER «погаснет ли стоп»`. Причина —
|
||
механическая: стоп срабатывает, когда СВЕЖЕ-намайненная карта следующего прогона держит кластер вне
|
||
предъявленной памяти (`hasUnpresentedCluster`, `internal/pipeline/mining.go`), а этого набора ещё не
|
||
существует ни у кого за $0. Что поле ЕСТЬ: информационный счётчик для экрана подписи — «о скольких
|
||
поверхностях последней карты владелец ещё не высказался», считанный ТЕМ ЖЕ правилом, которым прогон
|
||
сворачивает решения в банк (`ownerHandled(unsignedEngineSurfaces(...))`).
|
||
|
||
Пины сегодня — цепи `internal/pipeline/bankchain_test.go`
|
||
(`TestTheChainOwnerDecidesTheWholeMapAndTheRunGoesOn` · `TestTheStopIsAFlagNotAGateOnDecisions`): решение
|
||
по всей карте прогон НЕ останавливает, а стоп — флажок, а не гейт на решения.
|
||
|
||
## 23. Пинги — предметы ВНЕ карты дофикса, правок не делал
|
||
|
||
1. **`exit 2` при OOM читается платформой как успех** (заказ §4.5 прямо называет это пингом). Движок при
|
||
нехватке памяти выходит `fatal error: out of memory` + код 2, а `platform/internal/ingest/exit.go`
|
||
читает 2 как «команда СДЕЛАЛА работу». Класс общедвижковый. Кап §4.5 не даёт `bank-apply` стать первым,
|
||
кто до него доедет, но самого класса не снимает.
|
||
2. **Каталог книги без права записи даёт `exit 1`**, и платформа спишет проблему прав ХОСТА в бюджет
|
||
попыток КНИГИ. Класс движка корректен (см. §21); чинить надо чтение кода 1 на платформе.
|
||
3. **Проекция отвечает «что применится», а не «почём».** Рекомендация: **не строить**. Закон п.7 про
|
||
«почём ДО подтверждения» уже исполнен там, где есть деньги (rebill-смета, `status --json`), а этот
|
||
глагол $0 по построению — единственная его «цена» это пере-редактура, которая квотируется и гейтится в
|
||
другом месте и другим согласием. Поле «$0» в отчёте было бы шумом. Решать оркестратору.
|
||
4. **Карта подписи не удаляется после погасшего стопа** (`os.Remove` в `mining.go` не встречается).
|
||
Пред-существующее. Потребитель, читающий файл как ответ на «есть ли что подписать», читает уже
|
||
подписанную карту. Зафиксировано ЯВНО тестом цепи как текущее поведение; честный ответ на тот же
|
||
вопрос — `signature.undecided` (§22).
|
||
5. **Цена глагола Θ(N²) по построению** (§18) — это то, почему кап на КОЛИЧЕСТВЕ стоит на 5 000, а не
|
||
выше. Лечится индексом по ключу терма в `applyOne`/`dropTerms` и по нормализованной поверхности в
|
||
`addReject`/`dropReject`; вне заказа, не делал. Поднимать кап без этого нельзя.
|
||
6. **Апстрим-дефект `yaml.v3` (§15) лечится по-настоящему двумя способами,** и оба вне пака: (а) тип
|
||
скаляра схемы с `MarshalYAML`, возвращающим узел с явным стилем (проверено по исходнику: явный стиль
|
||
проверяется РАНЬШЕ ветки блочного скаляра, `encode.go:557-570`); (б) переезд на `go.yaml.in/yaml/v4`
|
||
— там этот случай починен, но это релиз-кандидат и смена module path сразу в двух зонах. Мой обход
|
||
несёт ссылку и характеризационный тест, а не «похоже, баг yaml».
|
||
7. **Цена доказывающего рендера** ≈5 % времени (§15); по памяти разница внутри разброса прогонов и НЕ
|
||
измерена. Плюс `notCanonical` теперь тоже разбирает документ. Названо, чтобы следующий замер не
|
||
приписал это чему-то другому.
|
||
|
||
## 24. Что НЕ доказано и что НЕ удалось
|
||
|
||
- **Рантайм-вердикт по ПЛАТНОМУ пути остаётся PLAUSIBLE.** Санкции на платные вызовы у дофикса нет, и
|
||
ни одного платного вызова не сделано (`sha256` базы стенда не сдвинулся). Это ожидаемый исход, а не
|
||
дефект отчёта.
|
||
- **Цепь §4.12 доказана на СИНТЕТИЧЕСКОМ контрасте**, а не на книге стенда: банк-стоп на этой машине не
|
||
воспроизводится вовсе (`pipeline.yaml` требует jieba-контраста, которого клон не получает по
|
||
построению — строка 123). Ограничение названо в комментарии самого теста: синтетика меняет, КАКИЕ
|
||
кандидаты предложены, но не то, проходит ли подписанная строка фильтр подписи. Для вопроса «гаснет ли
|
||
стоп» это корректно; для качества майнера — нет.
|
||
- **Расхождение по ВРЕМЕНИ с замером пака (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 и пофайловый дифф `^func Test` дофикса вырезаны как прожитая
|
||
приёмка. Адреса: заказ — `docs/archive/prompts/BACKEND_SEAM_FIX_SESSION_PROMPT_2026-08-27.md`
|
||
(комплектность сверялась механически: `grep -cE '^### 4\.' <промт>` → 16); доказательства по каждому
|
||
пункту — §15–§25 выше; сводный счёт тестов и переименования — §40; каталог посадок —
|
||
`backend/cmd/tmmutate/mutations.json`.
|
||
|
||
⚠ D39.158 называет «вход §26» — читатель, пришедший по этой ссылке, попал верно: адреса выше.
|
||
|
||
## 28. Состав дофикса
|
||
|
||
**Новое:** `internal/seed/normalize.go` (канонический вид документа) · `internal/membank/problems.go`
|
||
(вердикт загрузчика по субъектам) · `cmd/tmmutate/` (харнесс мутаций + каталог посадок; счёт записей на
|
||
дату дофикса и команда актуального счёта — §16) ·
|
||
`internal/pipeline/bankchain_test.go`.
|
||
**Тронуто:** `internal/membank/decisions.go` (доказывающий рендер, нормализация запроса, набор-осознанный
|
||
алиас, перевёрнутое окно, байты результата, `note` не стирается) · `memseed.go`, `memvoice.go` (вердикт
|
||
как список субъектов; `Error()` побайтно прежний) · `internal/pipeline/bankdecisions.go` (кап, контекст,
|
||
пер-файловый `canonical_rewrite`, `signature{}`, байты из чистого слоя) · `mining.go` (`signatureMapPath`
|
||
и `rejectSurfaces` — одно определение на двоих) · `status.go`, `manifest.go` (единственный производитель
|
||
конверта путей; конверт на манифесте) · `internal/config/book.go` (попарно различные пути, `book_id` как
|
||
элемент пути) · `cmd/tmctl/invocation.go` (гарды на присутствие, довод отказа), `bankapply.go`, `main.go`
|
||
(контекст) · **`Makefile`** (пер-пакетный таймаут назван явно — §31, это единственная правка в самой
|
||
батарее и она ничего не ослабляет) + семь тест-файлов.
|
||
|
||
## 29. Ревью старшей моделью: три дефекта, каждый воспроизведён МНОЙ и починен
|
||
|
||
Ревью запрашивалось по решениям, где заказ оставлял форму на сессии. **Каждую находку сессия
|
||
воспроизвела сама прежде, чем чинить** — чужой результат, взятый на веру, ровно так же становится
|
||
нормой. У двух подтверждённых решений ревью дало довод СИЛЬНЕЕ, и довод заменён в комментариях кода: у конверта путей на манифесте — что бамп версии выбросил бы все сохранённые сайдкары
|
||
и пере-нарезал каждую книгу ради аддитивного поля (компромисс назван вслух: манифест семантически про
|
||
дерево глав, путь артефакта там гость); у SIGTERM — атомарность и порядок записей вместо
|
||
длины непрерываемого участка.
|
||
|
||
### 1. Кап в БАЙТАХ не ограничивает ВРЕМЯ — мой довод был неверен
|
||
|
||
Расчёт «1 МиБ ≈ 12 000 решений ≈ 20 с» брал ≈85 байт на решение из документа с заметками. Минимальное
|
||
законное решение — `{"action":"decline","src":"t1"}`, ≈35 байт. **Пере-мерено тем же методом**
|
||
(`/usr/bin/time -f '%e %M'`, документы из минимальных `decline`):
|
||
|
||
| N | размер | wall | maxRSS |
|
||
|---|---|---|---|
|
||
| 5 000 | 174 КБ | **11.32 с** | 40 МБ |
|
||
| 12 000 | 421 КБ | **57.96 с** | 70 МБ |
|
||
| 20 000 | 709 КБ | **153.5 с** | 112 МБ |
|
||
| 28 900 | **1 029 369 Б — ПОД капом** | **308.2 с** | 133 МБ |
|
||
|
||
То есть байтовый кап пускал документ, который платформа убивает по своему 60-секундному бюджету на
|
||
КАЖДОЙ попытке, навсегда, — а строка отказа «split it», ради которой класс 14 и заводился, не печаталась
|
||
никогда. Это тот самый вечный ретрай, который полоса отказов существует чтобы превращать в действие.
|
||
**Починено:** второй кап — на КОЛИЧЕСТВЕ решений, 5 000, тот же класс 14 (11 с в худшей замеренной форме,
|
||
пятикратный запас внутри бюджета платформы). Байтовый кап остался пред-разборным сторожем и больше ничего
|
||
про время не утверждает. Пин `TestTheDecisionCOUNTIsCappedBecauseTheBYTESCannotBound`, мутация W.
|
||
|
||
### 2. Субъект поломки нёс РЕНДЕРИНГ — тупик на оси, у которой нет двери
|
||
|
||
Три проверки из четырёх называют `dst` в тексте сообщения (человеку он нужен), а `dst` — ровно то, что
|
||
решение меняет. Ключом по тексту поломка про терм выглядела НОВОЙ в тот момент, когда дверь меняла
|
||
рендеринг этого терма: строка дельты с `gender: badvalue` (ось, у которой в `Decision` НЕТ поля вообще)
|
||
плюс решение, меняющее `dst` того же терма, давали `exit=14` — одна и та же поломка по обе стороны под
|
||
двумя разными рендерингами, и починить её через дверь нельзя вовсе. Это §4.2 на уровень ниже.
|
||
**Починено:** `subjectOf(class, src, sense, since, until)` — субъект строится ТОЛЬКО из класса поломки и
|
||
идентичности терма, никогда из поля, которое дверь переписывает; экспортированные `[]string`-обёртки не
|
||
изменились, ни один чужой вызов не тронут. Пин `TestAFaultTheDoorHasNoChannelForCannotDeadlockIt`,
|
||
мутация Y.
|
||
|
||
### 3. И то же самое ещё уровнем выше: поломки, которые ЗАГРУЗЧИК прячет
|
||
|
||
Три проверки живут ПОД загрузчиком банка и на документе, который не грузится, не выполняются вовсе.
|
||
Значит, как только решение чинит загружаемость, всё, что они впервые увидели, читается как новое — и
|
||
владельца отвергают за починку того, что мешало всему. ⚠ Первая починка была ПРАВИЛОМ МАСКИ и оказалась
|
||
ХУЖЕ дефекта: правило снято целиком, итоговое лечение и его довод — §30 п.1.
|
||
|
||
### 4. Латентная потеря данных на пути nil-байтов
|
||
|
||
Если рендер ТЕКУЩЕГО документа падает и это пред-существующая поломка, она сообщается, а не отвергает
|
||
(и это правильно) — но тогда `res.DeltaBytes == nil`, а обвязка считала `DeltaTouched && changedDoc(raw,
|
||
nil, …)` → true и **записала бы файл нулевой длины поверх всех одобрений владельца**. До этого входа не
|
||
дошёл ни ревьюер, ни сессия (130+ подобранных значений после нормализации прошли round-trip); но
|
||
`renderProved` существует именно потому, что множество ломающих значений не известно целиком, и
|
||
незащищённый `nil` противоречит его собственной предпосылке. **Починено одной строкой:** nil-байты не
|
||
пишутся никогда. Поведенческого пина нет по той же причине, по которой его нет у G, — вход недостижим;
|
||
инвариант чистого слоя (`accepted ⇒ DeltaBytes != nil`) держит `FuzzDecisionFieldsThroughTheDoor`.
|
||
|
||
### 5. Три комментария несли неверные доводы — исправлены
|
||
|
||
В этом проекте комментарий — контракт, по которому действует следующая сессия, поэтому неверный ДОВОД при
|
||
верном коде опаснее опечатки. Один из трёх стоит того, чтобы стоять и здесь: «путь `yaml.Node` закрыт»
|
||
преувеличивало — закрыт `Encode`, а РУЧНАЯ сборка узла с явным стилем работает и отвергнута по ЦЕНЕ
|
||
(второй рендерер всей схемы), а не по невозможности (§23 п.6).
|
||
|
||
### 6. Ещё две правки по ревью
|
||
|
||
**Нечитаемая карта подписи** возвращалась как `{surfaces:0, undecided:0}` — побайтно то же, что «всё
|
||
решено», единственный ответ, который это поле не должно давать по случайности. Заведено поле
|
||
`unreadable`; пин `TestASignatureMapThatCannotBeReadDoesNotReportAsAllDecided`, мутация Z. ⚠ Здесь закрыта
|
||
только ошибка РАЗБОРА; ошибку ЧТЕНИЯ (`chmod 000`) закрыл §34.
|
||
**У `tmmutate` опасное значение было ДЕФОЛТНЫМ** (`-root .` правит живое дерево). Флаг стал обязательным;
|
||
в шапке названо и то, что отпечаток снимается на старте и потому сертифицирует только дерево, которое
|
||
было чистым в тот момент.
|
||
|
||
## 30. Интервальная самоверификация субагентом: ещё пять дефектов, все воспроизведены и починены
|
||
|
||
Ревью гонялось В СЕРЕДИНЕ работы против явных критериев, read-only, в копии; дерево под ним двигалось,
|
||
поэтому его вердикты пере-исполнены на финальном снимке. Каждый дефект сессия воспроизвела своим
|
||
прогоном прежде, чем чинить; воспроизведение каждого теперь живёт в его пине, а не здесь.
|
||
|
||
### 1. ⛔ Правило маски проглатывало ливлок, который вносил САМ вызов
|
||
|
||
Собственная починка §29 п.3 («если до вызова документ не грузился, а после грузится, эти поломки
|
||
сообщаются и не отвергают ничего») оказалась ХУЖЕ дефекта. На входе «битая строка + `Hero→Odin` с алиасом
|
||
`Shared`» и решениях «починить битую строку + одобрить `Shared→Dva`» дверь диагностировала
|
||
детерминированный ливлок матчера (`firing key "shared" is shared by different terms`), **записала
|
||
документ** и отчиталась `exit 0`, назвав СВОЮ поломку чужой. Комментарий правила («состояние после такого
|
||
вызова никогда не хуже, чем до») был ложью ровно здесь: до — банк не грузился (громкий стоп), после —
|
||
грузится и инжектит ливлок. И два опубликованных поля (`Preexisting`, `preexisting_problems`, оба
|
||
обещающие «этот вызов их не вызывал») становились ложными утверждениями.
|
||
|
||
**Починено иначе и без исключений:** правило маски снято целиком; вместо него `ParseBankSeed` возвращает
|
||
записи, которые он ПОСТРОИЛ, даже когда отказывает. Тогда три проверки под загрузчиком выполняются и на
|
||
«до», и на «после», сравнение по субъекту работает как везде, и оба случая решаются одним правилом:
|
||
починка загружаемости больше не отвергается (поломка видна с обеих сторон), а внесённый ливлок
|
||
отвергается (его субъекта на «до» нет). Пины — обе половины
|
||
`TestRepairingAnUnloadableDocumentIsNotRefusedOverWhatItUNMASKS`, мутация AD.
|
||
|
||
### 2. Побег §4.7 принимал инертный отказ
|
||
|
||
`ownsSurface(in.Delta, …)` пропускал проверку, когда поверхность была термом дельты сама по себе: на
|
||
дельте `Nick→Nik` + `Hero→Nik2 (alias Nick)` отказ `Nick` принимался, строка `Nick` снималась, а `Hero`
|
||
ПРОДОЛЖАЛ фарить `Nick` — ровно тот вред, ради которого §4.7 и заказан. Замерено дополнительно: во всех
|
||
остальных входах побег — no-op (после `dropTerms` алиасом никто не владеет), то есть он менял ответ
|
||
только во вредную сторону, и зеркальный случай, ради которого он якобы существовал, работает без него.
|
||
**Починено:** побег снят — ⚠ `ownsSurface` в коде НЕТ, и заводить заново не надо. Пин
|
||
`TestADeclineIsRefusedWhenTheSurfaceGOESONFiringAsAnotherTermsAlias`, мутация AB.
|
||
|
||
### 3. И то же правило ОТВЕРГАЛО законный отказ, с доводом, который неверен
|
||
|
||
`aliasOwner` не смотрел на СТАТУС: на дельте `Hero (status: auto, alias Nick)` отказ `Nick` отвергался
|
||
доводом «"Nick" is an ALIAS of the approved term … already excluded from proposals». Обе половины довода
|
||
ложны — строка не approved, а `unsignedEngineSurfaces` выбрасывает mined-строки со статусом ≠ `approved`,
|
||
то есть её поверхности из предложений НЕ исключены и отказ как раз осмыслен. `status: auto` в дельте —
|
||
не угол, а ровно то состояние, о котором говорит первая мина формата. **Починено:** `aliasOwner` судит
|
||
только подписанные строки (пустой статус = approved, как дефолтит загрузчик). Пин
|
||
`TestDecliningTheAliasOfAnUNSIGNEDRowIsAccepted`, мутация AC.
|
||
|
||
### 4. §4.14 был исполнен неполно — и тем же способом, против которого заказан
|
||
|
||
Довод отказа `--keys-file` не описывал `bank-apply` — глагол, который этот пак и добавил. А тест,
|
||
написанный ЗАКРЫТЬ §4.14, перечислял четыре команды ручным списком — ровно слабость, против которой
|
||
поднят §4.11(в), воспроизведённая в тесте про неё же. **Починено:** довод называет все девять; тест
|
||
перечисляет из `dispatchCommands` и требует, чтобы имя каждой команды встречалось в её собственном
|
||
отказе. Мутация AF.
|
||
|
||
### 5. Гейт тотальности читал ОДИН файл и одну форму объявления
|
||
|
||
`refusalClassesFromSource` разбирал `refusal.go` и требовал явного `vs.Type == RefusalClass`. Класс,
|
||
объявленный в соседнем файле пакета или конверсией `RefusalX = RefusalClass("x")`, был ему невидим —
|
||
та же слепота, ради снятия которой гейт и переписан. **Починено:** `parser.ParseDir` по всему пакету и
|
||
обе формы объявления.
|
||
|
||
### Плюс два, которые ревью нашло, а я закрыл превентивно
|
||
|
||
**Позиционные субъекты** («record N has no identity») не были запинены вовсе — добавлен
|
||
`TestTwoRecordsWithNoIdentityDoNotBlockEachOthersRepair` и мутация AE. **Фаззер двери возвращался рано на
|
||
отказе** и потому не видел бы возврата блокер-класса: оракул отказа усилен — отказ обязан не выдать
|
||
байтов, не отчитаться о принятых решениях и не изменить ни один документ.
|
||
|
||
### Что ревью подтвердило исполнением (не переделывал)
|
||
|
||
Нормализация идемпотентна на 50-строчном адверсариальном корпусе и КОПИРУЕТ (проверено мутацией выхода
|
||
при неподвижном входе) · ложного провала `renderProved` найти не удалось (400 000 случайных строк по
|
||
алфавиту управляющих/пунктуации/CJK, невалидный UTF-8, NUL, C0/C1, одиночные суррогаты) · оба капа не
|
||
off-by-one · после второй проверки контекста ничего не пишется, `defer lock.Release()` стоит сразу за
|
||
захватом · общности не нарушено ни одной новой строкой · `TestDecisionByBankID` и пин на инъективность
|
||
действительно независимы (все три константы пере-считаны в Python по спецификации из комментария
|
||
`TermID`, а не из его же выхода —
|
||
`backend/internal/membank/decisions_test.go:348=Computed outside Go, from the spec in TermID's own comment`) ·
|
||
`SeedProblems.Error()` побайтно равен прежнему склеенному сообщению.
|
||
|
||
**Общность — проверена грепом по девяти тронутым файлам:** в Go, которую дофикс тронул, нет ни одного
|
||
литерала пары/письменности/орфографии; нормализация — `strings.TrimSpace`, определённый Юникодом, а не
|
||
скриптом.
|
||
|
||
⚠ **Унаследованное замечание, не дефект пака:** всё, на чём эта дверь ключуется, проходит через
|
||
`internal/text/norm.go`, где таблица `trad2simp` и ветка катакана→хирагана живут В GO, а не в langpack'е.
|
||
Пара, которой в репо нет, деградирует до NFKC+lowercase — то есть не блокирована, но фолд Go-резидентный.
|
||
Пингом, не правкой: это шире двери.
|
||
|
||
## 31. Батареи после всех правок — и одна правка в самой батарее
|
||
|
||
Сняты ПОСЛЕ рефакторинга §33 и после всех правок по воркфлоу-ревью §34 — то есть описывают дерево,
|
||
которое передавалось: `make 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, §36–§37) —
|
||
грепать надо его. Тело `ApplyDecisions` стало последовательностью из пяти судящих фаз, из которых
|
||
**ровно одна** что-то меняет (`foldAccepted`): это и есть контракт двери, теперь видимый в самом коде, а
|
||
не только в комментарии.
|
||
|
||
⚠ **Считать длину функции надо по ТЕЛУ** (от `^func` до первой строки ровно `}`), а не от `^func` до
|
||
следующего `^func`: второе правило на методах даёт мусор, и именно им была получена несуществующая
|
||
«норма репозитория в 75 строк» — такой нормы в проекте НЕТ. **Замерено по ТЕЛУ:** две точки входа — **92**
|
||
и **90** строк против **42** у следующей по длине функции тех же двух файлов; максимумы пакетов совсем
|
||
другие — **218** (`Status`, `internal/pipeline`) и **105** (`loadVoiceSections`, `internal/membank`);
|
||
после разбора самая длинная функция обоих файлов — **44** (`signatureState`).
|
||
|
||
⚠ **И вот чем доказательство эквивалентности ОКАЗАЛОСЬ слабее, чем здесь было написано.** Первая
|
||
редакция утверждала «весь каталог пере-прогнан, как записано». На деле рефакторинг сломал ДВЕ записи
|
||
каталога — они ссылались на переменную, которую он убрал из области видимости, — а инструмент считал
|
||
провал СБОРКИ красным, то есть две записи не проверяли ничего, а отчёт называл тесты, которые не могли
|
||
отработать. Нашло воркфлоу-ревью; обе записи пере-выражены, у инструмента появился третий вердикт — §34.
|
||
|
||
## 34. Воркфлоу-ревью дофикса: шесть осей, все находки воспроизведены и закрыты
|
||
|
||
Третье и последнее ревью дофикса — **воркфлоу**, а не одиночный агент (форма — §45). Полосы выбраны по
|
||
тому, чего не касались два предыдущих ревью: гонка двух `bank-apply` · последствия новых полей для
|
||
платформы (read-only) · эквивалентность только что сделанного рефакторинга · сам `cmd/tmmutate` (он
|
||
теперь в репозитории, значит продакшн-код) · честность хендофф-документов · общность и безопасность
|
||
данных. Каждая находка перепроверена своим прогоном прежде, чем чинить; правки доводов в комментариях и
|
||
правки в самом этом документе живут по месту, здесь — только то, что осталось фактом.
|
||
|
||
### ⛔ Главное: две посадки каталога НЕ КОМПИЛИРОВАЛИСЬ — и это внёс мой же рефакторинг
|
||
|
||
`P-alias-judged-on-the-set` и `AB-alias-escape` патчат тело `refuseInertDeclines`, ссылаясь на `in.Delta`,
|
||
которое рефакторинг §33 убрал из области видимости, — а инструмент считал их КРАСНЫМИ, потому что `red`
|
||
был буквально `terr != nil`, и провал СБОРКИ выглядел так же, как поймавший пин.
|
||
|
||
**Починено на двух уровнях.** Обе записи пере-выражены под новую сигнатуру и краснеют на своих пинах
|
||
поимённо; у инструмента появился ТРЕТИЙ вердикт (`NOTHING` — ни один тест не назвал себя, гейт падает)
|
||
плюс обязательная зелёная базовая линия пакета ДО первой посадки. ⚠ Оба механизма и провенанс инцидента
|
||
живут в самом инструменте — `cmd/tmmutate/main.go`, комментарии «THE PACKAGE MUST BE GREEN BEFORE
|
||
ANYTHING IS PLANTED» и «NOT a red …»; второй копии этот отчёт не держит.
|
||
|
||
### Продуктовые дефекты — все воспроизведены мной
|
||
|
||
| что | как это выглядело | починка |
|
||
|---|---|---|
|
||
| **остановленный вызов ПЕЧАТАЛ, что применил** | на отмене контекста отчёт уходил в stdout с `mode:"apply"`, `changed:true` и `state:"applied"` — при нуле записанных байт. Отличал их только код выхода | `mode:"stopped"`, `changed:false`, и `accepted[]` очищается так же, как на пути отказа (это поле потребитель перебирает). Пин `TestAStoppedCallDoesNotPRINTThatItApplied`, мутация AG бьёт по всем трём полям |
|
||
| **нечитаемая карта подписи ≠ «всё решено»** | фикс §29 п.6 закрыл ошибку РАЗБОРА, но не ошибку ЧТЕНИЯ: `chmod 000` возвращал нулевое состояние — побайтно то же, что «нечего подписывать». Тест назывался ровно про это свойство и его не покрывал | `os.IsNotExist` отделён от прочих ошибок чтения; пин расширен случаем `chmod 000`, мутация AH |
|
||
| **отказ предупреждал о перезаписи, которой не будет** | откат «всё-или-ничего» не сбрасывает `*Touched`, поэтому `canonical_rewrite` горел на ОТВЕРГНУТОМ вызове. Диалог «сейчас будут уничтожены комментарии» для вызова, который ничего не пишет, — так настоящее предупреждение перестают читать | поля обнуляются при отказе; пин `TestARefusedCallDoesNotWarnAboutARewriteThatWillNotHappen`, мутация AI |
|
||
| **отказ по алиасу сида говорил неправду** | «declining it would change nothing» — но если дельта несёт СВОЮ строку с этой поверхностью, отказ её сбрасывает и чинит ровно ту коллизию, которую тот же отчёт печатает в `preexisting_problems`. А предписанное «remove it from glossary_seed» строку дельты не убрало бы. Воспроизведено | отказ сужен до случая, где его собственная фраза верна; пин `TestDecliningASeedAliasIsAcceptedWhenTheDeltaHoldsThatRow`, мутация AJ |
|
||
|
||
### Что ревью ПОДТВЕРДИЛО (и это тоже результат)
|
||
|
||
**Рефакторинг §33 — эквивалентен**: порядок фаз, мутация `rs[i].ok` через слайс и место отката проверены
|
||
отдельной полосой, расхождений нет. **Платформа не ломается**: аддитивные поля отчёта, конверт
|
||
`artifacts{}` на манифесте без бампа версии, код 14 и размерные капы — прослежены до её кода, дефектов
|
||
нет. Исправлены при этом два комментария, утверждавших про чужую зону неверное, и вот верное: код 5
|
||
платформа пишет как `stopped` (и только при зафиксированном намерении остановиться, иначе `failed`), а
|
||
отчёт двери печатается НЕ в каждом исходе — четыре отказа (10, 12, 13 и 14 до чтения книги) не печатают
|
||
ничего, как и два не-отказных случая (5 и 1).
|
||
|
||
## 35. Проверка ЗАКРЫТИЯ — отдельным воркфлоу, и она нашла, что закрыто не всё
|
||
|
||
На вопрос «все замечания исправлены?» сессия не отвечала по памяти: независимые агенты прошли по всем
|
||
пережившим находкам §34 и по живому дереву решали КАЖДУЮ — закрыта / не закрыта / частично / отклонена
|
||
осознанно. Закрыто оказалось не всё; все остатки доделаны, и их результат живёт не здесь, а в
|
||
исправленных по месту секциях (§15, §16, §18, §31, §33) — второй копии этот отчёт намеренно не
|
||
держит.
|
||
|
||
**Два остатка были признаны вне полномочий пака, и оба закрылись позже:** межкнижная гонка за один
|
||
`mined_delta` — снятием объявляемых ключей (D39.158 п.5; остаток «второй конфиг ТОЙ ЖЕ книги» заведён
|
||
строкой бэклога 222), запись карты подписи обычным `os.WriteFile` — атомарной записью с конвертом
|
||
(§36, §41).
|
||
|
||
⚠ **Вывод, который стоит унести дальше самого пака.** Это было ТРЕТЬЕ ревью подряд, и каждое находило
|
||
настоящие дефекты, которых не видели предыдущие: одиночный старший ревьюер — три (§29), интервальное —
|
||
пять (§30), воркфлоу — двадцать шесть, проверка закрытия — ещё десять остатков (из 28 её пунктов: 16
|
||
закрыто, 2 отклонено осознанно, 7 частично, 3 не закрыто). **Ни одно «все замечания исправлены» в этой
|
||
сессии не оказалось верным при проверке.** Вывод не «ревью бесполезны», а обратный: закрытие надо
|
||
ПРОВЕРЯТЬ отдельно от починки, и не тем, кто чинил.
|
||
|
||
---
|
||
|
||
# ДОФИКС-2 (13 пунктов §4; промт архивирован — `docs/archive/prompts/BACKEND_SEAM_FIX2_SESSION_PROMPT_2026-08-27.md`) — секции 36+
|
||
|
||
## 36. Состав фикс2-пака
|
||
|
||
**Новое:** `internal/store/stopmemory.go` + миграция v16 (`bank_stop_presented`) · `internal/seed/signaturemap.go`
|
||
(конверт карты: `map_version` + `map_id` от содержимого) · `internal/pipeline/bankreport_test.go` ·
|
||
`internal/pipeline/stopflag_test.go` · `internal/store/stopmemory_test.go` · `internal/seed/normalize_test.go`.
|
||
**Тронуто:** `internal/pipeline/mining.go` (предикат новизны · разделение «стоп»/«авто-провод» · запись памяти ·
|
||
атомарная запись карты с конвертом · снос формул старой модели) · `internal/pipeline/bankdecisions.go`
|
||
(вердикт/исход, `finishReport` — единственная точка фиксации; staged-запись обоих файлов + fsync каталога;
|
||
кап-отчёты; `written_delta`/`written_rejects`; пере-док `SignatureState`) · `internal/pipeline/refusal.go`
|
||
(два яруса полосы + класс `write_incomplete`) · `internal/pipeline/artifact.go` (staged-вариант + `syncDir`) ·
|
||
`internal/config/book.go` (`filepath.Clean` абсолютных + retired-отказ ключей `mined_delta`/`mined_rejects`) ·
|
||
`internal/membank/decisions.go` (`RenderSignatureMap` с доказательством; версия отчёта → **v2**) ·
|
||
`internal/miner/miner_emit.go` (`DeltaFile`) · `cmd/tmctl/main.go` (exit **15**), `bankapply.go`, `render.go` ·
|
||
`internal/pipeline/waverun.go` (док стопа) · тесты: `bankchain_test.go` (переписан под модель флажка, две цепи),
|
||
`miningstop_join_test.go` (два теста переписаны с старой семантики), `internal/config/decisionpaths_test.go`
|
||
(переписан под retired-модель), `internal/membank/decisions_test.go` (+3 пина) · `cmd/tmmutate/mutations.json`
|
||
(36 → **56** записей: 20 новых FX* + 6 ре-якорено после рефакторинга).
|
||
|
||
## 37. Решения §4.1–§4.3, каждое с доводом — и названные остатки
|
||
|
||
- **Память** — плоское множество нормализованных поверхностей всех предъявленных кластеров (Src + Aliases),
|
||
таблица `bank_stop_presented (book_id, surface)`. Кластерность живёт в ПРЕДИКАТЕ (пересечение), не в схеме:
|
||
для вопроса «есть ли кластер вне памяти» объединение множеств поверхностей эквивалентно хранению кластеров,
|
||
а строк меньше и мигрировать нечего. Рост — только вширь (`INSERT OR IGNORE`), обрезки нет по букве промта.
|
||
- **Двусторонний остаток кластерной памяти** записан пином, а не только словами
|
||
(`TestAClusterSharingAnAliasWithTheMemoryReadsAsPresented`): мягкая сторона — лишний стоп на новом написании;
|
||
вредная — новая сущность, делящая поверхность-алиас с виденным кластером, предъявлена НЕ будет (поедет
|
||
редактору авто-строкой). Обе — принятые остатки решения, смена трейда = новое решение.
|
||
- **Порядок записи**: карта (АТОМАРНО, §4.10 сделан первым) → память, в момент стопа. **Отказ записи памяти
|
||
НЕ валит состоявшийся стоп**: карта на диске, пауза настоящая; цена — один доброкачественный повторный стоп
|
||
(ERROR-строка в логе называет это). Валить прогон значило бы превратить законную паузу в аварию из-за
|
||
бухгалтерской строки. Между файлом и SQLite общей транзакции нет — принятый остаток, направление потерь
|
||
безопасное (повторный стоп, не молчаливая потеря).
|
||
- **Остановившийся прогон авто-банк НЕ пишет** — названное решение (§4.3 ⚠): стоп — граница владельца, и
|
||
запись банка на ней меняла бы состояние, которое владелец как раз рассматривает; возобновление без решений
|
||
доводит авто-провод на следующем прогоне (сходится, проверено цепью Б, прогон 3). Чекпоинты терминолога
|
||
делают оба варианта $0-эквивалентными — выбран тот, что сохраняет артефактный набор стопа прежним.
|
||
- **Черновая волна (draft-only)**: карта пишется, стопа нет, памяти нет (по букве §4.2); авто-провод теперь
|
||
исполняется и при поднятом флаге (раньше — нет; это изменение поведения, названо здесь).
|
||
- **Флаг-поднят-нового-нет** — новое состояние: WARN-строка «--verify-bank is raised and the delta is
|
||
non-empty, but every cluster... already presented» + авто-провод + полноценное продолжение. Молчание больше
|
||
не спутать с «майнинг не сконфигурирован».
|
||
- **Миграция**: v15→v16 — новая таблица `CREATE TABLE IF NOT EXISTS` (дешёвая форма из промта; ALTER не
|
||
трогали, долг 49а не задет). ⚠ Голова схемы двигается: `OpenReadOnly` отказывает классом 13 всем
|
||
непере-мигрированным книгам до `tmctl migrate` — порядок деплоя «migrate первым» это покрывает (D39.134 п.3),
|
||
живьём проверено на копии стенда (v11 → v16, пре-миграционный бэкап, integrity green). После апгрейда память
|
||
пуста → ОДИН доброкачественный стоп на первом verify-прогоне каждой книги — миграционная нота в журнал.
|
||
|
||
## 38. Заявление = команда: замеры фикс2
|
||
|
||
- **§4.4 пере-замер своей командой** (числа промта 2110/442/117/325 первоисточника в дереве не имеют — не
|
||
цитирую). Первоисточник ТЕПЕРЬ В ДЕРЕВЕ (упрёк проверки закрытия принят): постоянный якорь-пин
|
||
`internal/miner/emissioncap_test.go` (`TestTheEmissionCapPrecedesTheFiltersAndTheTailIsUnreachable`) —
|
||
тот же корпус (фамилии langpack × имена, 600 имён по 6 вхождений в 6 разных рамках), утверждает ТЕКУЩЕЕ
|
||
поведение и краснеет в день сдвига потолка или порядка фильтров, приводя двигающего к пингу
|
||
(прецедент — токенование якоря пинга живым фактом, коммит 0f4c556). Замер той сессии:
|
||
`ranked=2354 · годных во всём алфавите=209 · годных в окне потолка=11 · невидимых постоянно=198`;
|
||
отклонение ВСЕХ 11 годных окна (9 эмитированных + алиасы) → второй прогон: `emitted=0, fresh_surfaces=0` —
|
||
ни один из 198 не поднялся. Структурная причина — `miner_emit.go` (`ranked = ranked[:emitRankCap]` ДО
|
||
`emissionEligible`, сид- и реджект-скипы тратят слоты ВНУТРИ окна). Картина промта подтверждена на моих
|
||
числах; заказ — не лечить, пинг в журнале.
|
||
- **Каталог мутаций**: `go run ./cmd/tmmutate -root <копия>` → **56 записей, 0 неожиданных исходов**
|
||
(55 RED + записанный выживший G-byte-gate). Все 20 новых FX-посадок краснеют СВОИМИ пинами (в выводе
|
||
инструмента поимённо); ре-якорены после рефакторинга: E3 (пере-выражена: предмет «declared» умер с §4.9(б),
|
||
новая — «unreadable ≠ undecided»), H, S, Z, AH, AG, AI.
|
||
- **Пины §4.11, проверенные посадкой ДО записи в каталог** (правило «сначала посадка, потом пин»): посадка
|
||
`subjectOf → class` вскрыла, что мой первый «межтермовый» пин держится гардом shared-key (его класс-строка
|
||
несёт идентичность) и для этой мутации ВАКУУМЕН — довод пина переписан честно, мутацию ловит оконный пин
|
||
(`TestANewFaultIsNotMaskedByTheSameTermsFaultInAnotherWindow`), что и записано в каталоге.
|
||
- **Батарея**: `go build ./... && go vet ./... && go test ./... -count=1` — зелёная целиком; `make lint` —
|
||
0 issues; `make battery` — EXIT=0. Замер времени не публикую: прогоны шли под параллельной нагрузкой
|
||
(агент-ревью и мутационный харнесс), а время под нагрузкой этот проект уже дважды обжигало (§31).
|
||
|
||
## 39. Живые пробы фикс2 — на КОПИИ книги стенда (minirun-verify2), в books/ не записано ничего
|
||
|
||
Копия вне дерева; мёртвые пути переправлены в трёх файлах (book.yaml, pipeline.yaml, pairs/zh-ru.yaml);
|
||
две строки снятых ключей удалены в копии (сам стенд — зона оркестратора, он в курсе). jieba-контраста на
|
||
машине нет — стоп/цепь кроются харнессом (`miningstop_join_test.go`, `bankchain_test.go`), как и в прошлый раз.
|
||
|
||
| проба | результат |
|
||
|---|---|
|
||
| немигрированный проект (v11 против v16) | `exit=13`, текст называет `tmctl migrate` |
|
||
| `tmctl migrate` | `v11 -> v16`, пре-миграционный бэкап, integrity green |
|
||
| approve+decline одним документом | `exit=0`, отчёт **v2**: `mode=apply`, `written_delta=true`, `written_rejects=true`, оба файла по конвенции, note с ведущим `\n` записана и читается |
|
||
| повтор того же документа | `exit=0`, `changed=false`, оба `already_applied`, `written_*=false` |
|
||
| проекция | `exit=0`, `mode=projection`, `written_*=false` |
|
||
| решение о ПОДПИСАННОМ сид-терме | `exit=14`, `mode=refused`, причина называет сид и цену (base snapshot) |
|
||
| снятый ключ `mined_delta:` в book.yaml | `exit=10`, отказ называет retired + конвенцию + лечение |
|
||
| документ на 5001 решение | `exit=14` И отчёт на stdout: `mode=refused`, `rejected[0].index=-1`, `signature.unreadable=true` |
|
||
| запись в несписываемый каталог (книга без базы, лок пред-создан) | **`exit=15`**, `mode=write_incomplete`, `written_*={false,false}`, `accepted=[]`; после chmod повтор ТОГО ЖЕ документа → `exit=0` |
|
||
| абсолютный `project_db` со вставкой `/./` на конвенционный путь дельты | `exit=10`, отказ называет оба ключа |
|
||
| sha256 стендовой базы | не сдвинулся; `git -C books status` чист |
|
||
|
||
## 40. Дифф `^func Test` — исполнением
|
||
|
||
```
|
||
$ git diff -U0 -- . | grep -cE "^-func (Test|Fuzz)" → 0 # удалено: НОЛЬ
|
||
$ git ls-files --others --exclude-standard -- . | grep '_test.go$' \
|
||
| xargs grep -h '^func Test' | wc -l → 117 # было 97: +20 тест-функций (фаззеров по-прежнему 3)
|
||
```
|
||
**Сводно по трём пакам:** пак оставил 59 тест-функций + 2 фаззера, дофикс добавил 38 + 1, фикс2 — ещё 20;
|
||
**удалено за все три пака НОЛЬ**, и это проверено дважды — грепом по диффу и поимённо по пинам, которые
|
||
отчёты паков называют по имени.
|
||
Переименовано (не удалено): `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. Чек заказа фикс2 — снят
|
||
|
||
Попунктный чек тринадцати пунктов §4 фикс2 вырезан как прожитая приёмка. Адреса: заказ —
|
||
`docs/archive/prompts/BACKEND_SEAM_FIX2_SESSION_PROMPT_2026-08-27.md` (комплектность сверялась
|
||
механически: `grep -cE '^### 4\.' <промт>` → 13); решения с доводами и названные остатки — §37; замеры —
|
||
§38; живые пробы — §39; дифф тестов и переименования — §40; что НЕ удалось — §41; сужения воркфлоу-ревью
|
||
— §45.
|
||
|
||
⚠ D39.158 называет «вход §43» — читатель, пришедший по этой ссылке, попал верно: адреса выше.
|
||
|
||
## 44. Проверка ЗАКРЫТИЯ — отдельным агентом, не чинившим (§5.1)
|
||
|
||
Прогнана по презумпции «каждое заявленное закрытие ложно, пока не увидено исполнением»: все 13 пунктов
|
||
заказа фикс2 (§36–§41, §45) и все 4 диспозиции §42 подтверждены в дереве (13 точечных тестовых прогонов + 3 полных пакета — зелёные;
|
||
носители по file:line; реверс-пин признан невакуумным с названным механизмом падения). Расхождения нашлись
|
||
и здесь (⚠-вывод §35 держится и на этой сессии), все закрыты по месту — правки видны в §38, §40 и §42.
|
||
Одно стоит того, чтобы его запомнили: пере-замер §4.4 жил только в скратчпаде — то есть был «числом без
|
||
первоисточника» ровно того класса, за который заказ корил чужие числа; первоисточник положен в дерево постоянным якорем-пином
|
||
`internal/miner/emissioncap_test.go`.
|
||
|
||
## 45. Воркфлоу-ревью фикс2: две кодовые находки и три остатка, которые оно сузило
|
||
|
||
Форма: шесть параллельных полос-искателей под антипаттерны проекта (неверные доводы при верном коде ·
|
||
вакуумные пины · семантика флажка глубоким чтением · дверь/отчёт глубоким чтением · общность движка ·
|
||
шов/контракт), каждая находка — через трёх адверсариальных скептиков с разными линзами (корректность
|
||
трассой · достижимость · «не принятый ли уже остаток»), выживание — большинство. Все выжившие находки
|
||
закрыты; правки доводов в комментариях и документах живут по месту, здесь — то, что осталось фактом.
|
||
|
||
**Кодовые (2), обе закрыты правкой + пином + посадкой:**
|
||
|
||
| находка | закрытие |
|
||
|---|---|
|
||
| **`tmctl translate --dry-run` молча игнорировался и запускал ПЛАТНЫЙ прогон** — третье написание денежной дыры, которую это же дерево уже закрывало для `--decisions=`/`--keys-file=`; пред-существует фикс2, нигде не был записан | гард присутствия: `--dry-run` принимают только `bank-apply` и `redrive`, остальным — громкий отказ с ценой в тексте; пин `TestDryRunIsRefusedWhereNothingProjects`; посадка FX21 |
|
||
| **Повтор после exit 15 из-за отказа `syncDir` сходился в exit 0, так и не пере-доказав долговечность** — retry по контракту класса шёл byte-no-op-веткой мимо единственного места с синком | no-op-ветка теперь пере-доказывает долговечность: не открывающийся на синк каталог держит класс 15 (`written_*={false,false}`, текст называет непроверенное) до выздоровления среды; пин `TestARetryAfterAFailedDirectorySyncReProvesDurability`; посадка FX22 |
|
||
|
||
**Что сужено и осталось живым:**
|
||
|
||
- **Якорь-пин §4.4 НЕ краснел при сдвиге потолка** — премиссы не фиксировали значение 200 (150/300/400
|
||
проходили зелёными), вопреки трём носителям клейма. Значение потолка теперь запинено премиссой с
|
||
текстом, ведущим двигающего к пингу; проверено: смена 200 → красный.
|
||
- **Довод арбитра сужен**: «межкнижная гонка недостижима по построению» была шире правды — остался
|
||
второй конфиг ТОЙ ЖЕ книги (один каталог/`book_id`, другой объявленный `project_db`): пишет те же
|
||
файлы под другим локом, мимо flock живого прогона (ревью воспроизвело живьём). Межкнижная версия
|
||
снята; остаток заведён строкой бэклога **222** (D39.158 п.9).
|
||
- **Клауза «ничего не произошло» у классов 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|TableJSONPath|bankStopTableJSON|tm-bank-stop` по `*.go` → 0 |
|
||
| **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 пере-сняты; найденные расхождения исправлены
|
||
по месту выше.
|