1440 lines
192 KiB
Markdown
1440 lines
192 KiB
Markdown
# Доказательная база пака «входная дверь шва» (движковая половина D39.156)
|
||
|
||
Бэкенд-сессия 24.08.2026. Промт — `docs/BACKEND_SEAM_PACK_SESSION_PROMPT.md`, план — `SEAM_PACK_PLAN.md`.
|
||
**Каждое число — с командой, которой получено;** приёмка пере-раняет выборочно. Команды из `backend/`.
|
||
|
||
## 0. Состав пака
|
||
|
||
**Новое:** `internal/membank/decisions.go` (чистая логика решений) · `internal/pipeline/bankdecisions.go`
|
||
(обвязка: лок, чтения, записи, отчёт) · `cmd/tmctl/bankapply.go` (CLI) · `internal/seed/decode.go`
|
||
(строгие декодеры схемы) · `internal/standdata/standdata.go` (тест-резолв корпуса) + 6 тест-файлов и
|
||
2 фаззера.
|
||
**Тронуто:** `internal/config/book.go` (конвенционные дефолты) · `internal/pipeline/mining.go`
|
||
(дефолт-осведомлённые чтения) · `internal/pipeline/status.go` (`status_version` + `artifacts{}`) ·
|
||
`internal/pipeline/export.go` (`export_version`) · `internal/pipeline/bankexport.go` (`bankTermID` →
|
||
`membank.TermID`, одно определение на двоих) · `internal/pipeline/refusal.go` + `cmd/tmctl/main.go`
|
||
(класс 14) · `internal/store/store.go` (+34 строки: `LockProject`) · `internal/membank/memseed.go`
|
||
(строгий разбор) · `internal/seed/seed.go` (устав пакета) · `cmd/tmctl/invocation.go`, `dotenv.go`
|
||
(`--keys-file`, `--decisions`).
|
||
|
||
## 1. Батареи
|
||
|
||
⚠ **Батарея НЕ детерминированно зелена на этой машине, и это НЕ пак** — читай до пере-рана, иначе
|
||
красное будет отнесено на меня. Два пред-существующих `-race`-флейка в `internal/store` (§10 п.7)
|
||
срабатывают примерно раз на 2–4 полных прогона: замерено `go test ./... -race -count=1` ×4 → прогон 1
|
||
чист, прогон 2 — `TestKillMinus9LosesAtMostOneCall`. **Красное ИМЕННО в этих двух тестах ожидаемо;
|
||
любое другое красное — регрессия.**
|
||
|
||
| что | команда | результат |
|
||
|---|---|---|
|
||
| хермет. батарея | `make battery` | `EXIT=0` на чистом прогоне; skip: `TestMinerFullBookParity`, `TestHelperEventsRun`, `TestHelperKillLoop`. См. предупреждение выше |
|
||
| стенд-батарея, **без единого `TM_*_DIR`** | `make battery-stand` | `EXIT=2`, красен ровно `TestMinerFullBookParity`, причина — отсутствующий `eval/exp16/data/jieba_dict_general_zh.txt` (строка 123) |
|
||
| линтер | `make lint` | `0 issues.` |
|
||
| vet под оба тег-набора · формат | `go vet ./... && go vet -tags live ./...` · `test -z "$(gofmt -l .)"` | чисто |
|
||
|
||
Стенд-картина совпала с опорной точкой оркестратора ⇒ **критерий §4.6 выполнен буквально.**
|
||
|
||
## 2. Адверсариальные посадки: 16/16 краснеют
|
||
|
||
Харнесс сажает мутацию → гоняет тесты НАЗВАННОГО пакета → откатывает; он же снимает sha256 целей до
|
||
прогона и проверяет после (`TREE IS NOT PRISTINE`). ⚠ Пакет пина ≠ пакет правки.
|
||
|
||
| # | посадка | пакет пина | что покраснело |
|
||
|---|---|---|---|
|
||
| M1 | дефолт срабатывает на НЕпустом ключе | `config` | `TestDeclaredDecisionPathIsNotDefaulted` |
|
||
| 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/` не записано ничего.**
|
||
|
||
| проба | результат |
|
||
|---|---|
|
||
| глагол на НЕмигрированном проекте | `exit=13`, `schema_mismatch found=11 expected=15 … run tmctl migrate` |
|
||
| проекция (`--dry-run`) | `exit=0`, `mode=projection`, `canonical_rewrite=true`, md5 обоих файлов НЕ изменились |
|
||
| применение | `exit=0`, `changed=true`; ручной `陈博` сохранил `gender`/`decl`/`translit_policy` |
|
||
| повтор того же решения | `changed=false`, `already_applied`, **inode и md5 не изменились** |
|
||
| конфликт с подписанной строкой сида (`春秋蝉`) | `exit=14`, отчёт называет строку сида ПОИМЁННО, файлы не тронуты |
|
||
| занятый лок (flock держит сторонний процесс) | `exit=12`, `wall=0.02s` |
|
||
| `decline` над рукописной дельтой | md5 дельты не изменился, комментарии владельца целы |
|
||
| reject-файл ИЗ ОДНИХ КОММЕНТАРИЕВ | `exit=0` (до фикса R1 было `exit=10`, `seed: EOF`) |
|
||
| загружаемость результата | `seed-lint OK: 0 fail-louds, 0 shared-key collisions` |
|
||
| конвенционный дефолт (ключи из `book.yaml` удалены) | `artifacts.mined_delta = <dir>/<book_id>.mined-delta.yaml`, файлов на диске нет; `approve`+`decline` одним вызовом создают оба |
|
||
| все seed/delta/signature/auto-bank стенда под строгим загрузчиком | 21 из 21 чисто (независимо: 29/29 на более широкой выборке, включая книги dovodka/role-topology/tenant-panel) |
|
||
|
||
## 4. Замеры
|
||
|
||
| что | число | вердикт |
|
||
|---|---|---|
|
||
| **цена глагола** (`bank-apply --dry-run`, `/usr/bin/time`) | 5 000 термов → 1.60с/133МБ · 10 000 → 3.62с/217МБ · 20 000 → 9.08с/350МБ; ×2.3–2.5 на удвоение | **датум для таймаута платформенного воркера.** Бюджет платформы на ОДИН вызов движка — 60с (`platform/internal/runs/reconcile.go:280`), до него ~80k термов. Реальная книга (кап майнера 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` доказывает, что прогон читает именно тот файл), **но цепь
|
||
целиком не гонялась ни разу**, а мина №1 строки 199 — livelock именно уровня ЦЕПИ, которого частичные
|
||
тесты по построению не видят.
|
||
|
||
**Почему я его не снял — проверено, не заявлено:** банк-стоп на этой машине не воспроизводится вовсе
|
||
(`pipeline.yaml` требует `contrast_path: …/eval/exp16/data/jieba_dict_general_zh.txt`, `ls` → `No such
|
||
file or directory` — та же причина, по которой красен парити), и даже с контрастом до стопа надо дойти
|
||
ОПЛАЧЕННОЙ черновой волной. Синтетический контраст = подделать замер.
|
||
|
||
**Команда:** на КОПИИ книги, припаркованной на exit 3, скормить `bank-apply` документ на всю карту
|
||
подписи (id из банк-экспорта), затем возобновить `translate --verify-bank`.
|
||
**ДЕФЕКТ** — стоп снова с ТЕМИ ЖЕ поверхностями. **НЕ дефект** — свежая пачка ≤200 термов реверс-секции
|
||
на большой книге (мина №3 строки 199: кап режется ПОСЛЕ фильтров).
|
||
|
||
## 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:`.
|
||
- **Размер документа решений не ограничен** (`os.ReadFile`), тогда как платформа свои чтения от движка
|
||
капает (`maxStatus` 16МБ / `maxBank` 256МБ). Дотянуться могут: на SaaS — воркер самой платформы
|
||
(ограничен её API-капами), локально — оператор, роняющий свой процесс. Документ на 10 000 решений
|
||
≈2МБ ⇒ кап в диапазоне 1–16МБ стоит одной константы плюс строки отказа. **Однострочник при лендинге.**
|
||
|
||
## 10. Пинги — предметы ВНЕ карты пака, правок не делал
|
||
|
||
1. **`rubyToCandidates` мёртв в проде.** `grep -rn "rubyToCandidates" --include=*.go .` → 4 хита:
|
||
определение `internal/membank/memseed.go:301`, два комментария и ЕДИНСТВЕННЫЙ вызов —
|
||
`memseed_test.go:204`. `internal/pipeline/seeding.go:36-38` строит `manualSrcs` (аргумент этой
|
||
функции) и никуда не передаёт; Go молчит, потому что запись в map считается использованием.
|
||
Следствие: строк `Source:"ruby"` в банке не возникает, а платформа держит для них ветку словаря
|
||
(`platform/internal/ingest/bank.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). Чередующийся A/B на `git worktree` HEAD,
|
||
3 раунда × 10: `kill9` — 2/30 на HEAD против 0/30 у меня; `seam` (`-race`, 30) — 2/30 против 1/30;
|
||
БЕЗ `-race` — 0/30. Покрытие независимо подтверждает: `LockProject`/`Release` этими тестами не
|
||
исполняются вовсе. ⚠ **0/30 против 2/30 НЕ значит «пак улучшает»** — на таком числе событий это шум
|
||
(Фишер p≈0.5); решающее и достаточное — что флейк живёт на дереве, которого пак не касался.
|
||
Воспроизведение: два дерева, `-race -count=10`, чередуя, под параллельным `go test ./...`.
|
||
|
||
## 11. Три правила протокола, вынесенные инцидентами сессии
|
||
|
||
1. **Харнесс мутаций обязан фингерпринтить цели.** Прогон `mutate.py` был убит таймаутом инструмента,
|
||
`finally` не отработал, и посадка M4 (снятый гейт байтового no-op) просидела в
|
||
`internal/pipeline/bankdecisions.go` ~40 минут, выглядя продакшн-кодом; сторонний ревьюер снял с
|
||
этого дерева замеры и доложил «дефект гейта идемпотентности». Поймал посадку пин
|
||
`TestBankApplyRepeatIsAByteNoOp` на первом прогоне после. Харнесс починен (sha256 целей до и после).
|
||
Пин усилен по следу: повтор проверяется над файлом с РУЧНЫМ комментарием в шапке — это то, что
|
||
пере-рендер уничтожает и чего не заметила бы проверка содержимого — плюс что `changed:false` не врёт.
|
||
2. **Снимай отпечаток дерева ПЕРЕД сборкой бинаря замера.** Симметричная половина того же класса:
|
||
ревьюер собрал `tmctl` из незакоммиченного дерева, не сняв отпечаток, и инструмент унаследовал
|
||
посадку. За сессию на это наступили обе стороны.
|
||
3. **Одиночный прогон — не замер.** Атрибуцию флейков я сперва вывел из одного прогона («зелено в
|
||
изоляции ⇒ нагрузка»), затем другой одиночный прогон дал ровно обратное. Решает только чередующийся
|
||
A/B (§10 п.7).
|
||
|
||
## 12. Попунктный чек промта
|
||
|
||
| пункт | статус |
|
||
|---|---|
|
||
| §2 зона записи только `backend/` + секция «Бэкенд» в `docs/PROGRESS.md`; не коммитить; чужое не трогать | ✅ `git status` подтверждает |
|
||
| §4.1 дефолты пустых ключей; гард ЯВНОГО пути не ослаблен; отсутствующий дефолт = «решений нет» | ✅ M1 + `TestDeclaredAndUnreadableDecisionPathStillRefuses` |
|
||
| §4.2 глагол $0; байтовый no-op; отзыв = ЗАМЕНА; всё-или-ничего; два решения по терму = отказ; конфликт с сидом С ИМЕНЕМ; проекция БЕЗ мутации; занятый лок РОВНО 12; flock неблокирующе; версия в ответе; `depth` явным полем | ✅ M2–M8 + `TestDeclineWritesTheRejectAndWithdrawsTheApproval`, `TestOneCallDecidesATermOnce`, `TestDecisionOnASignedSeedTermIsRefusedByName`, `TestBankApplyDryRunWritesNothing`, `TestBankApplyDeclineThenApproveConverges`; §3, §4 |
|
||
| §4.2 НЕ проектировать «глубину до черновика» | ✅ `depth: edit_wave`; `TestTheRunReadsTheConventionalDelta` доказывает `Source:"mined"` |
|
||
| §4.3 `--keys-file` только у `translate`; деплой-файл ПЕРВЫМ; нечитаемый = отказ конфигом; ничего «на вырост» | ✅ M6 + `TestKeysFileWinsOverTheConventionalEnv` + `TestAnUnreadableKeysFileRefusesWithTheConfigClass` |
|
||
| §4.4 строгость сид-загрузчика + тест на unknown-key | ✅ 2 кейса в `TestSeedLintCatchesDefects` (`gendr`, `termz`) |
|
||
| §4.5 путь артефакта аддитивно + версия документа; НЕ тащить пере-именование банк-экспорта (ЛОМАЮЩЕЕ, окно — строка 161) | ✅ `artifacts{}` + `status_version`; пин `rep.Artifacts.BankExport == r.bankExportPath()` |
|
||
| §4.6 дефолт корпуса маркерным резолвом, env-переопределения старше | ✅ §1 |
|
||
| §4.7 платных вызовов НОЛЬ; `--keys-file` фейковыми ключами; вердикт PLAUSIBLE | ✅ sha256 базы (§4) + `TestTheKeysFileActuallyReachesTheProvider` (сверяет `Authorization` на fake-провайдере, и БЕЗ флага тот же прогон отказывает конфигом — проверка не вакуумна); §7 |
|
||
| §5 батарея после каждой правки; красный тест не подгонять; посадки ВНЕ списка промта; посадка называет ПАКЕТ; живая проба в КОПИИ; артефакт-находок | ✅ 16 посадок, 8 сверх списка; флейки охарактеризованы и НЕ тронуты; §2, §3 |
|
||
| §5.1 последний абзац — план или обещание? сделать СЕЙЧАС | ✅ всё найденное ревью закрыто в этом дереве; единственное отложенное — §8, вне моих возможностей по деньгам |
|
||
| §5.1 дифф `^func Test` исполнением; интервальная самоверификация субагентом | ✅ §13; ревью гонялось в СЕРЕДИНЕ пака против явных критериев |
|
||
| §6 оси: шов (инвентарь ЧУЖОГО кода) · общность · деньги | ✅ читал `artifacts.go`/`bank.go`/`engine.go`/`spawn.go`/`resync.go`/`exit.go` сам; проверил, что ни один декодер платформы не строгий ⇒ аддитивные поля безопасны; Go не ветвится по паре/книге; деньги — sha256 базы |
|
||
| §8 число с командой · §9 эхо до работы · §10 «что НЕ удалось» · §11 развилки пингом | ✅ §1–§4 · выдано · §7 · §10 |
|
||
|
||
## 13. Дифф `^func Test` — исполнением
|
||
|
||
```
|
||
$ git diff -U0 -- backend | grep -E "^[+-]func Test"
|
||
+func TestTheRefusalTableIsTotal(t *testing.T) {
|
||
|
||
$ git ls-files --others --exclude-standard -- backend | grep '_test.go$' | xargs grep -h '^func Test' | wc -l
|
||
59
|
||
$ ... | xargs grep -h '^func Fuzz' | wc -l
|
||
2
|
||
```
|
||
|
||
**Добавлено:** 59 тест-функций в шести НОВЫХ файлах + `TestTheRefusalTableIsTotal` в существующем
|
||
`cmd/tmctl/exitcontract_test.go` + 2 фаззера. **Удалено:** ноль.
|
||
|
||
**Тронуто в существующих файлах — шесть, ни у одной сигнатура не изменилась:**
|
||
- `exitcontract_test.go` — в список полосы добавлены `exitSchemaMismatch` и `exitDecisionsRejected`
|
||
(иначе гейт молча перестал бы покрывать новый код) + тест тотальности;
|
||
- `invocation_test.go` — frozen-строка usage расширена именем `bank-apply`. Объявленное расширение
|
||
контракта тем же механизмом, которым строка росла пять раз до меня (её собственный комментарий их
|
||
перечисляет), и второй половине того же теста («usage обязана называть каждую dispatch-команду»)
|
||
иначе не удовлетворить;
|
||
- `checks/labelharness_test.go`, `membank/labelharness_test.go`, `miner/miner_parity_test.go` — §4.6:
|
||
три копии резолвера заменены на `standdata` (−120 строк, +62);
|
||
- `membank/seedlint_test.go` — два кейса unknown-key (§4.4).
|
||
|
||
---
|
||
|
||
# Дофикс той же двери (сессия 25.08.2026)
|
||
|
||
Промт — `docs/BACKEND_SEAM_FIX_SESSION_PROMPT.md` (16 пунктов §4), план — `SEAM_FIX_PLAN.md`. Секции 0–13
|
||
выше — улика приёмки, не тронуты. **Каждое число ниже — с командой, которой получено;** команды из
|
||
`backend/`. Дерево не коммичено, лендит оркестратор.
|
||
|
||
## 14. Батареи
|
||
|
||
| что | команда | результат |
|
||
|---|---|---|
|
||
| **база ДО правок** (дерево пака) | `make battery` | `EXIT=2`, красен ровно `TestKillMinus9LosesAtMostOneCall` — один из двух названных промтом пред-существующих `-race`-флейков. Флейк воспроизвёлся на дереве, которого я ещё не касался: атрибуция моя, не наследованная |
|
||
| батарея после правок | `make battery` | см. §31 |
|
||
| стенд-батарея | `make battery-stand` | см. §31 |
|
||
| линтер | `make lint` | `0 issues.` |
|
||
|
||
## 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 МБ) выше любого прогона с ней.** Первая редакция публиковала
|
||
здесь «≈13 %», сравнивая минимум с минимумом, — снято. Основная цена глагола — НЕ проверка (см. §18).
|
||
|
||
## 16. §4.11 + свои посадки: 36 мутаций, 35 краснеют, 1 выживает осознанно
|
||
|
||
Харнесс — **в репозитории**: `cmd/tmmutate` + каталог `cmd/tmmutate/mutations.json` (36 записей —
|
||
`python3 -c "import json;print(len(json.load(open('cmd/tmmutate/mutations.json'))))"`, у каждой
|
||
названо СВОЙСТВО, за которое она отвечает). Три сессии подряд писали такой инструмент в скретчпад и
|
||
теряли его вместе с сессией; каталог — это и есть память о том, что кто-то счёл достойным охраны.
|
||
Прогон — на КОПИИ дерева (`cp -a`, равенство копии проверено побайтно), сам инструмент снимает
|
||
отпечаток каждой цели и каталога целиком и краснеет на «TREE IS NOT PRISTINE».
|
||
|
||
**База ДО пинов: 20 из 24 выжили** (⚠ снято на РАННЕЙ редакции каталога из 24 записей; нынешние 36 её
|
||
не воспроизводят — это исторический замер, а не команда, которую можно повторить) — то есть все десять
|
||
слепых пятен §4.11 подтверждены исполнением,
|
||
плюс мой собственный новый код был не запинен. **Итог после всех итераций: 36 посадок, 35 краснеют на
|
||
СВОЁМ пине, одна (G) записана как аргументированный выживший (§17).** Каталог поэтому не отчёт, а ГЕЙТ:
|
||
исход, не совпавший с записанным, роняет прогон — в том числе неожиданно ПОЙМАННАЯ посадка, что значит
|
||
«запись устарела, обнови её».
|
||
|
||
⚠ **Две мои собственные посадки поймали два моих же ВАКУУМНЫХ пина** (последняя итерация): пин на
|
||
позиционные субъекты держал строки без идентичности ПЕРВЫМИ, поэтому их позиции никогда не двигались; а
|
||
пин на полноту довода отказа искал имя команды в тексте, где оно и так стоит — в обвинении «not by %q»,
|
||
а не в объяснении. Оба переписаны и оба теперь краснеют. Это второе за сессию подтверждение правила
|
||
«сначала посадка, потом пин»: пин, написанный первым, проверяет то, что и так верно.
|
||
|
||
```
|
||
$ cd <копия backend> && go run ./cmd/tmmutate -root . -logs <dir>
|
||
```
|
||
|
||
| # | посадка (свойство) | пакет пина | что покраснело |
|
||
|---|---|---|---|
|
||
| A | лок двери — тот же flock, что держит ПРОГОН | `pipeline` | `TestBankApplyRefusesAProjectAN_RUNIsHolding` |
|
||
| B | `canonical_rewrite` + `preexisting_problems` (2 правки в 1 файле) | `pipeline` | `TestTheReportCarriesTheTwoFieldsThePackMitigatesItsRisksWith` |
|
||
| C | класс отказа без номера уезжает в catch-all 19 | `cmd/tmctl` | `TestTheRefusalTableIsTotal` |
|
||
| D | нормализация в REPLACEMENT-половине отказа | `membank` | `TestADeclineWithdrawsAnApprovalWrittenInAnotherOrthography` |
|
||
| E1 | глубокая копия алиасов в `withRubyAliases` | `membank` | `TestWithRubyAliasesDoesNotWriteIntoTheCallersRows` |
|
||
| E2 | перенос `note` при промоушене | `membank` | `TestPromotionCarriesTheOwnersNote` +1 |
|
||
| E3 | рантайм-половина гарда ОБЪЯВЛЕННОГО пути | `pipeline` | `TestADeclaredDecisionFileThatVANISHESIsStillAnError` |
|
||
| F | абсолютность путей в `artifacts{}` | `pipeline` | `TestTheReportedPathsAreAbsoluteEvenWhenTheConfigIsNOT` |
|
||
| G | байтовый гейт `changedDoc` | `pipeline` | **ВЫЖИВАЕТ — см. §17** |
|
||
| H | решения едут С КНИГОЙ, а не с `project_db` | `config` | `TestTheDecisionFilesTravelWithTheBookAndNotWithTheDatabase` |
|
||
| I | нормализация ДОКУМЕНТА (значение уже в файле) | `membank` | `TestAHandAuthoredNoteTheEngineCannotWriteIsRepairedNotRefused` |
|
||
| J | равенство в `renderProved` (а не «просто разобралось») | `membank` | `TestTheRenderGateRefusesBytesThatReadBackAsAnotherDocument` |
|
||
| K | нормализация ЗАПРОСА (идемпотентность повтора) | `membank` | `TestARepeatOfADecisionWhoseTextIsPaddedIsStillAlreadyApplied` |
|
||
| L | `book_id` — один элемент пути | `config` | `TestBookIDIsRefusedWhenItIsNotAPathElement` |
|
||
| M | четыре движковых пути попарно различны | `config` | `TestTwoKeysOnOnePathAreRefused` |
|
||
| N | гард флага — на ПРИСУТСТВИЕ, не на значение | `cmd/tmctl` | `TestAFlagIsRefusedForBEINGTHERE_NotForCarryingAValue` |
|
||
| O | перевёрнутое окно глав | `membank` | `TestAReversedChapterWindowIsRefused` |
|
||
| P | инертный отказ судится по НАБОРУ | `membank` | `TestApprovingATermAndDecliningItsAliasInOneCallIsRefused` |
|
||
| Q | `note` не стирается умолчанием | `membank` | `TestANoteIsNotErasedByAnOmission` |
|
||
| R | кап документа решений | `pipeline` | `TestTheDecisionDocumentIsCapped` |
|
||
| S | стоп между решением и записью не пишет | `pipeline` | `TestAStopRequestWritesNothing/cancelled_while_the_decision_is_being_computed` |
|
||
| T | пред-существующая поломка — по СУБЪЕКТУ | `membank` | `TestRepairingOneOfTwoBrokenTermsIsAccepted` |
|
||
| U | честность отчёта о непогасшем стопе | `pipeline` | `TestTheStopClearsWhenTheOwnerDecidesTheWholeSignatureMap` |
|
||
| V | конверт путей на поверхности потребителя | `pipeline` | `TestTheManifestCarriesTheArtifactEnvelope` |
|
||
| W | кап на КОЛИЧЕСТВЕ решений (§29 п.1) | `pipeline` | `TestTheDecisionCOUNTIsCappedBecauseTheBYTESCannotBound` |
|
||
| Y | субъект поломки без рендеринга (§29 п.2) | `membank` | `TestAFaultTheDoorHasNoChannelForCannotDeadlockIt` +1 |
|
||
| Z | нечитаемая карта подписи ≠ «всё решено» (§29 п.6) | `pipeline` | `TestASignatureMapThatCannotBeReadDoesNotReportAsAllDecided` |
|
||
| AB | побег «это был терм сам по себе» (§30 п.2) | `membank` | `TestADeclineIsRefusedWhenTheSurfaceGOESONFiringAsAnotherTermsAlias` |
|
||
| AC | `aliasOwner` игнорирует статус (§30 п.3) | `membank` | `TestDecliningTheAliasOfAnUNSIGNEDRowIsAccepted` |
|
||
| AD | загрузчик отдаёт построенные записи при отказе (§30 п.1) | `membank` | `TestRepairingAnUnloadableDocumentIsNotRefusedOverWhatItUNMASKS` |
|
||
| AE | позиционный субъект (§30, «плюс два») | `membank` | `TestTwoRecordsWithNoIdentityDoNotBlockEachOthersRepair` |
|
||
| AF | довод отказа описывает всех, кого отвергает (§30 п.4) | `cmd/tmctl` | `TestTheKeysFileRefusalDescribesEveryoneItTurnsAway` |
|
||
| AG | остановленный вызов не печатает, что применил (§34) | `pipeline` | `TestAStoppedCallDoesNotPRINTThatItApplied` |
|
||
| AH | нечитаемая карта подписи ≠ «всё решено» (§34) | `pipeline` | `TestASignatureMapThatCannotBeReadDoesNotReportAsAllDecided` |
|
||
| AI | отказ не предупреждает о перезаписи, которой не будет (§34) | `pipeline` | `TestARefusedCallDoesNotWarnAboutARewriteThatWillNotHappen` |
|
||
| AJ | отказ по алиасу сида сужен до случая, где он инертен (§34) | `membank` | `TestDecliningASeedAliasIsAcceptedWhenTheDeltaHoldsThatRow` |
|
||
|
||
## 17. Единственная выжившая мутация — и почему пин на неё невозможен
|
||
|
||
**G (байтовый гейт).** Заказ §4.11(ж) просит поведенческий пин. Его нет и быть не может: гейт
|
||
**недостижим за первым**, и это доказывается, а не предполагается.
|
||
|
||
`deltaChanged = DeltaTouched && changedDoc(диск, next, пусто)`. `DeltaTouched` ставится только когда
|
||
решение реально изменило документ, а `next` — канонический рендер УЖЕ ИЗМЕНЁННОГО документа. Чтобы
|
||
`changedDoc` вернул false, байты на диске должны БЫТЬ этим рендером — но тогда документ, разобранный из
|
||
них, уже равен результату, никакое решение ничего не меняло, и `DeltaTouched` был бы false. Нормализация
|
||
щели не открывает: файл, чьи значения несут окружающие пробелы, в себя же не рендерится.
|
||
|
||
**Диспозиция:** гейт ОСТАВЛЕН как второй рубеж на случай, когда `*Touched` станет грубее сегодняшнего, и
|
||
запинен как КОНТРАКТ ФУНКЦИИ (`TestChangedDocIsTheSecondFenceAndItsContractIsPinnedDirectly`), включая
|
||
правило «отсутствующий файл + пустой документ — НЕ создавать». Снести его я не стал: изгородь, за которую
|
||
я вижу не до конца, дешевле оставить, чем убрать.
|
||
|
||
## 18. Замеры — своей названной методикой
|
||
|
||
**Методика:** `/usr/bin/time -f '%M %e'`, 3 прогона на размер, медиана, машина без нагрузки,
|
||
`bank-apply --dry-run` (записи нет), документы из N `approve` с короткой заметкой.
|
||
|
||
| N решений | размер документа | wall (мин/мед/макс) | maxRSS (мин/макс) |
|
||
|---|---|---|---|
|
||
| 1 000 | 82.9 КБ | 0.57 / 0.58 / 0.74 с | 51.9 / 52.1 МБ |
|
||
| 5 000 | 423 КБ | 4.91 / 5.18 / 5.36 с | 171 / 215 МБ |
|
||
| 10 000 | 848 КБ | 14.53 / 15.73 / 16.54 с | 391.7 / 391.9 МБ |
|
||
| 20 000 | 1.72 МБ | 46.72 / 52.16 / 52.20 с | 740 / 762 МБ |
|
||
|
||
**Разночтение по памяти между паком и приёмкой закрыто в пользу приёмки.** Пак дал 133/217/350 МБ на
|
||
5k/10k/20k, приёмка 199/395/798 МБ, мой замер — 203/392/745 МБ. Числа приёмки воспроизводятся, числа пака
|
||
занижены примерно вдвое. **По времени сойтись не удалось:** пак дал 1.60/3.62/9.08 с, у меня 5.2/15.7/50 с
|
||
— в 3–5 раз медленнее. Проверкой раунд-трипа это НЕ объясняется (§15: ≈5 %), бинарь пака я не
|
||
пере-собирал, и объяснения у меня нет. **Число, которое поедет в платформенный пак как датум для
|
||
таймаута, — моё или приёмочное, не паковское.**
|
||
|
||
**Почему цена сверхлинейная — факт исходника, а не гипотеза:** каждый пер-решенческий помощник
|
||
сканирует весь документ (`findTerm`, `dropTerms`, `addReject`, `aliasOwner`, `deltaHoldsSurface` — все
|
||
`O(len(документа))`; ⚠ первая редакция называла здесь `ownsSurface`, которой в коде нет — она была снята
|
||
вместе с побегом §30 п.2), поэтому проход по 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 }
|
||
```
|
||
|
||
`undecided > 0` — **факт**, что стоп не погаснет. `undecided == 0` — **не обещание**, и это написано в
|
||
комментарии типа явно. Пустой `map` значит «прогон до границы банка ещё не доходил». В режиме проекции
|
||
число — то, что БУДЕТ после применения (единственное чтение, которое может иметь превью).
|
||
|
||
Запинено там, где оно осмысленно, — в тесте цепи §4.12: частичный документ на карте из двух сурфейсов
|
||
даёт `undecided=1`, полный — `undecided=0`, и после него стоп действительно гаснет.
|
||
|
||
## 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-самопроверка
|
||
сравнила его с пропатченным же хешем и **прошла**. Дерево копии осталось с `rep.CanonicalRewrite = false`,
|
||
и **два полных раунда измерений после этого шли по мутированному коду**: три пина отчитались как поймавшие
|
||
мутации, к которым не имели отношения (E3, F, R, S, U, V показывали ложный «RED» на `TestTheReport…`).
|
||
|
||
Поймано ровно тем, чему учит §11 п.3 отчёта пака: одиночный прогон — не замер. Я пере-проверил один
|
||
подозрительный «RED» изолированным прогоном, он не воспроизвёлся, и это вскрыло дефект инструмента.
|
||
|
||
**Починено дважды:** одно чтение и одна запись на файл (порядок объявлен контрактом в комментарии), плюс
|
||
второй независимый рубеж — отпечаток ВСЕХ файлов каталога, снятый на заведомо чистом дереве и
|
||
пере-проверяемый ПЕРЕД каждой мутацией и после последней. Дерево копии после итогового прогона побайтно
|
||
равно рабочему (`diff -rq --exclude=bin <рабочее> <копия>` пусто, и
|
||
`find . \( -name '*.go' -o -name '*.json' \) | sort | xargs sha256sum | sha256sum` даёт на обеих сторонах
|
||
одно значение при 265 файлах. ⚠ Сам дайджест здесь НЕ приводится: дерево с тех пор менялось, и
|
||
записанное число воспроизвести уже нельзя — повторяемо равенство двух сторон, а не константа).
|
||
|
||
**Общая форма правила, шире этого инструмента:** самопроверка, чей эталон снят ПОСЛЕ первого изменения,
|
||
не проверяет ничего. Эталон снимается один раз, на состоянии, которое известно как чистое.
|
||
|
||
## 26. Попунктный чек §4 — все шестнадцать
|
||
|
||
Комплектность сверена механически: `grep -cE '^### 4\.' docs/BACKEND_SEAM_FIX_SESSION_PROMPT.md` → **16**.
|
||
|
||
| пункт | статус | чем закрыт |
|
||
|---|---|---|
|
||
| **4.1** ⛔ блокер: round-trip на ОБОИХ документах | ✅ **сделано ШИРЕ заказа** | нормализация документа + запроса, рендер ДОКАЗЫВАЕТ равенство (не только разбираемость — заказанная форма пропустила бы две тихие потери), байты проверены один раз и один раз записаны, фаззер пути ДВЕРИ, характеризационный тест на библиотеку. §15, §20; мутации I/J/K; живая проба §19 |
|
||
| **4.2** пред-существующие чинятся по одной | ✅ сделано; **дважды углублено по ревью** | вердикт загрузчика разложен по СУБЪЕКТАМ (`membank/problems.go`), ключ сравнения — субъект. Первая редакция останавливалась на два шага раньше: субъект нёс РЕНДЕРИНГ, и правка `dst` делала чужую поломку «новой» (§29 п.2); и поломки ПОД загрузчиком были не видны, пока документ не грузился (§29 п.3). Мутации T, Y, AD (⚠ X снята вместе с правилом маски — §29 п.3, каталог её больше не несёт); пины `TestRepairingOneOfTwoBrokenTermsIsAccepted`, `TestAFaultTheDoorHasNoChannelForCannotDeadlockIt`, `TestRepairingAnUnloadableDocumentIsNotRefusedOverWhatItUNMASKS`; живая проба §19 |
|
||
| **4.3** два ключа на один путь | ✅ **сделано ШИРЕ на одну проверку** | попарно различны четыре движковых пути (`project_db`, `glossary_seed`, `mined_delta`, `mined_rejects`) — `project_db == mined_delta` означало бы YAML поверх sqlite. Проверено на всех `book.yaml` репозитория и стенда (`find . -name book.yaml \| wc -l` → **22**): нарушителей ноль. Мутация M |
|
||
| **4.4** гард флага на ПРИСУТСТВИЕ | ✅ сделано | `fs.Visit` (единственное, что отвечает на этот вопрос), оба гарда, плюс пустое значение у присутствующего флага — тоже отказ. Мутация N |
|
||
| **4.5** кап документа + строка отказа полосой | ✅ сделано, ПЕРЕСМОТРЕНО по ревью | ДВА капа: 1 МиБ на чтение + **5 000 решений** на применение. Первая редакция капила только байты и утверждала, что этим ограничено ВРЕМЯ — опровергнуто замером (§29). Класс 14 у обоих. Хвост про `exit 2` — пингом. Мутации R, W |
|
||
| **4.6** конверт путей потребителю | ✅ сделано, компромисс назван | `manifest --json` несёт тот же `artifacts{}`; ВЫВОДИТСЯ на каждом чтении, поэтому версия документа не двигалась. Компромисс вслух: манифест семантически про дерево глав, путь артефакта там гость. Мутация V |
|
||
| **4.7** алиас судится по НАБОРУ | ✅ сделано; **углублено по §30 п.2** | проверка перенесена ПОСЛЕ свёртки и судит РЕЗУЛЬТАТ — именно поэтому два отказа (терм + его алиас) остаются законными: к концу вызова алиасом никто не владеет. ⚠ Первая редакция этой строки добавляла «своя поверхность проверяется по входному документу» — такого побега в коде НЕТ, он был заведён и снят как вредный (§30 п.2). Мутации P, AB |
|
||
| **4.8** перевёрнутое окно глав | ✅ сделано | отказ с цитатой окна; открытая верхняя граница законна. Мутация O |
|
||
| **4.9** честность про непогасший стоп | ✅ **исполнено фактом, не предсказанием** | `signature{map,surfaces,undecided}` по правилу самого прогона; `undecided>0` — факт, `undecided==0` — явно не обещание. Аргумент в §22. Мутация U |
|
||
| **4.10** SIGTERM | ✅ сделано; сомнение записки СНЯТО замером; **довод в комментарии исправлен по ревью** | ctx проверяется дважды, стоп не пишет ничего, exit 5. Несущий довод — НЕ длина непрерываемого участка (первая редакция назвала её «≈20 с, два порядка внутри stop grace», и это было неверно в 15 раз), а то, что записи идут последними и атомарно: убитый где угодно до них процесс не записал ничего. Мутация S (пин целится именно во вторую проверку) |
|
||
| **4.11** десять посадок, пин на каждую | ✅ 9 из 10 + доказательство по десятой | A–H, E1–E3 краснеют своими пинами; **G недостижима за первым гейтом — доказано, не предположено** (§17), вместо поведенческого пина запинен контракт функции |
|
||
| **4.12** цепь в репозитории | ✅ сделано | `internal/pipeline/bankchain_test.go`, $0 на фейковом провайдере, ОДИН проект от стопа до погасшего стопа; форма фикстуры (2 терма = 1 approve + 1 decline) проверяется как ПРЕМИСА; ограничение синтетики названо в комментарии; карта подписи после погасшего стопа зафиксирована явно |
|
||
| **4.13** девять мелочей | ✅ по каждой решение и довод | §21: починено 3, отклонено с аргументом 5, пингом 1 |
|
||
| **4.14** довод отказа `--keys-file` | ✅ сделано | текст называет `redrive` (платный, свой `.env`), `backup`/`migrate` (деплой-шаги) и $0-читающие поимённо. Развилку про канал ключей `redrive` НЕ решал |
|
||
| **4.15** три тавтологичных пина | ✅ сделано | вердикт `Preexisting` исполняется всеми четырьмя проверками (`TestAPreExistingFaultOfEveryKindStaysReportedAndUnblocking`); четвёртый оракул фаззера снят с тавтологии и стал НЕПОДВИЖНОЙ ТОЧКОЙ; `TestDecisionByBankID` сверяется с НЕЗАВИСИМО посчитанными константами + отдельный пин на инъективность, ради которой в `TermID` сделана длина-префикс |
|
||
| **4.16** платных вызовов ноль | ✅ | `sha256` базы стенда не сдвинулся; рантайм по платному пути — PLAUSIBLE, как и ожидалось |
|
||
| §5 батарея после каждой правки · посадки СВЕРХ списка · посадка называет ПАКЕТ · живая проба в КОПИИ · артефакт-находок | ✅ | 36 посадок (26 сверх списка §4.11), пакет пина ≠ пакет правки записан в каталоге, проба в копии, эта секция |
|
||
| §5.1 последний абзац — план или обещание? · дифф `^func Test` исполнением · интервальная самоверификация субагентом · каждый клейм — с результатом инструмента ЭТОЙ сессии | ✅ | обещаний в отчёте нет: всё найденное закрыто в дереве; §27; ревью гонялось в СЕРЕДИНЕ работы против явных критериев; каждое число выше — с командой |
|
||
| §6 три оси | ✅ | round-trip формата (§15, §20) · слепота гейта (§16, §17) · общность: в Go, которую я тронул, нет ни одного литерала пары/письменности/орфографии (проверено грепом по девяти файлам); нормализация — `strings.TrimSpace`, определённый Юникодом, а не скриптом |
|
||
| §8 заявление = команда · §9 эхо до работы · §10 «что НЕ удалось» · §11 развилки | ✅ | §14–§27 · выдано до кода · §24 · §21–§23 |
|
||
|
||
## 27. Дифф `^func Test` — исполнением
|
||
|
||
```
|
||
$ git diff -U0 -- backend | grep -cE "^-func (Test|Fuzz)" → 0 # удалено: НОЛЬ
|
||
|
||
$ git ls-files --others --exclude-standard -- backend | grep '_test.go$' \
|
||
| xargs grep -h '^func Test' | wc -l → 97 # пак оставил 59
|
||
$ ... | xargs grep -h '^func Fuzz' | wc -l → 3 # пак оставил 2
|
||
```
|
||
|
||
**Добавлено дофиксом:** 38 тест-функций и 1 фаззер. ⚠ Первая редакция писала 94/35 — цифра была снята до
|
||
правок §34, которые добавили ещё три пина. **Удалено: ноль** — и это проверено дважды: грепом
|
||
по диффу (0 удалённых `func Test`) и поимённо по семнадцати пинам, которые отчёт пака называет по имени
|
||
(каждый существует ровно один раз).
|
||
|
||
**Тронуто (тело изменено, сигнатура — нет):** `cmd/tmctl/exitcontract_test.go` — `TestTheRefusalTableIsTotal`
|
||
перестал держать ручной список и читает классы из ИСХОДНИКА обходом `GenDecl`/`ValueSpec` (⚠ не по
|
||
`*ast.BasicLit` в `case`, как соседний гейт диспетча: значения `RefusalClass` объявлены КОНСТАНТАМИ, и
|
||
копирование той техники воспроизвело бы её собственную слепоту — это её же пинг) ·
|
||
`internal/membank/decisions_test.go` — `TestDecisionByBankID` сверяется с независимо посчитанными
|
||
константами · `internal/membank/decisions_fuzz_test.go` — оракул 4 переписан с тавтологии на неподвижную
|
||
точку · `internal/pipeline/bankdecisions_test.go` и `statusartifacts_test.go` — сигнатура
|
||
`ApplyBankDecisions` получила `context.Context`.
|
||
|
||
**Новый файл:** `internal/pipeline/bankchain_test.go` (цепь §4.12).
|
||
|
||
## 28. Состав дофикса
|
||
|
||
**Новое:** `internal/seed/normalize.go` (канонический вид документа) · `internal/membank/problems.go`
|
||
(вердикт загрузчика по субъектам) · `cmd/tmmutate/` (харнесс мутаций + каталог посадок, `python3 -c "import json;print(len(json.load(open('cmd/tmmutate/mutations.json'))))"` → **36**) ·
|
||
`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, это единственная правка в самой
|
||
батарее и она ничего не ослабляет) + семь тест-файлов.
|
||
**Едут вместе с деревом и без них его нельзя принять:** `backend/docs/SEAM_FIX_PLAN.md` (записка-план
|
||
§7 промта) · `backend/docs/SEAM_PACK_FINDINGS.md` (эта доказательная база) · `backend/docs/SEAM_PACK_PLAN.md`
|
||
(план пака, не мой, лежит незакоммиченным с 24.08) · секция «Бэкенд» в `docs/PROGRESS.md` ·
|
||
`docs/BACKEND_SEAM_FIX_SESSION_PROMPT.md` (в нём дописана записка предыдущей сессии, §32).
|
||
|
||
## 29. Ревью старшей моделью: три дефекта, каждый воспроизведён МНОЙ и починен
|
||
|
||
Ревью запрашивалось по семи решениям, где заказ оставлял форму на мне. Оно согласилось с пятью, не
|
||
согласилось с одним и нашло три вещи, которых я не видел. **Каждую я воспроизвёл сам прежде, чем чинить**
|
||
— не потому что не доверяю, а потому что чужой результат, взятый на веру, ровно так же становится нормой.
|
||
|
||
### 1. Кап в БАЙТАХ не ограничивает ВРЕМЯ — мой довод был неверен
|
||
|
||
Мой расчёт «1 МиБ ≈ 12 000 решений ≈ 20 с» брал ≈85 байт на решение из документа с заметками. Минимальное
|
||
законное решение — `{"action":"decline","src":"t1"}`, ≈35 байт. **Пере-мерено мной, тот же метод:**
|
||
|
||
| N минимальных `decline` | размер | 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
|
||
rejected: ['方源→Фан Юань II: gender "badvalue" is not one of male|female|neuter|hidden']
|
||
preexisting: ['方源→Фан Юань: gender "badvalue" is not one of male|female|neuter|hidden']
|
||
```
|
||
|
||
Одна и та же поломка по обе стороны, под двумя разными рендерингами, и починить её через дверь нельзя
|
||
вовсе. Это §4.2 на уровень ниже — то есть мой фикс §4.2 остановился на шаг раньше.
|
||
**Починено:** `subjectOf(class, src, sense, since, until)` — субъект строится ТОЛЬКО из класса поломки и
|
||
идентичности терма, никогда из поля, которое дверь переписывает. Четыре проверки получили
|
||
субъект-несущие внутренние варианты, экспортированные `[]string`-обёртки не изменились (ни один чужой
|
||
вызов не тронут). После фикса тот же вход: `exit=0`, `dst` записан, поломка в `preexisting`. Пин
|
||
`TestAFaultTheDoorHasNoChannelForCannotDeadlockIt`, мутация Y.
|
||
|
||
### 3. И то же самое ещё уровнем выше: поломки, которые ЗАГРУЗЧИК прячет
|
||
|
||
Найдено мной при проверке п.2. Три проверки живут ПОД загрузчиком банка и на документе, который не
|
||
грузится, не выполняются вовсе. Значит, как только решение чинит загружаемость, всё, что они впервые
|
||
увидели, читается как новое — и владельца отвергают за починку того, что мешало всему.
|
||
**Воспроизведено:** дельта с одной строкой, у которой и `dst: ""` (не грузится), и `gender: alsobad`
|
||
(замаскировано) → починка `dst` даёт `exit=14` по gender.
|
||
**Первая починка была ПРАВИЛОМ МАСКИ** — «если до вызова документ не грузился, а после грузится, эти
|
||
поломки сообщаются и не отвергают ничего» — и она оказалась ХУЖЕ дефекта; как именно, см. §30 п.1.
|
||
**Итоговая починка другая и без исключений:** загрузчик отдаёт построенные им записи ДАЖЕ когда
|
||
отказывает, поэтому три проверки выполняются по обе стороны, и обычное сравнение по субъекту отвечает на
|
||
оба случая само. Пины `TestRepairingAnUnloadableDocumentIsNotRefusedOverWhatItUNMASKS` (обе половины),
|
||
мутации X (снята вместе с правилом), AD.
|
||
|
||
### 4. Латентная потеря данных на пути nil-байтов
|
||
|
||
Ревью нашло, я перепроверил чтением: если рендер ТЕКУЩЕГО документа падает и это пред-существующая
|
||
поломка, она сообщается, а не отвергает (и это правильно) — но тогда `res.DeltaBytes == nil`, а обвязка
|
||
считала `DeltaTouched && changedDoc(raw, nil, …)` → true и **записала бы файл нулевой длины поверх всех
|
||
одобрений владельца**. Ревьюер не сумел до этого дойти (130+ подобранных значений после нормализации
|
||
прошли round-trip), и я тоже; но `renderProved` существует именно потому, что множество ломающих значений
|
||
не известно целиком, и незащищённый `nil` противоречит его собственной предпосылке.
|
||
**Починено одной строкой:** nil-байты не пишутся никогда. Поведенческого пина нет по той же причине, по
|
||
которой его нет у G, — вход недостижим; инвариант чистого слоя (`accepted ⇒ DeltaBytes != nil`) держит
|
||
`FuzzDecisionFieldsThroughTheDoor`.
|
||
|
||
### 5. Три комментария несли неверные доводы — исправлены
|
||
|
||
В этом проекте комментарий — контракт, по которому следующая сессия действует, поэтому неверный ДОВОД при
|
||
верном коде опаснее опечатки. (а) `normalizedDecisions` объяснял себя тем, что «держит `note`
|
||
записываемым» — проверяемо неверно: документ нормализуется ещё раз на выходе. Настоящая цена —
|
||
ПРАВДИВОСТЬ ОТЧЁТА: без него повтор решения с пробельным текстом отвечает `applied`, пока байтовый гейт
|
||
отвечает `changed: false`. (б) Довод про SIGTERM опирался на длину непрерываемого участка с числом,
|
||
неверным в 15 раз; несущий довод — атомарность и порядок записей. (в) «Путь `yaml.Node` закрыт»
|
||
преувеличивало: закрыт `Encode`, а РУЧНАЯ сборка узла с явным стилем работает (`encode.go:557-570`) — она
|
||
отвергнута по цене (второй рендерер всей схемы), а не по невозможности.
|
||
|
||
### 6. Ещё две правки по ревью
|
||
|
||
**Нечитаемая карта подписи** возвращалась как `{surfaces:0, undecided:0}` — побайтно то же, что «всё
|
||
решено», единственный ответ, который это поле не должно давать по случайности. Заведено поле
|
||
`unreadable`; пин `TestASignatureMapThatCannotBeReadDoesNotReportAsAllDecided`, мутация Z.
|
||
**У `tmmutate` опасное значение было ДЕФОЛТНЫМ** (`-root .` правит живое дерево). Флаг стал обязательным;
|
||
в шапке названо и то, что отпечаток снимается на старте и потому сертифицирует только дерево, которое
|
||
было чистым в тот момент.
|
||
|
||
### С чем ревью НЕ согласилось и что я оставил как есть
|
||
|
||
Ничего: единственное «DISAGREE» — кап (п.1) — исполнено. Пять решений подтверждены, у двух из них ревью
|
||
дало довод СИЛЬНЕЕ моего, и я заменил довод в комментарии: у конверта путей на манифесте — что бамп
|
||
версии выбросил бы все сохранённые сайдкары и пере-нарезал каждую книгу ради аддитивного поля; у SIGTERM
|
||
— атомарность вместо длины.
|
||
|
||
## 30. Интервальная самоверификация субагентом: ещё пять дефектов, все воспроизведены и починены
|
||
|
||
Ревью гонялось В СЕРЕДИНЕ работы против семи ЯВНЫХ критериев (§5.1 промта), read-only, в копии.
|
||
Дерево двигалось под ним трижды, поэтому его вердикты пере-исполнены на финальном снимке. Каждый дефект
|
||
я воспроизвёл сам, своим прогоном, прежде чем чинить.
|
||
|
||
### 1. ⛔ Правило маски проглатывало ливлок, который вносил САМ вызов
|
||
|
||
Моё же исправление §29 п.3 оказалось хуже дефекта. **Воспроизведено мной:** дельта = битая строка
|
||
(`dst: ""`) + `Hero→Odin` с алиасом `Shared`; решения = починить битую строку + одобрить `Shared→Dva`.
|
||
|
||
```
|
||
маска снята: rejected=0 accepted=2 bytes=true
|
||
preexisting=[firing key "shared" is shared by different terms "Hero"→"Odin" и "Shared"→"Dva" … (livelock…)]
|
||
контроль (документ грузится): rejected=1, та же строка — ОТКАЗ
|
||
```
|
||
|
||
То есть дверь диагностировала детерминированный ливлок матчера, **записала документ** и отчиталась
|
||
`exit 0`, назвав СВОЮ поломку чужой. Комментарий правила («состояние после такого вызова никогда не хуже,
|
||
чем до») был ложью ровно здесь: до — банк не грузился (громкий стоп), после — грузится и инжектит ливлок.
|
||
И два опубликованных поля (`Preexisting`, `preexisting_problems`, оба обещающие «этот вызов их не
|
||
вызывал») становились ложными утверждениями.
|
||
|
||
**Починено иначе и без исключений:** правило маски снято целиком; вместо него `ParseBankSeed` возвращает
|
||
записи, которые он ПОСТРОИЛ, даже когда отказывает. Тогда три проверки под загрузчиком выполняются и на
|
||
«до», и на «после», сравнение по субъекту работает как везде, и оба случая решаются одним правилом:
|
||
починка загружаемости больше не отвергается (поломка видна с обеих сторон), а внесённый ливлок
|
||
отвергается (его субъекта на «до» нет). После фикса тот же вход: `rejected=1`, байты не выданы.
|
||
Пины — обе половины `TestRepairingAnUnloadableDocumentIsNotRefusedOverWhatItUNMASKS`, мутация AD.
|
||
|
||
### 2. Побег §4.7 принимал инертный отказ
|
||
|
||
`ownsSurface(in.Delta, …)` пропускал проверку, когда поверхность была термом дельты сама по себе.
|
||
**Воспроизведено:** дельта `Nick→Nik` и `Hero→Nik2 (alias Nick)`, `decline Nick` → `accepted=1`, строка
|
||
`Nick` снята, а `Hero` ПРОДОЛЖАЕТ фарить `Nick`. Ровно тот вред, ради которого §4.7 и заказан.
|
||
Замерено дополнительно: во всех остальных входах побег — no-op (после `dropTerms` алиасом никто не
|
||
владеет), то есть он менял ответ только во вредную сторону, и зеркальный случай, ради которого он якобы
|
||
существовал (отказ терму + отказ его алиасу), работает без него.
|
||
**Починено:** побег снят. Пин `TestADeclineIsRefusedWhenTheSurfaceGOESONFiringAsAnotherTermsAlias`,
|
||
мутация AB.
|
||
|
||
### 3. И то же правило ОТВЕРГАЛО законный отказ, с доводом, который неверен
|
||
|
||
`aliasOwner` не смотрел на СТАТУС. **Воспроизведено:** дельта `Hero (status: auto, alias Nick)`,
|
||
`decline Nick` → отказ: «"Nick" is an ALIAS of the approved term "Hero"→"" … already excluded from
|
||
proposals». Обе половины довода ложны: строка не approved, а `unsignedEngineSurfaces` выбрасывает mined
|
||
строки со статусом ≠ `approved`, то есть её поверхности из предложений НЕ исключены и отказ как раз
|
||
осмыслен. `status: auto` в дельте — не угол: это ровно состояние, о котором первая мина формата строки
|
||
199. **Починено:** `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.
|
||
**Фаззер двери возвращался рано на отказе** и потому не видел бы возврата блокер-класса. Оракул отказа
|
||
усилен: отказ обязан не выдать байтов, не отчитаться о принятых решениях и не изменить ни один документ —
|
||
то есть та же слабость, за которую §4.15 критикует старый оракул 4, снята и здесь.
|
||
|
||
### Что ревью подтвердило исполнением (не переделывал)
|
||
|
||
Нормализация идемпотентна на 50-строчном адверсариальном корпусе и КОПИРУЕТ (проверено мутацией выхода
|
||
при неподвижном входе) · ложного провала `renderProved` найти не удалось (400 000 случайных строк по
|
||
алфавиту управляющих/пунктуации/CJK, невалидный UTF-8, NUL, C0/C1, одиночные суррогаты) · оба капа не
|
||
off-by-one · после второй проверки контекста ничего не пишется, `defer lock.Release()` стоит сразу за
|
||
захватом · общности не нарушено ни одной новой строкой · `TestDecisionByBankID` и пин на инъективность
|
||
действительно независимы (все три константы пере-считаны в Python по спецификации из комментария
|
||
`TermID`) · `SeedProblems.Error()` побайтно равен прежнему склеенному сообщению.
|
||
|
||
⚠ **Унаследованное замечание, не дефект пака:** всё, на чём эта дверь ключуется, проходит через
|
||
`internal/text/norm.go`, где таблица `trad2simp` и ветка катакана→хирагана живут В GO, а не в langpack'е.
|
||
Пара, которой в репо нет, деградирует до NFKC+lowercase — то есть не блокирована, но фолд Go-резидентный.
|
||
Пингом, не правкой: это шире двери.
|
||
|
||
## 31. Батареи после всех правок — и одна правка в самой батарее
|
||
|
||
| команда | результат |
|
||
|---|---|
|
||
| `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 по построению, строка **123**. Больше ничего |
|
||
|
||
⚠ **Обе строки сняты ПОСЛЕ рефакторинга §33 и после всех правок по воркфлоу-ревью §34** — то есть
|
||
описывают дерево, которое передаётся.
|
||
|
||
**ОДНО утверждение о детерминизме, чтобы приёмка не гадала.** Два store-теста
|
||
(`TestKillMinus9LosesAtMostOneCall`, `TestTheSeamIsNotChargedToTheStoreOperationBudget`) ведут себя так:
|
||
в ПОЛНОЙ батарее на ненагруженной машине они проходят (строка выше, `EXIT=0`); под нагрузкой
|
||
параллельных сессий падают; а запущенные ИЗОЛИРОВАННО через `-run` падают **детерминированно и на обеих
|
||
сторонах A/B** — они сознательно сжимают `opTimeout`, и в одиночку сжатого времени не хватает. То есть:
|
||
красное в этих двух — ожидаемо и НЕ пак (доказано A/B ниже); зелёное в них — тоже нормальный исход;
|
||
красное в любом ДРУГОМ тесте — регрессия.
|
||
**Корпус под строгим загрузчиком — 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
|
||
```
|
||
|
||
Изолированный прогон обеих сторон даёт БУКВАЛЬНО одинаковый вывод — тот же тест, то же сообщение, тот же
|
||
номер миграции:
|
||
|
||
```
|
||
--- FAIL: TestTheSeamIsNotChargedToTheStoreOperationBudget (0.85s)
|
||
migrate_test.go:41: store: apply migration 9: context deadline exceeded
|
||
```
|
||
|
||
То есть на дереве, которого пак не касался, этот тест в изоляции падает ДЕТЕРМИНИРОВАННО: он сознательно
|
||
сжимает `opTimeout`, и в одиночку сжатого времени не хватает. Атрибуция закрыта — ни пак, ни дофикс тут
|
||
ни при чём, и это МОЙ замер, а не унаследованное утверждение. Тесты не тронуты (D39.121).
|
||
`TestManifestOfAnOlderDocumentVersionIsRefused` в стенд-батарее упал ЧИСТО от нагрузки: 5 из 5 зелёных
|
||
на обеих сторонах.
|
||
|
||
### ⚠ Одна правка В САМОЙ батарее, и вот замер, из-за которого она сделана
|
||
|
||
`Makefile`: `go test ./... -race -count=1` → `… -timeout=20m` (в обеих целях).
|
||
|
||
**Почему.** Go по умолчанию даёт пакету 10 минут. Замерено: `internal/pipeline` под `-race` идёт
|
||
**557.7 с** (`/usr/bin/time`, машина с ТРЕМЯ параллельными агент-сессиями, load average 3–12 в моменты
|
||
замера) — ⚠ первая редакция называла это «на незагруженной машине», и слово было НЕВЕРНО: нагрузка была,
|
||
что и вскрыла приёмка, намерив 92.8/97.6 с на действительно свободной машине. Оба замера настоящие —
|
||
разброс ×6 даёт сама нагрузка, и под ней два прогона батареи подряд закончились
|
||
`panic: test timed out after 10m0s` по всему пакету. Это читается как падение и падением не является.
|
||
Довод в `Makefile` переписан на диапазон с условиями, без слова idle и без привязки к машине (заказ §8
|
||
промта FIX2 закрыт этой правкой — оркестратор уведомлён).
|
||
|
||
**Это НЕ подгонка гейта под зелень:** таймаут — не утверждение. Ни один тест не ослаблен, ни один не
|
||
удалён; те же самые тесты просто доходят до конца. Пакет, который действительно ЗАВИС, всё так же
|
||
падает — на двадцатой минуте вместо десятой.
|
||
|
||
**Чего я НЕ доказал:** сколько из 557.7 с добавил дофикс. База в начале сессии на нетронутом дереве пака
|
||
дала 474.2 с, но внешняя нагрузка между двумя замерами не контролировалась, поэтому разницу я НЕ
|
||
атрибутирую. Что измерено точно: **все одиннадцать моих новых тестов этого пакета стоят 10.5 с** под
|
||
`-race` из 557.7 с, то есть 1.9 % — не они.
|
||
|
||
## 32. Записка предыдущей сессии — попунктно, включая одно ОПРОВЕРЖЕНИЕ
|
||
|
||
Записка живёт в хвосте `docs/BACKEND_SEAM_FIX_SESSION_PROMPT.md` (её дописала бэкенд-сессия пака перед
|
||
компактом своего контекста). Прошёл по ней целиком; она уехала в работу почти вся.
|
||
|
||
| что говорила записка | что вышло |
|
||
|---|---|
|
||
| «§4.1/§4.2/§4.4/§4.7/§4.8 я перепроверил сам, не трать время» | всё равно воспроизвёл сам — три из пяти живьём на копии книги стенда. Совпало |
|
||
| **«промт ШИРЕ замера: `dst` и `sense` с ведущим `\n` round-trip НЕ ломают»** | **ОПРОВЕРГНУТО моим замером (§15): ломают.** Ломается любое строковое поле обеих схем. Прав был ПРОМТ, а не записка — и если бы я поверил записке, фикс был бы заплаткой на одно поле |
|
||
| «харнесс мутаций умер вместе с сессией, пиши заново, сразу с отпечатком целей» | написан заново, **положен в репозиторий** с отпечатком до и после. Отпечатка оказалось мало — см. §25 |
|
||
| «одиночный прогон — не замер» | применено: перепроверка одного подозрительного `RED` изолированным прогоном вскрыла дефект самого харнесса |
|
||
| «субагенты пишут в твоё дерево, сверяйся `git ls-files --others`» | обоим ревьюерам запрещено писать, сверено в конце; свои два пробных файла удалил и проверил отсутствие |
|
||
| «красное в `internal/store` — почти наверняка не ты, но ПРОВЕРЬ ЗАМЕРОМ против HEAD» | сделан A/B против `git worktree` на HEAD (§31). Проверка была не лишней: она дала более сильный результат, чем ожидала записка |
|
||
| сомнение §4.9 «дверь этого не знает» | верно про предсказание — и оказалось, что дверь знает ДРУГОЕ и достаточное (§22) |
|
||
| сомнение §4.10 «кап может сделать проблему несуществующей — замерь ПОСЛЕ капа» | замерил: ~11 с, механизм с носителем. Но несущий довод другой — атомарность, а не длительность (§29 п.5) |
|
||
| сомнение §4.6 «сделал бы и назвал компромисс вслух» | так и сделано |
|
||
| сомнение §4.13 «`note` нельзя очистить — реши осознанно» | решено; попутно нашлась несогласованность, о которой записка не знала: `approve` заметку хранил, `decline` СТИРАЛ |
|
||
| «не наследуй ни моё, ни приёмочное число памяти, пере-мерь» | пере-мерено; сошлось с приёмкой, не с паком (§18) |
|
||
| «сначала посади мутацию, потом пиши пин» | применено; дважды поймало ВАКУУМНЫЕ пины моего же авторства (§16) |
|
||
| онбординг в код: два слоя, карта пинов, ловушки стенда (три файла с мёртвыми путями, схема v11, нет jieba) | всё три ловушки сработали ровно как описано; без записки это стоило бы нескольких заходов вслепую |
|
||
|
||
**Вывод для оркестратора:** записка была полезной и один раз ВРЕДНОЙ. Она не помечена как «проверено
|
||
исполнением» построчно, а её самый уверенный абзац оказался неверным. Полезен формат, а не авторитет.
|
||
|
||
## 33. Рефакторинг по итогам вопроса о качестве Go-кода
|
||
|
||
Замер, а не вкус — но замер пришлось делать ДВАЖДЫ, и первый был неверен.
|
||
|
||
⚠ **Первая редакция этой секции утверждала, что две точки входа были «самыми длинными функциями своих
|
||
пакетов», а потолок пакета — 75.** И то и другое НЕВЕРНО: правило, которым я считал, брало строки от
|
||
`^func` до следующего `^func` вместо тела функции, и на методах давало мусор. По правилу, считающему
|
||
ТЕЛО (от `^func` до первой строки ровно `}`), максимумы пакетов совсем другие: `internal/pipeline` — 218
|
||
(`Status`), `internal/membank` — 105 (`loadVoiceSections`). Никакой «нормы репозитория в 75» нет.
|
||
|
||
**Что верно по правильному замеру и чего хватает для довода:** две точки входа были **92 и 90** строк
|
||
кода при том, что следующая по длине функция в тех же двух файлах — **42**. То есть вход был вдвое
|
||
длиннее всего остального в самой двери, которую он и представляет.
|
||
|
||
Обе разобраны на именованные фазы — чистое извлечение, без изменения поведения:
|
||
|
||
```
|
||
ApplyDecisions → resolveAll · refuseSeedConflicts · foldAccepted · refuseInertDeclines · refuseNewFaults
|
||
ApplyBankDecisions → openDecisionRequest · readBookState · decisionReport · changedFiles · writeDecisionFiles
|
||
```
|
||
|
||
Тело `ApplyDecisions` стало последовательностью из пяти судящих фаз, из которых **ровно одна** что-то
|
||
меняет (`foldAccepted`) — это и есть контракт двери, теперь видимый в самом коде, а не только в
|
||
комментарии. Самая длинная функция обоих файлов после: **44** строки (`signatureState`), по команде
|
||
ниже. ⚠ Две прежние редакции писали здесь 45 и 48 — первая устарела вместе с правками §34, вторая
|
||
считалась неверным правилом.
|
||
|
||
**Команда** (правило §14 «каждое число — с командой»): непустые строки ТЕЛА, от `^func` до первой
|
||
строки ровно `}`, минус начинающиеся с `//`:
|
||
|
||
```
|
||
python3 - <<'EOF'
|
||
import pathlib,re,glob
|
||
def funcs(f):
|
||
src=pathlib.Path(f).read_text().splitlines(); out=[]
|
||
for i,l in enumerate(src):
|
||
if not l.startswith("func "): continue
|
||
j=i+1
|
||
while j<len(src) and src[j]!="}": j+=1
|
||
n=re.sub(r'^func\s+(\([^)]*\)\s*)?','',l).split("(")[0]
|
||
out.append((len([x for x in src[i:j+1] if x.strip() and not x.strip().startswith("//")]),n))
|
||
return out
|
||
for f in ("internal/membank/decisions.go","internal/pipeline/bankdecisions.go"):
|
||
for c,n in sorted(funcs(f),reverse=True)[:3]: print(c,n,f)
|
||
EOF
|
||
```
|
||
|
||
**Чем доказано, что поведение не изменилось:** весь каталог пере-прогнан после рефакторинга и дал ту же
|
||
картину, плюс зелёные пакеты и `make lint` — 0 issues.
|
||
|
||
⚠ **И вот чем это доказательство ОКАЗАЛОСЬ СЛАБЕЕ, чем здесь было написано.** Первая редакция этой
|
||
секции утверждала «32/32 как записано». На деле рефакторинг сломал ДВЕ записи каталога — они ссылались на
|
||
переменную, которую он убрал из области видимости, — и инструмент считал провал СБОРКИ красным, то есть
|
||
две записи не проверяли ничего, а отчёт называл тесты, которые не могли отработать. Нашло воркфлоу-ревью,
|
||
я воспроизвёл. Обе записи пере-выражены, у инструмента появился третий вердикт, и после этого прогон
|
||
даёт **36 посадок, 0 неожиданных исходов** — см. §34.
|
||
|
||
## 34. Воркфлоу-ревью: шесть осей, 50 находок, 26 переживших скептиков
|
||
|
||
Третье и последнее ревью — **воркфлоу**, а не одиночный агент: шесть независимых полос читают дерево
|
||
параллельно, каждая находка отдаётся отдельному скептику с заданием ЕЁ ОПРОВЕРГНУТЬ, выживает только то,
|
||
что он не смог. Полосы выбраны по тому, чего не касались два предыдущих ревью: гонка двух `bank-apply` ·
|
||
последствия новых полей для платформы (read-only) · эквивалентность только что сделанного рефакторинга ·
|
||
сам `cmd/tmmutate` (он теперь в репозитории, значит продакшн-код) · честность хендофф-документов ·
|
||
общность и безопасность данных. **50 сырых находок, 26 пережили скептиков** (2 мажора, 12 миноров,
|
||
12 в ранге «пинг» — из которых два настоящих пинга ниже, два «дефекта нет, проверено», а остальные восемь
|
||
по существу мелкие правки комментариев и документов и сделаны здесь же). Каждую я перепроверил своим
|
||
прогоном прежде, чем чинить.
|
||
|
||
### ⛔ Главное: две посадки каталога НЕ КОМПИЛИРОВАЛИСЬ — и это внёс мой же рефакторинг
|
||
|
||
`P-alias-judged-on-the-set` и `AB-alias-escape` патчат тело `refuseInertDeclines`, ссылаясь на `in.Delta`.
|
||
Рефакторинг §33 убрал `in` из области видимости этой функции. **Проверено мной:** прогон всех 36 посадок
|
||
с `go build` после каждой — ровно эти две не собираются (`undefined: in`).
|
||
|
||
А инструмент считал их КРАСНЫМИ, потому что `red` был буквально `terr != nil`, и провал СБОРКИ выглядел
|
||
так же, как поймавший пин. То есть §33 «весь каталог пере-прогнан, 32/32 как записано» было **слабее, чем
|
||
я написал**: две записи не проверяли ничего, а отчёт называл конкретные тесты, которые не могли отработать.
|
||
|
||
**Починено на двух уровнях.**
|
||
(1) Обе записи пере-выражены так, чтобы компилироваться против новой сигнатуры, и теперь краснеют на
|
||
своих пинах поимённо.
|
||
(2) У инструмента появился ТРЕТИЙ вердикт. `RED` теперь требует, чтобы хоть один тест назвал себя
|
||
(`--- FAIL:`); прогон, в котором не отчитался ни один тест, — это `NOTHING`, и он **роняет гейт**. Плюс
|
||
перед первой посадкой каждый пакет прогоняется НЕТРОНУТЫМ и обязан быть зелёным: без базовой линии
|
||
«этот пин ловит эту мутацию» опирается на код возврата, который выдают также сборка, флейк, таймаут и
|
||
опечатка в имени пакета. **Негативная проба:** посадка, которая заведомо не компилируется, теперь даёт
|
||
`NOTHING … 1 unexpected outcome(s)` и exit 1.
|
||
|
||
### Продуктовые дефекты — все воспроизведены мной
|
||
|
||
| что | как это выглядело | починка |
|
||
|---|---|---|
|
||
| **остановленный вызов ПЕЧАТАЛ, что применил** | на отмене контекста отчёт уходил в stdout с `mode:"apply"`, `changed:true` и `state:"applied"` — при нуле записанных байт. Отличал их только код выхода | `mode:"stopped"`, `changed:false`. Пин `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 |
|
||
|
||
### Пинги, а не правки
|
||
|
||
- **⛔ Арбитр покрывает ПРОЕКТ, а не пути.** Flock берётся на `<project_db>.lock`, пишутся
|
||
`mined_delta`/`mined_rejects`, которые `book.yaml` может направить куда угодно. Две книги с РАЗНЫМИ
|
||
`project_db` и ОДНИМ delta друг друга не исключают: ревью продемонстрировало гонку на 1500 одобрений с
|
||
каждой стороны — одна сторона потеряла всё, `exit 0`, отчёт «применено». Внутрикнижную версию этого
|
||
§4.3 запрещает; межкнижная законна сегодня. **Выбор между вторым арбитром и отказом при загрузке
|
||
трогает ратифицированный закон п.2 — это не решение одного пака.** В коде теперь стоит явное «арбитр
|
||
покрывает проект, а не пути», а не молчаливое допущение. ⚠ В корпусе нарушителей нет: все объявленные
|
||
пути лежат внутри своей книги (мёртвый префикс `/home/ubuntu/` — это переезд машины, D39.157, а не
|
||
чужой каталог).
|
||
- **Карта подписи пишется обычным `os.WriteFile`**, мимо атомарной конвенции сайдкаров этого же пакета —
|
||
пред-существующее, не мой код.
|
||
|
||
### Что ревью ПОДТВЕРДИЛО (и это тоже результат)
|
||
|
||
**Рефакторинг §33 — эквивалентен**: порядок фаз, мутация `rs[i].ok` через слайс и место отката проверены
|
||
отдельной полосой, расхождений нет. **Платформа не ломается**: аддитивные поля отчёта, конверт
|
||
`artifacts{}` на манифесте без бампа версии, код 14 и размерные капы — прослежены до её кода, дефектов
|
||
нет. Исправлены при этом два комментария, утверждавших про чужую зону неверное: код 5 платформа пишет как
|
||
`stopped` (и только при зафиксированном намерении остановиться, иначе `failed`) — не `paused`; и отчёт
|
||
печатается НЕ в каждом исходе — три отказа (10, 14 до чтения книги, 12) не печатают ничего.
|
||
|
||
### Правки в самих документах
|
||
|
||
Ревью нашло семь мест, где документ нарушал собственное правило «каждое число — с командой»: «≈13 % по
|
||
памяти» противоречило трём числам рядом (диапазоны перекрываются — снято как НЕ измеренное) · размер
|
||
каталога назывался и 24, и 32 · §26 ссылалась на снятую мутацию X · §28 не перечисляла три документа,
|
||
без которых дерево нельзя принять · «20 из 24» не воспроизводится нынешним каталогом (помечено как
|
||
исторический замер) · дайджест дерева снят как невоспроизводимая константа · строка про `aliasOwner`
|
||
описывала проверку, которой в коде нет. Все семь исправлены.
|
||
|
||
## 35. Проверка ЗАКРЫТИЯ — отдельным воркфлоу, и она нашла, что закрыто не всё
|
||
|
||
На вопрос «все замечания исправлены?» я не стал отвечать по памяти: четыре независимых агента прошли по
|
||
всем 26 пережившим находкам §34 и по живому дереву решали КАЖДУЮ — закрыта / не закрыта / частично /
|
||
отклонена осознанно. **Из 28 проверенных пунктов закрытыми оказались 16, осознанно отклонёнными 2,
|
||
частично 7 и НЕ закрытыми 3.**
|
||
|
||
Что доделано по этой проверке:
|
||
|
||
| остаток | что было не так | как закрыто |
|
||
|---|---|---|
|
||
| **F4** | остановленный вызов перестал врать в `mode`/`changed`, но `accepted[]` по-прежнему нёс `state:"applied"` — а это ровно то поле, которое потребитель перебирает | список очищается, как и на пути отказа; пин расширен, мутация AG бьёт по всем трём полям |
|
||
| **F16** | новая формулировка сама сделала исчерпывающее заявление («три отказа») и промахнулась: `schema_mismatch` (13) тоже печатает пустоту | перечислены все четыре плюс два не-отказных случая (5 и 1) |
|
||
| **F17** | тот же ложный клейм про платформу выжил в ЭТОМ ЖЕ паке, в `bankdecisions_test.go`; плюс ссылка `:755` на строку `:756` | оба исправлены; `grep paused` по движку — ложных клеймов не осталось |
|
||
| **§33** | ⛔ «две точки входа — самые длинные функции своих пакетов, потолок пакета 75» **НЕВЕРНО**: правило считало не тела функций. Настоящие максимумы — 218 (`Status`) и 105 (`loadVoiceSections`) | утверждение снято, довод пере-сформулирован на том, что верно (92/90 против 42 у следующей функции тех же двух файлов), приведена команда |
|
||
| **§16** | секция, которая ПРЕДСТАВЛЯЕТ каталог, называла его размер неверно (32/31 при 36 записях) и не несла четырёх строк §34 | пере-считано командой, строки добавлены |
|
||
| **§18** | в списке «пер-решенческих помощников» стояла `ownsSurface`, которой в коде нет — довод Θ(N²) частично опирался на несуществующую функцию | заменена на реально существующую `deltaHoldsSurface` |
|
||
| **§26** | строка §4.7 в одном предложении противоречила и себе, и коду, и §21 | пере-написана по коду |
|
||
| **§31** | два разных утверждения о детерминизме store-флейков в одной секции (`EXIT=0` против `5×FAIL`) | одно: в полной батарее на ненагруженной машине зелены, под нагрузкой падают, ИЗОЛИРОВАННО падают детерминированно на обеих сторонах A/B |
|
||
| **§15 ↔ §18** | две несогласованные тройки секунд на 10 000 решений | сказано, что абсолютные числа между секциями не складываются, и что ≈5 % выведены только из пары, снятой подряд |
|
||
| **§27** | 94 тест-функции / «добавлено 35» — снято ДО правок §34 | 97 / 38, командой |
|
||
| **§31, корпус** | строка «45 из 45» стояла ВНЕ таблицы (сломанный markdown) и её вторая половина не несла команды | вынесена блоком с обеими командами |
|
||
| **журнал** | пинг пака «1.6с на 5 000 / 9.1с на 20 000» — датум, которым платформенный пак будет мерить таймаут, расходится с моим замером в 3–16 раз; пинг «кап — однострочник при лендинге» уже сделан; четыре пинга жили только здесь | в журнал добавлен блок «что в записи пака устарело» и четыре недостающих пинга |
|
||
|
||
**Что осталось ОСОЗНАННО не сделанным** — два пункта, оба вне полномочий пака: межкнижная гонка за один
|
||
`mined_delta` (трогает ратифицированный закон п.2) и запись карты подписи обычным `os.WriteFile`
|
||
(пред-существующее, не мой код).
|
||
|
||
⚠ **Вывод, который стоит унести дальше самого пака.** Это было ТРЕТЬЕ ревью подряд, и каждое находило
|
||
настоящие дефекты, которых не видели предыдущие: одиночный старший ревьюер — три, интервальное — пять,
|
||
воркфлоу — двадцать шесть, проверка закрытия — ещё десять остатков. **Ни одно «все замечания исправлены»
|
||
в этой сессии не оказалось верным при проверке.** Вывод не «ревью бесполезны», а обратный: закрытие надо
|
||
ПРОВЕРЯТЬ отдельно от починки, и не тем, кто чинил.
|
||
|
||
---
|
||
|
||
# ДОФИКС-2 (промт BACKEND_SEAM_FIX2_SESSION_PROMPT.md, 13 пунктов §4) — секции 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)
|
||
# ⚠ первая редакция этой строки писала 115: снималась ДО реверс-пина §42/4б и якоря §4.4 — поймано
|
||
# проверкой закрытия, пере-снято после них
|
||
```
|
||
Переименовано (не удалено): `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 поимённо; ⚠ первая редакция писала «шесть» —
|
||
сумма по FX1/FX2/FX4 выдана за вывод одной посадки, поймано воркфлоу-ревью, §45).
|
||
|
||
## 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-резюм в обеих семантиках.
|
||
|
||
## 42. Интервальная самоверификация субагентом (§5.1): четыре находки, все minor, все закрыты или аргументированы
|
||
|
||
Ревью шло против десяти явных критериев, адверсариально, с исполнением (точечные тесты + посадки в
|
||
своей копии). Код по всем десяти сошёлся; находки — класс «довод/покрытие», ровно тот, что предыдущая
|
||
сессия назвала своим худшим.
|
||
|
||
| # | находка | диспозиция |
|
||
|---|---|---|
|
||
| 1 | докстрока `renderSignatureStop` учила отменённой механике «промотируй/отклони, потом re-run» при верном печатаемом тексте | закрыта (переписана на модель флажка; печатаемый баннер был верен и не менялся) |
|
||
| 2 | комментарий v16 в `migrate.go` формулировал предикат как «a surface outside this set» — читается как «любая поверхность вне», тогда как код стреляет только на кластере, чья ВСЯ поверхность вне памяти; реализация «по комментарию» вернула бы ре-стоп при росте кластера | закрыта («whole surface set outside», с named-ценой ошибки) |
|
||
| 3 | `bankapply.go`: «stop before the lock (5)» — безотчётное stop-окно шире (пост-локовая проверка `ctx.Err()` до `ApplyDecisions` тоже без отчёта) | закрыта («before the VERDICT exists», обе проверки названы; поведение верно — печатать нечего, вердикта ещё нет) |
|
||
| 4а | грань домена §4.1 «предикат по итоговой карте, не по пулу ДО потолка» не краснится ни одной посадкой: вечный стоп невоспроизводим в фикстурах (2 терма при потолке 200), а одно-строчной мутации не существует — карта, предикат и память читают ОДИН список `mined` после потолков, расхождение требует сознательной перестройки | **аргументированный не-пин** по прецеденту G: сама структура (один список) и есть защита; записано здесь, чтобы следующая сессия не считала свип дырявым |
|
||
| 4б | новизна из ОБРАТНОЙ секции не была покрыта: в стоп-фикстурах terminology-гейт выключен и reverse пуста | закрыта поведенческим пином `TestNoveltyFromTheReverseSectionAloneTripsTheFlag`: run 1 с пред-отклонённой banknote-only поверхностью (карта без неё), run 2 со снятым отказом — стоп обязан сработать на новизне, пришедшей ТОЛЬКО через обратную дверь |
|
||
|
||
**Что ревью подтвердило исполнением (не переделывал):** предикат/карта/память — один список после обеих
|
||
секций и потолков (`mining.go`, file:line в его отчёте) · все шесть исходов отчёта проходят через
|
||
`finishReport`; исходные поля присваиваются ровно в ДВУХ конструкторах — `finishReport` и до-локовом
|
||
`capRefusalReport` (у капа `bookState` ещё не существует; проверка закрытия поймала, что первая редакция
|
||
этой строки говорила «нет нигде» — категорика была шире правды) · `TestTheRefusalTableIsTotal` невакуумен для
|
||
класса 15 (AST-словарь + сверка длины таблицы) · retired-ключи ловятся полями, а не голым strict-decode ·
|
||
конверт карты читают все движковые читатели, `DecodeFile` на карту не наведён · дверь память не читает ·
|
||
в новом Go-коде ветвлений по паре/книге нет.
|
||
|
||
## 43. Попунктный чек §4 — все тринадцать
|
||
|
||
Комплектность сверена механически: `grep -cE '^### 4\.' docs/BACKEND_SEAM_FIX2_SESSION_PROMPT.md` → **13**.
|
||
|
||
| пункт | статус | чем закрыт |
|
||
|---|---|---|
|
||
| **4.1** память «предъявленного стопом» | ✅ | store v16 `bank_stop_presented` (плоское множество поверхностей кластеров — Src+Aliases, `INSERT OR IGNORE`, без обрезки); предикат `hasUnpresentedCluster` — пересечение кластера с памятью, домен — ИТОГОВАЯ карта после потолков (обе секции); двусторонний остаток записан пином. Цена миграции названа (§37). Пины: цепь Б, `stopflag_test.go`, `stopmemory_test.go`, реверс-новизна; мутации FX1–FX3 |
|
||
| **4.2** порядок и момент записи | ✅ | карта (атомарно) → память, в момент СТОПА; отказ памяти НЕ валит стоп (ERROR-строка, названо §37); «подтверди карту» НЕ заведено; draft-only ветка названа. Развилка карта/SQLite-транзакция решена явно (направление потерь — доброкачественный повторный стоп) |
|
||
| **4.3** авто-провод при поднятом флаге | ✅ | «останавливаться ли» и «проводить ли» — два решения; авто-провод на ЛЮБОМ неостановившемся прогоне; вторая половина названа: остановившийся прогон авто-банк НЕ пишет (решение с доводом, §37). Мутация FX4; пины в WHATSurvives и цепи Б |
|
||
| **4.4** хвост за потолком | ✅ не лечился | пере-замер своей командой (2354/209/11/198, §38) — числа промта без первоисточника не цитируются; фильтр карты НЕ заведён; пинг в журнале |
|
||
| **4.5** единая точка фиксации | ✅ | вердикт (`decisionVerdict`) / исход (`finishReport` — единственный конструктор, свитч по `applyOutcome`, шесть исходов); «записано» — пост-состояние ПЕРЕЧИТАННЫХ файлов; пин `TestEveryOutcomeStampsItsOwnMode` + греп-инвариант (присваиваний вне конструктора нет — подтверждено интервальным ревью). Вердиктная половина сохранена (сильная форма отвергнута по букве промта) |
|
||
| **4.6** `signature.undecided` | ✅ | пере-документирован: НЕ отвечает на вопрос о стопе (в обе стороны), информационный счётчик для экрана подписи; память дверь НЕ читает (решение с доводом: ответ требовал бы следующего намайненного набора, которого нет ни у кого за $0); поле не удалено |
|
||
| **4.7** полоса в два яруса | ✅ | пол «нет отката, повтор безопасен»; клауза «ничего не произошло» — у пяти исходных классов; гардрейл «разрушительное — только на классе» буквой; класс `write_incomplete` (exit 15) один на оба исхода, различие — `written_*`; отказ по обоим капам печатает отчёт (контракт сведён к одному носителю). Мутации FX18–FX20 |
|
||
| **4.8** подготовка обоих файлов | ✅ | stage обоих (write+sync+chmod) → два rename → fsync каталога; заявлено НЕ шире сделанного (атомарность против среды, не против SIGKILL — довод в `writeDecisionFiles`, остаток в §41); отказ синка — класс 15 с оговоркой долговечности. Мутации FX16–FX17, пины staged/unsyncable |
|
||
| **4.9** Clean + конвенция | ✅ | (а) `filepath.Clean` абсолютных в resolve, репро приёмки закрыто пином; (б) оба ключа СНЯТЫ retired-отказом с лечением в тексте (прецедент `adult:`), пути — чистая функция каталога и book_id: МЕЖКНИЖНАЯ гонка (⛔-пинг прошлого пака) снята; воркфлоу-ревью показало ОСТАТОК той же оси — второй конфиг ТОЙ ЖЕ книги (один каталог, один book_id, другой объявленный project_db) пишет те же файлы под другим локом; остаток — пинг, не решение пака (§45). Мутации FX12–FX13 |
|
||
| **4.10** карта: атомарность и конверт | ✅ | `writeFileAtomic` + конверт `map_version`/`map_id` (sha256 содержимого), рендер ДОКАЗЫВАЕТ round-trip (`RenderSignatureMap` через `renderProved`); легаси-карта читается; карта НЕ накопитель (слияния нет). Мутации FX14–FX15; живьём — инод-пин |
|
||
| **4.11** слепые пятна | ✅ | все шесть строк таблицы закрыты посадкой+пином (normText-класс · субъект-класс · субъект-окно · абсолютность путей отчёта двери · границы обоих капов · граница окна); пин семантики §4.1 — цепь Б + два переписанных харнесс-теста (возврат старой формулы краснит три теста, вывод tmmutate по FX2 поимённо; «шесть» первой редакции — ошибка, §45). Вакуумный пин пойман посадкой ДО записи (§38) |
|
||
| **4.12** наследие — списком | ✅ | шапка mining.go · комментарий у loadMinedRejects · unsignedEngineSurfaces · docstring runBankMiningStop · сообщение стопа · renderSignatureStop + два стейл-хинта render.go · help `--verify-bank` · WaveSignatureStop/waverun · комментарий v16; новое состояние «дельта непустая, нового нет» — явная WARN-строка; цепной тест переписан (замена названа: `TestTheStopIsAFlagNotAGateOnDecisions`); пинги с ПОРЯДКОМ и миграционная нота — в журнале |
|
||
| **4.13** платных вызовов ноль | ✅ | фейковый провайдер + харнесс + копия стенда; sha256 стендовой базы не сдвинулся; рантайм по платному пути — PLAUSIBLE; «выигрыш по чекпоинтам» НЕ заявлен (механизма нет, §41) |
|
||
| §5 батарея/каталог/проба/артефакт | ✅ | батарея после каждой правки; `make battery` EXIT=0; стенд-батарея красна ровно на jieba-парити; каталог 36→56, `NOTHING` роняет гейт (свойство не тронуто); живая проба §39; эта база |
|
||
| §6 три оси | ✅ | семантика флажка — прогонами движка (цепи, харнесс), без кода платформы · точка фиксации — конструктор + греп + пин исходов · слепота гейта — 56 посадок / 0 неожиданных |
|
||
| §7–§11 | ✅ | план до кода (`SEAM_FIX2_PLAN.md`, механическая сверка 13) · эхо до работы · «что НЕ удалось» §41 · развилки решались по букве промта, интерпретаций против него нет |
|
||
|
||
## 44. Проверка ЗАКРЫТИЯ — отдельным агентом, не чинившим (§5.1)
|
||
|
||
Прогнана по презумпции «каждое заявленное закрытие ложно, пока не увидено исполнением»: все 13 строк §43
|
||
и все 4 диспозиции §42 подтверждены в дереве (13 точечных тестовых прогонов + 3 полных пакета — зелёные;
|
||
носители по file:line; реверс-пин признан невакуумным с названным механизмом падения). Найдено ПЯТЬ
|
||
расхождений — традиция «ни одно "всё исправлено" не выдерживает проверки» держится и на этой сессии:
|
||
|
||
| # | находка | диспозиция |
|
||
|---|---|---|
|
||
| 1 | счётчик §40 «115» устарел на единицу (реверс-пин добавлен после замера) | пере-снят ПОСЛЕ всех правок: **117** (+ якорь §4.4); поправка с провенансом в §40 |
|
||
| 2 | untracked-бинарь `backend/tmctl` (20 МБ, след сессии) | удалён |
|
||
| 3 | клейм §42 «присваиваний вне конструктора нет» буквально ложен: `capRefusalReport` — второй, до-локовый конструктор | формулировка исправлена в §42 (код законен, документирован, запинен — расходилась категорика, не закрытие) |
|
||
| 4 | пере-замер §4.4 жил только в скратчпаде — тот же класс «числа без первоисточника», за который промт корил 2110/… | проб положен в дерево постоянным якорем-пином `internal/miner/emissioncap_test.go`: утверждает текущее поведение, краснеет в день сдвига потолка/порядка фильтров (прецедент 0f4c556); §38 поправлен |
|
||
| 5 | untracked вне backend/+docs/: `eval/dovodka/*`, `platform/docs/p8-review/*` | НЕ этого пака (mtime 24.08, параллельные зоны — платформенный P8 назван промтом законной параллелью); чужое не трогаю, оркестратору видно из этого же отчёта |
|
||
|
||
После правок: батарея пакетов miner/pipeline/membank/config/seed/store — зелёная; каталог не менялся
|
||
(56 записей — финальный прогон «0 неожиданных» шёл на дереве ДО пунктов 1–4, ни один из которых не
|
||
трогает код вне нового тест-файла miner).
|
||
|
||
## 45. Воркфлоу-ревью: шесть полос × три скептика — 23 сырых находки, 20 выживших, 16 уникальных, все закрыты
|
||
|
||
Форма: шесть параллельных полос-искателей под антипаттерны проекта (неверные доводы при верном коде ·
|
||
вакуумные пины · семантика флажка глубоким чтением · дверь/отчёт глубоким чтением · общность движка ·
|
||
шов/контракт), каждая находка — через трёх адверсариальных скептиков с разными линзами (корректность
|
||
трассой · достижимость · «не принятый ли уже остаток»), выживание — большинство; 75 агентов, скептики
|
||
убили 3, дубли между полосами — 4. Полосы отдельно подтвердили 75 позиций «проверено, сходится».
|
||
|
||
**Кодовые (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 major + 10 minor), все закрыты:**
|
||
|
||
- **«FX2 краснит шесть тестов»** (§40/§43, дважды) — ложь, приписанная инструменту: краснят РОВНО ТРИ
|
||
(`TestTheStopIsAFlag…`, `TestMiningStopWHATSurvivesResume`, `TestTerminologistResumeIsFree`);
|
||
«шесть» было суммой по FX1/FX2/FX4, выданной за вывод одной посадки — нарушение §8 в самой острой
|
||
форме (число рядом с командой, которая его опровергает). Оба места исправлены с провенансом ошибки.
|
||
- **Якорь-пин §4.4 НЕ краснел при сдвиге потолка** — премиссы не фиксировали значение 200 (150/300/400
|
||
проходили зелёными), вопреки трём носителям клейма. Значение потолка теперь запинено премиссой с
|
||
текстом, ведущим двигающего к пингу; проверено: смена 200 → красный.
|
||
- **«pre-lock check» в `bankapply.go` не существовал** — обе проверки контекста пост-локовые; переписано
|
||
на правду (пост-чтение — без отчёта, пред-запись — С отчётом; стоп раньше первой проверки вообще не
|
||
наблюдается). ⚠ Это была моя правка ПО находке интервального ревью — исправление само внесло ложь;
|
||
§42 п.3 диспозиция скорректирована этим пунктом.
|
||
- **Шапка полосы в `cmd/tmctl/main.go` осталась вторым носителем старого пола** («nothing was written»),
|
||
который класс 15 опровергает — приведена к двух-ярусной букве, «nothing was written» перенесено в
|
||
клаузы классов.
|
||
- **Довод арбитра сужен**: «межкнижная гонка недостижима по построению» была шире правды — остался
|
||
второй конфиг ТОЙ ЖЕ книги (один каталог/book_id, другой объявленный `project_db`): пишет те же файлы
|
||
под другим локом, мимо flock живого прогона (ревью воспроизвело живьём). Межкнижная версия снята;
|
||
остаток — пинг в журнале (второй арбитр/отказ при загрузке — закон п.2, решение не паку); комментарий
|
||
`bankdecisions.go`, §43 и журнальная запись исправлены.
|
||
- Minor-доводы, все закрыты: клауза «nothing happened at all» классов 10–14 сужена до «состояния книги»
|
||
(пред-полётный бэкап translate пишется ДО отказа 12/13 — теперь оговорён) · v16-комментарий migrate.go
|
||
завышал цену контрфактуала («re-stop on every run» → один лишний стоп на рост кластера) · draft-only
|
||
прогон с флагом печатал «nothing in the delta is new to it», хотя новизна не вычислялась — своя строка ·
|
||
баннер стопа выдавал размер карты за число НОВЫХ термов (`(N term(s) in the map)`, `WaveSignatureStop.Error`
|
||
переписан; поимённой разметки новизны в карте нет — продуктовая поверхность, не этот пак) ·
|
||
`bankexport.go` учил отменённую механику «promote/decline в файлы» (→ дверь) · стейл `:485` в render.go
|
||
(→ по имени) · док `addKeyed` «only a record's position» (ложь — статусы и рендеринги тоже) ·
|
||
кап-отчёт: `preexisting_problems: []` ≠ «проверено чисто» — оговорено на поле · фикстура нормализации
|
||
в `stopflag_test.go` была тождеством под фолдом (клейм про trad→simp был вакуумен) — заменена на 陳博
|
||
с премисс-проверкой пересечения фолда.
|
||
|
||
Каталог: 56 → **58** посадок (FX21, FX22). После всех правок этой секции: полный прогон каталога на
|
||
копии итогового дерева — **58 записей, 0 неожиданных исходов** (FX21/FX22 краснеют своими пинами
|
||
поимённо; посадка «потолок 200 → 150» отдельно проверена руками — якорь §4.4 красный с текстом,
|
||
ведущим к пингу); `make battery` — **EXIT=0**, `make lint` — 0 issues.
|
||
|
||
---
|
||
|
||
# ФИКС3 (промт BACKEND_SEAM_FIX3_SESSION_PROMPT.md, 6 пунктов) — секция 46
|
||
|
||
## 46. Фикс-лист приёмки дофикса-2: четыре пина, один снос, зачистка двух контрактов
|
||
|
||
Комплектность: `grep -cE '^### 4\.' docs/BACKEND_SEAM_FIX3_SESSION_PROMPT.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.2-пина фикс2 и не пере-снят |
|
||
| **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*` (⚠ первая редакция писала «0, тесты жили в незакоммиченном файле» — ложь,
|
||
пойманная проверкой закрытия: `contractblockers_test.go` ЗАКОММИЧЕН, дифф их видит; снос легитимен —
|
||
предмет снесён заказом §4.5, других удалений диффа нет).
|
||
|
||
**Проверка закрытия (не-чинившим):** все шесть пунктов подтверждены исполнением — обе мутации (FX23,
|
||
FX24) посажены им в своей копии и убиты пинами; греп-оси §6 пере-сняты; три расхождения — два неточных
|
||
самоотчёта этой секции (исправлены выше с провенансом) и вновь рождённый `backend/tmctl` (артефакт
|
||
`go build ./cmd/tmctl/` из каталога; удалён, класс закрыт `backend/.gitignore` с `/tmctl`).
|