Cut documentation sediment in the backend zone: agents read each file end to end and removed layered history whose content lives in the decision journal

This commit is contained in:
heaven 2026-09-02 05:49:23 +03:00
parent 6c26ac231b
commit 5b187db0d2
9 changed files with 518 additions and 1278 deletions

View file

@ -2,16 +2,16 @@
Зона сессии «Бэкенд». Контракт — `docs/architecture/05-decisions-log.md` (свежая голова — с хвоста файла); целевая архитектура (7 слоёв, инвариант общности §0.1 — любая книга/пара/структура; статус стройки — шапка-таблица) — `docs/architecture/09-target-architecture.md`; норматив общности — `12-go-style-notes.md`; **актуальный сессионный промт — список «Активные хендофф-промты» в `docs/README.md`**; контракты Фазы 0 — `03-implementation-notes.md` (через баннер); квирки провайдеров — `docs/experiments/00-provider-quirks.md` (читать ПЕРЕД правкой адаптеров/вызовами). **Язык-данные майнера — вне пайплайна: `internal/lang`+`configs/langpacks/` (D39.16); пар-калибровки — `configs/pairs/<пара>.yaml`, промпты — `prompts/<пара>/<роль>.md` по конвенции (D39.23).** **Подсистемы (`chunk`/`miner`/`checks`/`membank`) не импортируют друг друга и не импортируют `pipeline` — драйвер является композит-корнем (D39.23; проверяется `go list -deps`).** **`.env` не читать.**
*(Актуализировано оркестратором 01.08 по мандату владельца, факты сверены по коду; предыдущая ревизия 17.07 — эры D39.16, до паков 1620 и фазы 2 общности D39.64.)*
*(Актуализировано оркестратором 01.08 по мандату владельца, факты сверены по коду.)*
## Карта пакетов
| Пакет | Что делает | Ключевые файлы |
|---|---|---|
| `cmd/tmctl` | CLI: `translate` / `status` (read-only N/M+паспорта глав+деньги, `--json`; работает ПРИ живом прогоне — store без flock) / `report` (request-log+леджер книги **+ per-run quality-report**: структурный KPI предл./нарратив-абзац, тире, cosmetic-strip/echo-rates, глоссарий-промахи, trust-gated — наблюдаемость, НЕ гейт; D39.2-T4) / `export` (**экспорт-поверхность для полигона**: final_hash→checkpoint→checks.ExportNormalize, manifest-join c pending/ghost-учётом, ConfigDrift-поле, `--plaintext`; D39.5) / `build` (**писатель книги**, строка 236: EPUB 3 + чистый txt из той же экспорт-проекции в `<project_db>.book.<fmt>` рядом с БД, пути — в `artifacts.book_files`; книга с ЛЮБОЙ дырой — pending/withheld/incomplete/ghost — по умолчанию отказ **exit 16** с перечнем, `--partial` пишет с пометкой на первой странице и маркером на каждой дыре; слова читателя — данные `configs/langpacks/<цель>/reader.txt`, вне снапшота; `--format epub,txt`, `--out`) / `redrive` (переатака флагнутых: `--chapter/--chunk/--reason/--dry-run`, D15.3) / `manifest` (**$0-производитель персиста манифеста глав/чанков**, строка 100: нужен ДО первого прогона — дерево глав разобранной, но не запущенной книги; `--json` печатает сам документ) / `migrate` (**$0-write-open без прогона**, строка 174: read-only команды требуют ТОЧНОГО совпадения схемы и не мигрируют, поэтому апгрейд бинаря запирал все существующие книги — платформа зовёт `status --json` перед каждым спавном, а write-команда, которая мигрировала бы, не наступала никогда. Грузит ТОЛЬКО `book.yaml` — ни цен, ни ключей на $0-пути (строка 146); restore point берёт ПОД ЛОКОМ и только когда шаг реально применяется (имя `<метка>-pre-migrate.db` — вне секундного неймспейса платного пути, строка 173); несовпадение схемы у read-only путей — типизированный отказ **exit 13** с машинным токеном `schema_mismatch found=N expected=M`). **ДВА потолка прогона, и они ортогональны.** `--ceiling-usd` (строка 145) — ДЕНЬГИ: перекрывает книжный `ceilings.book_usd` ТОЛЬКО на этот прогон, в book.yaml не пишется, ноль/отрицательное = отказ запуска; каппит КУМУЛЯТИВНУЮ трату книги, а не приращение прогона. `--max-units` (D39.165 §1б) — ОБЪЁМ: не больше N ВЫХОДНЫХ ЮНИТОВ (гранулярность `units_total` манифеста, та самая, в которой платформа продаёт главы) будет ОПЛАЧЕНО этим прогоном. Юниты, которые прогон отдаёт за $0 (резюм, ре-пин), едут бесплатно и потолок не тратят; ретраи и эскалации — тоже нет, они внутри юнита. Принимает только `translate`. **Остановка по объёму — ЗАВЕРШЕНИЕ (exit 0), а не пауза:** словарь кодов выхода не расширялся, различение живёт в отчёте прогона и в логе. Отчёт различает ДОСТАВКУ (юнит, которого не было) и ПЕРЕ-ДЕЛКУ (уже доставленный юнит под сдвинутым снапшотом) — покупка, целиком ушедшая в переделку, обязана читаться как переделка. ⚠ На книге, которая МАЙНИТ банк, вторая покупка требует `--resnapshot` (авто-банк растёт между покупками и двигает edit-снапшот) — прогон предупреждает об этом в логе. main — тонкая обвязка: разбор/exit-коды/.env/рендеры в тестируемых функциях | `main.go`, `invocation.go`, `render.go`, `dotenv.go` |
| `cmd/tmctl` | CLI: `translate` / `status` (read-only N/M+паспорта глав+деньги, `--json`; работает ПРИ живом прогоне — store без flock) / `report` (request-log+леджер книги **+ per-run quality-report**: структурный KPI предл./нарратив-абзац, тире, cosmetic-strip/echo-rates, глоссарий-промахи, trust-gated — наблюдаемость, НЕ гейт; D39.2-T4) / `export` (**экспорт-поверхность для полигона**: final_hash→checkpoint→checks.ExportNormalize, manifest-join c pending/ghost-учётом, ConfigDrift-поле, `--plaintext`; D39.5) / `build` (**писатель книги**, строка 236: EPUB 3 + чистый txt из той же экспорт-проекции в `<project_db>.book.<fmt>` рядом с БД, пути — в `artifacts.book_files`; книга с ЛЮБОЙ дырой — pending/withheld/incomplete/ghost — по умолчанию отказ **exit 16** с перечнем, `--partial` пишет с пометкой на первой странице и маркером на каждой дыре; слова читателя — данные `configs/langpacks/<цель>/reader.txt`, вне снапшота; `--format epub,txt`, `--out`) / `redrive` (переатака флагнутых: `--chapter/--chunk/--reason/--dry-run`, D15.3) / `manifest` (**$0-производитель персиста манифеста глав/чанков**, строка 100: нужен ДО первого прогона — дерево глав разобранной, но не запущенной книги; `--json` печатает сам документ) / `migrate` (**$0-write-open без прогона** (D39.134): единственный путь, доводящий схему проекта до головы бинаря — read-only команды её НЕ мигрируют. Грузит ТОЛЬКО `book.yaml` — ни цен, ни ключей на $0-пути (строка 146); restore point берёт ПОД ЛОКОМ и только когда шаг реально применяется (имя `<метка>-pre-migrate.db` — вне секундного неймспейса платного пути, строка 173); несовпадение схемы у read-only путей — типизированный отказ **exit 13** с машинным токеном `schema_mismatch found=N expected=M`). **ДВА потолка прогона, и они ортогональны.** `--ceiling-usd` (строка 145) — ДЕНЬГИ: перекрывает книжный `ceilings.book_usd` ТОЛЬКО на этот прогон, в book.yaml не пишется, ноль/отрицательное = отказ запуска; каппит КУМУЛЯТИВНУЮ трату книги, а не приращение прогона. `--max-units` (D39.165 §1б) — ОБЪЁМ: не больше N ВЫХОДНЫХ ЮНИТОВ (гранулярность `units_total` манифеста, та самая, в которой платформа продаёт главы) будет ОПЛАЧЕНО этим прогоном. Юниты, которые прогон отдаёт за $0 (резюм, ре-пин), едут бесплатно и потолок не тратят; ретраи и эскалации — тоже нет, они внутри юнита. Принимает только `translate`. **Остановка по объёму — ЗАВЕРШЕНИЕ (exit 0), а не пауза:** словарь кодов выхода не расширялся, различение живёт в отчёте прогона и в логе. Отчёт различает ДОСТАВКУ (юнит, которого не было) и ПЕРЕ-ДЕЛКУ (уже доставленный юнит под сдвинутым снапшотом) — покупка, целиком ушедшая в переделку, обязана читаться как переделка. ⚠ На книге, которая МАЙНИТ банк, вторая покупка требует `--resnapshot` (авто-банк растёт между покупками и двигает edit-снапшот) — прогон предупреждает об этом в логе. main — тонкая обвязка: разбор/exit-коды/.env/рендеры в тестируемых функциях | `main.go`, `invocation.go`, `render.go`, `dotenv.go` |
| `internal/llm` | OpenAI-совместимый транспорт + retry/backoff, **capability-слой** (budget_field/temperature/reasoning per-модель), провайдеры openai/local (no-proxy)/anthropic (DEPRECATED-референс); `failover.go` удалён паком-17 — маршрутизация лейблами живёт в config/pipeline (канал B, один хоп `chain[0]`, fail-closed) | `httpllm.go`, `capability.go`, `provider_*.go` |
| `internal/ledger` | Цены по usage (вкл. reasoning/cache-поля), `PriceForResponse` по фактической модели | `pricing.go` |
| `internal/store` | SQLite (modernc, CGO-free), цепочка миграций `schema_version` (⚠ ALTER-шаги v8+ не идемпотентны вопреки шапке — бэклог-строка 49а), reserve/settle+checkpoint, chunk_status, глоссарий (+подписной цикл терминолога), ruby, retrieval_state, request_log; `OpenReadOnly` — без flock/миграций/recovery для status/report/export (схема не совпала — типизированный `*store.SchemaMismatchError{Found,Expected}`, обе стороны); `Migrate` (+шов `beforeApply` под локом: restore point берётся там) — поверхность деплой-шага строки 174, `SchemaHead` — та самая `Expected`; write-open БД новее бинаря теперь ОТКАЗ, а не тихое открытие | `ledger.go`, `migrate.go`, `glossary.go`, `store.go` |
| `internal/store` | SQLite (modernc, CGO-free), цепочка миграций `schema_version`, reserve/settle+checkpoint, chunk_status, глоссарий (+подписной цикл терминолога), ruby, retrieval_state, request_log; `OpenReadOnly` — без flock/миграций/recovery для status/report/export (схема не совпала — типизированный `*store.SchemaMismatchError{Found,Expected}`, обе стороны); `Migrate` (+шов `beforeApply` под локом: restore point берётся там) — поверхность деплой-шага строки 174, `SchemaHead` — та самая `Expected`; write-open БД новее бинаря теперь ОТКАЗ, а не тихое открытие | `ledger.go`, `migrate.go`, `glossary.go`, `store.go` |
| `internal/config` | fail-fast загрузка models/pipeline/book; **эхо-мина-гейт** `echoMineViolation`; `CheckRunnable` блокирует неисполнимое (C2/fanout/judge); **пар-слой** (D39.23): `configs/pairs/<пара>.yaml` — калибровка нарезки + коридор длины пары; **промпт-резолв КОНВЕНЦИЕЙ** `prompts/<пара>/<роль>.md` из `Book.LangPair()`+роли, **fail-loud на пару без пакета** (ja-книга не едет молча через zh-промпт), осознанное исключение — `prompt_override` на стадии | `models.go`, `pipeline.go`, `pair.go` |
| `internal/text` | Нормализация и рун-примитивы, общие для банка/майнера/чекеров (script-agnostic слой, `lang` НЕ импортирует): `NormalizeSourceKey`/`NormalizeTargetForm` (версионированный артефакт `NormVersion()`, фолдится в снапшот), `NormalizeSource`, `TokenizeScript` (алфавит цели из данных, Mn-фолд U+0301; `TokenizeCyrillic` = ru-дефолт-обёртка), `DenseScript` — единый дом sizing-таксономии (D39.64) | `norm.go`, `runes.go`, `source.go` |
| `internal/seed` | Схема сид-YAML книги (`File`/`Term`/`Decl`/`Alias`) — общий артефакт: майнер её ПИШЕТ (mined-дельта), банк ЧИТАЕТ (`LoadGlossarySeed`); тип общий, поэтому эмиссия не может разъехаться с загрузчиком | `seed.go` |
@ -21,7 +21,7 @@
| `internal/membank` | Банк памяти v2, memmatch-v4: Aho-Corasick, спойлер-окна, disposition, **trust-gated longest-match** (draft-длиннее НЕ съедает вложенный approved; отказ = громкая телеметрия — терм-дрейф закрыт в КОДЕ, D39.2-T1), post-check, рендереры инъекции, загрузка/валидация сида (`SeedLint`) | `memory.go`, `memseed.go`, `mempostcheck.go` |
| `internal/miner` | Детерминированный оффлайн-майнер банка (WS3, $0): `MineBank` над нормализованными чанками + контраст-корпус → `[]Term`, `DeltaYAML` — сид-дельта на подпись владельца; все пар-данные приходят `*lang.Pack` | `miner.go`, `miner_*.go` |
| `internal/checks` | Детерминированные $0-вердикты над текстом чанка, **target-aware ПО ДАННЫМ (D39.64): `Checkers` компилируется из цель-данных, `TargetActive()`/`TargetScriptNonLatin()` гейтят всё — цель без данных инертна, Go-веток по паре нет**: санитайзер v7 (fold-first, CJK/Hangul-leak, CJK-глосс-whitelist) + `ExportNormalize` (**export-contract слой 6**, no-op без цель-данных), cheap-гейты и DC-чекеры (пар-данные из пака), coverage-гейт эксцизии, regression-guard. Флаг-вокабуляр НЕ импортирует: возвращает факт, диспозицию назначает драйвер | `sanitizer.go`, `cheapgates.go`, `checkers.go`, `coverage.go` |
| `internal/pipeline` | Композит-корень (драйвер): сетап `runner.go` → снапшот `snapshot.go` → сид `seeding.go` → цикл книги `bookrun.go` → волны `waverun.go` → петля чанка `chunkrun.go` → стадия `stagerun.go` → эскалация с ре-гейтом `escalation.go` → resume `resume.go`; classify/disposition; **реестр `roleInjectionRenderers`** (новая роль = данные+рендерер, не правка switch); `export.go`/`quality.go`/`status.go` — read-only проекции (общая derivation member-drop = `memberDrops`); **read-out файлы для платформы** (D39.85 — приватный SQLite движка ей закрыт): `manifest.go` (структура глав/чанков + стабильный id главы, строка 100), `bankexport.go` (весь банк тремя статусами, 125), машинная стоп-таблица в `mining.go` (101); все три пишутся через `artifact.go` (write-then-rename — их читают ПОКА идёт прогон) | `bookrun.go`, `waverun.go`, `stagerun.go`, `export.go`, `manifest.go`, `disposition.go` |
| `internal/pipeline` | Композит-корень (драйвер): сетап `runner.go` → снапшот `snapshot.go` → сид `seeding.go` → цикл книги `bookrun.go` → волны `waverun.go` → петля чанка `chunkrun.go` → стадия `stagerun.go` → эскалация с ре-гейтом `escalation.go` → resume `resume.go`; classify/disposition; **реестр `roleInjectionRenderers`** (новая роль = данные+рендерер, не правка switch); `export.go`/`quality.go`/`status.go` — read-only проекции (общая derivation member-drop = `memberDrops`); **read-out файлы для платформы** (D39.85 — приватный SQLite движка ей закрыт): `manifest.go` (структура глав/чанков + стабильный id главы, строка 100), `bankexport.go` (весь банк тремя статусами, 125); оба пишутся через `artifact.go` (write-then-rename — их читают ПОКА идёт прогон) | `bookrun.go`, `waverun.go`, `stagerun.go`, `export.go`, `manifest.go`, `disposition.go` |
| `internal/obs` | trace_id, структурные логи (contextHandler несёт book/chapter/chunk/stage/role из ctx), safego | |
## Инварианты — ЛОМАТЬ НЕЛЬЗЯ (каждый закреплён тестами)
@ -81,21 +81,13 @@ set -a; . ./.env; set +a; TM_LIVE=1 go test -tags live -run TestLive -v ./intern
Флаг переводит отсутствие данных из тихого скипа в громкий `Fatal` — то есть «я это гонял» становится проверяемым. Пути дефолтятся ОТ `$HOME` и от корня репозитория (не от `/home/ubuntu`), так что клон под любым пользователем и по любому пути находит то, что есть; точечные переопределения — `TM_CHECKER_LABELS_DIR`, `TM_MINER_PARITY_{CONTRAST,RECORDS,SEED}`.
Полная батарея на стенде:
```bash
go build ./... && go vet ./... && go vet -tags live ./... && test -z "$(gofmt -l .)" && \
TM_MINER_PARITY=1 TM_CHECKER_LABELS=1 go test ./... -race -count=1
```
**Golden-гард детерминизма** (`golden_test.go` + `testdata/golden/`): пинит бит-в-бит snapshotID, request_hash, wire-тела, вердикты и resume-байты. Красный golden = wire/вердикты изменились = `--resnapshot` = переоплата. Обновлять ТОЛЬКО на ратифицированной смене поведения: `TM_UPDATE_GOLDEN=1 go test ./internal/pipeline/ -run TestGolden`; при re-capture — **маскированный структурный дифф** (хеши/версии → плейсхолдер) обязан быть пустым, если смена версий не меняла вердиктов.
Финал каждой вехи — агентское адверсариальное селфревью (мандат CLAUDE.md 12.07); внешнее ревью — оркестратор.
## Известный техдолг (не трогать молча; ЕДИНЫЙ трекер — таблица «Бэклог» в `docs/PROGRESS.md`, номера строк = она)
- **D15.2:** READ-половина `tmctl export` построена (D39.5); annotation/override-половина и content-addressed resume отложены (строка 49). F3 at-most-once — после D15.2 (строка 50).
- **`Retries` НЕ фолдится в снапшот** (pre-existing, D39.4-LOW; сознательно так же для ручки `RegenerateEchoBeforeEscalate`, D39.64 — дефолт 0 = текущее поведение): в крэш-окне пониженный ретрай-бюджет молча бросает оплаченные OK-чекпоинты.
- ⚠ ALTER-шаги миграций v8+ НЕ идемпотентны вопреки шапке `migrate.go` — полу-применённая БД не сходится (строка 49а).
- Остаточные Cyrillic-хардкоды пар-гейтед DC-чекеров (`checks/repair.go:131`, `checks/checkers.go:457,474`) — инертны без dc-данных пары; кандидат на `c.wordScript` (строка 79).
- Открытый остаток строки 79 — граница ПЛОТНОСТИ письма Go-константой `cjkScriptNames` (`internal/lang/script.go`); Cyrillic-хардкоды словесной границы DC-чекеров ПОГАШЕНЫ — она берётся из данных цели (`isTargetWordLetter`/`c.wordScript`, D39.39/D39.78).
- `prompts/zh-ru/editor-mono.md` — DORMANT-референс D13.1 (не боевой, тестом закреплён); боевой = `prompts/zh-ru/editor.md` v3-discourse (БИЛИНГВ). Глосс-капы санитайзера (6 CJK / 24 лат-токен) — tunable-эвристики (D39.5). Флоры min_max_tokens = схема (D24.3).
- Хвост LOW/NOTE-находок адверсариала D39.4 — в ledger-очереди (строка 53).

View file

@ -6,70 +6,21 @@ guard_hash/verdict-split/fast-path v2) в ledger-очереди ПОСЛЕ сл
resume-путь денежно-критичен, реализация всё-или-ничего. ⚠ Скоуп спеки = ТОЛЬКО content-addressed
resume: `tmctl export` сюда НЕ входит и его READ-половина уже ПОСТРОЕНА отдельно (D39.5,
`internal/pipeline/export.go`); annotation/override-половина экспорта — отдельный отложенный кусок.
Исходный статус v3.1 (для истории):** v3.1 закрывает два ОБЯЗАТЕЛЬНЫХ амендмента D22.2 (§0-бис ниже) + пре-имплементационные
пункты (axis-тест, паритетные не-цели §8, освежение line-refs). Зона: спека в `backend/docs/`
(зона бэкенда); при лендинге оркестратор сворачивает её в
Зона: спека в `backend/docs/` (зона бэкенда); при лендинге оркестратор сворачивает её в
`docs/architecture/03-implementation-notes.md` новым разделом.
## 0-бис. v3.1-амендменты (D22.2 два обязательных + пре-имплементационные)
**Родословная — в НОТАХ, а не здесь.** Направление v1+v2 и три правки v3 ратифицированы **D20** п.1
(расщепить `snapshotID` на wire/вердикт-части; ключ чекпоинта — на wire-идентичности; вердикт-смену
re-classify-ить на resume бесплатно); ответы на три вопроса, которые спека держала открытыми
(пер-стадийная гранулярность · порог и флаг согласия · судьба `Adult`) — **D20** п.2; два ОБЯЗАТЕЛЬНЫХ
амендмента v3.1 (`SourceLang`/`TargetLang``verdictSnapshotID`; расщепление `cache_ttl`) и
пре-имплементационные пункты (axis-тест `RequestHash`, «паритетные не-цели» §8) — **D22** п.2. Тела: D20 — слайс
`docs/archive/architecture/05-decisions-D1-D38.md`, D22 — живой журнал решений. Каждая правка помечена в
тексте НИЖЕ словами «правка D20.1(a/b/c)» и «(v3.1-a/b)» — читать их надо там, где они действуют.
Ревью D22.2 сконструировало и ИСПОЛНИЛО stale-hit, который v3 пропускал. Два обязательных
амендмента до реализации:
- **(v3.1-a) `Book.SourceLang`/`Book.TargetLang``verdictSnapshotID`.** Языки — ВЕРДИКТ-входы,
потребляемые ВНЕ рендера: `classify` использует `TargetLang` для cjk-echo-проверки
(`disposition.go:196` `isCJKTarget`), `coverageCheck` использует пару `SourceLang`/`TargetLang`
для коридора `len_ratio` (`chunkrun.go:56`). `editor.md` НЕ рендерит `{{target_lang}}` (после
флипа D30.1 билингв-редактор рендерит `{{source_lang}}`, но не target) → на editor-стадии
`content_hash` их НЕ ловит, а вердикт (cjk/coverage) от них зависит. Сегодня они в ключе через
`brief_hash` (book-поле), но v3 дропает `brief_hash``content_hash`; значит нужен ЯВНЫЙ
вердикт-фейт. **Правило §4 переформулировано: судьбу обязано получить ЛЮБОЕ brief-поле,
потребляемое ВНЕ рендера** (языки — в `verdictSnapshotID`; инцидентально они и в translator
`content_hash` через рендер, но вердикт-зависимость — несущая). §4-строка добавлена.
- **(v3.1-b) `cache_ttl`-расщепление: `Context.CacheTTL` — advisory (НЕ в `wireSnapshotID`);
`prov.CacheTTL` — в `wireSnapshotID`.** Проверено кодом: `clients.go:38` передаёт клиенту
`CacheTTL: prov.CacheTTL` (провайдер-уровень, models.yaml, Anthropic-kind cache_control) —
**`Context.CacheTTL` (pipeline `context.cache_ttl`, «5m») НЕ доходит НИ ДО ОДНОГО клиента =
wire-ИНЕРТЕН.** v3 клал в `wireSnapshotID` инертный `Context.CacheTTL` и терял wire-влияющий
`prov.CacheTTL` — задом наперёд. v3.1: `Context.CacheTTL` демотирован до advisory (response-
нейтрален, reuse безопасен; НЕ пиним); `prov.CacheTTL` (пер-стадийно через резолвнутый провайдер)
`wireSnapshotID` + `guard_hash` (меняет cache_control на Anthropic-проводе). Anthropic снят со
стека (models.yaml) → экспозиция нулевая, но механизм чист. §4-строки поправлены.
**Пре-имплементационные (D22.2):** (1) axis-sensitivity тест `RequestHash` — флип `role`/`stage`
меняет хеш (сейчас мутация «удалить role» пережила бы сьют) → §12.9 тест-лист (`г-тер`);
(2) абзац «паритетные не-цели» §8 (classify-Source для editor-строк; `Retries`/`Escal.BudgetUSD`
вне хешей; транспорт-ретраи вне ключа); (3) line-refs освежены — **⚠ рефактор D23 расщепил
`runner.go` на `stagerun.go`/`chunkrun.go`/`escalation.go`/`bookrun.go`/`resume.go`/`status.go`;
исторические `runner.go:NNN`-ссылки ниже ИНДИКАТИВНЫ (по классу кода), актуальные локации —
в §12-плане и по grep имени функции.**
**Supersedes:** v2 (направление v1+v2 ратифицировано D-лог §Ратификации 09.07 п.2 и D20.1: расщепить
`snapshotID` на wire/вердикт-части; ключ чекпоинта — на wire-идентичности; вердикт-смену
re-classify-ить на resume бесплатно; три дыры v1 закрыты v2). **v3 закрывает три правки D20.1**
(«направление и 90% деталей ратифицированы; реализация разблокируется после v3-правок»):
- **(a)** guard_hash **обязан нести `role` + имя стадии** — без них формула НЕ суперсет текущей
защиты: конструируемый stale-VERDICT fast-path hit при флипе роли (клейм «строго сильнее» был
ложен). → §5, контрпример + §3.3/§3.4 формула.
- **(b)** dry-run §7 нуждается в **аналитическом правиле editor-каскада**: любая стадия НИЖЕ
re-bill-юнита того же чанка сама считается re-bill (иначе консент-проекция занижает ~2× на самом
дорогом классе правок — temp/reasoning/model translator'а, чей новый вывод $0-dry-run
материализовать не может). → §7 п.1-бис.
- **(c)** полевой маппинг §4 **доукомплектован**: `style_check_version`; слой YoPolicy/StyleAllowlist;
target-часть `memoryNormVersion` (→ и в postcheck-версию); судьба `jobs.snapshot_id`; verdict-фолд
конфига гейтов только при enabled. → §4, §6, §9.
**Ответы D20.2 вписаны в текст v3:** Q1 — пер-стадийная гранулярность `wireSnapshotID`/`guard_hash`
**ПОДТВЕРЖДЕНА** (§3.1); Q2 — порог `min($0.50, 5%×ProjectedBookUSD)` + флаг `--accept-rebill[=usd]`
(опциональный потолок суммы) + fallback-флор при `ProjectedBookUSD=0` **ПРИНЯТЫ**, но фикс каскада
(b) — ПЕРВЫМ (§7); Q3 — `Adult` остаётся wire/вердикт-НЕЙТРАЛЬНЫМ, слой не назначается: вместо хеша
**load-time adult-линт** (реализован бэкенд-пакетом №3: `Pipeline.CheckAdultChannel`; §4, §13).
**v2-контекст (сохранён):** v2 закрыл три дыры внешнего ревью — (a) fast-path-гард слабее текущего →
колонка `guard_hash`; (b) расщепление `memory_version` сделано ОБЯЗАТЕЛЬНЫМ; (c) loud-гейт согласия
заменён pre-flight dry-run. v3 доводит (a) до суперсета (role+stage), (b) до полноты (target-norm в
postcheck), (c) — маппинг §4 доукомплектован и каскад в проекции аналитический.
**Line-refs вида `runner.go:NNN` ниже ИНДИКАТИВНЫ (по классу кода):** рефактор D23 расщепил `runner.go`
на `stagerun.go` / `chunkrun.go` / `escalation.go` / `bookrun.go` / `resume.go` / `status.go`; актуальные
локации — в §12-плане и по grep имени функции.
**Гейтит:** онгоинг-режим (`add-chapters`, сегмент «ИИ-фабрик», Р9 / D15 п.2). Текущий
all-or-nothing resnapshot остаётся **приемлемым для статичной приёмочной книги Ф1 (D15.1 / D18 蛊真人)
@ -185,7 +136,7 @@ jsonOnly, maxTokens, wireSnapshotID, msgs)`. Замена: `snapshotID` → `wir
| Поле (сегодня) | Класс | Куда в v2 | Почему |
|---|---|---|---|
| `brief_hash` | wire | **dropped**`content_hash` | Метаданные книги рендерятся в шаблон (`render.go:108-121`) → в `msgs`; per-chunk `content_hash` ловит правку брифа гранулярно. **Правило (v3.1-a): ЛЮБОЕ brief-поле, потребляемое ВНЕ рендера, обязано получить явную судьбу** (`content_hash` его не ловит). Назначено: `SourceLang`/`TargetLang``verdictSnapshotID` (cjk-gate/coverage-коридор — строка ниже, v3.1-a); `YoPolicy`/`StyleAllowlist``verdictSnapshotID` (кормят стиль-флаггер); `Adult` (`book.go`) остаётся **wire/вердикт-НЕЙТРАЛЬНЫМ** (D20.2-Q3): слой не назначается, вместо хеша — **load-time adult-линт** `Pipeline.CheckAdultChannel` (реализован пакетом №3: `adult:true` без `channel:adult`-стадии → fail-loud). Первый рантайм-потребитель `Adult` (если появится) обязан ЯВНО вернуть его в `wireSnapshotID`/`verdictSnapshotID` — §13-Q3. |
| `brief_hash` | wire | **dropped**`content_hash` | Метаданные книги рендерятся в шаблон (`render.go:108-121`) → в `msgs`; per-chunk `content_hash` ловит правку брифа гранулярно. **Правило (v3.1-a): ЛЮБОЕ brief-поле, потребляемое ВНЕ рендера, обязано получить явную судьбу** (`content_hash` его не ловит). Назначено: `SourceLang`/`TargetLang``verdictSnapshotID` (cjk-gate/coverage-коридор — строка ниже, v3.1-a); `YoPolicy`/`StyleAllowlist``verdictSnapshotID` (кормят стиль-флаггер); `Adult` (`book.go`) остаётся **wire/вердикт-НЕЙТРАЛЬНЫМ** (D20.2-Q3): слой не назначается, вместо хеша — **load-time adult-линт** `Pipeline.CheckAdultChannel` (реализован пакетом №3: `adult:true` без `channel:adult`-стадии → fail-loud). Первый рантайм-потребитель `Adult` (если появится) обязан ЯВНО вернуть его в `wireSnapshotID`/`verdictSnapshotID` (D20 п.2, Q3). |
| `chunker_version` | wire | **dropped**`content_hash` | Правила чанкера дают `ch.Text``msgs`. Ре-чанкинг меняет границы/число чанков → `content_hash` почти всех → честный broad re-bill, гранулярно. |
| `estimator_version` | wire (деривация) | **guard_hash** (не в `RequestHash`) | Деривирует `maxTokens`, а фактический `maxTokens` — уже прямое поле `RequestHash`. В ключе избыточен; fast-path нуждается — кладём в `guard_hash`. Бамп busts fast-path каждого чанка; платят только те, чей `maxTokens` реально сменился (остальные — checkpoint-hit, re-classify $0). |
| `max_tokens_policy` | wire (деривация) | **guard_hash** | Как `estimator_version`, для attempt≥1 (`maxTokensForAttempt`). Избыточен в ключе (фактический `maxTokens` там), нужен fast-path-у. |
@ -410,12 +361,10 @@ job-snapshot-mismatch из fail-loud в пер-чанковое `content_hash`/`
без явного флага `--accept-rebill[=usd]`**: форма с ОПЦИОНАЛЬНЫМ потолком суммы (`--accept-rebill`
принимает проецируемый re-bill целиком; `--accept-rebill=1.50` — только если проекция ≤ $1.50, иначе
стоп: Р6 = согласие на КОНКРЕТНУЮ трату, не бланкетное). Гейт content-scoped: срабатывает только на
РЕАЛЬНУЮ проецируемую трату, а не на любой вердикт-only $0-change. `--resnapshot` остаётся
алиасом-синонимом (семантика — «прими проецируемый re-bill», не «пере-пинай всё»).
РЕАЛЬНУЮ проецируемую трату, а не на любой вердикт-only $0-change.
Это сохраняет Р6-класс согласия на трату, убирая ложные тревоги (вердикт-only смена больше не просит
согласия — она $0). `--resnapshot` как флаг остаётся синонимом-алиасом для обратной совместимости, но
его семантика — «прими проецируемый re-bill», а не «пере-пинуй всё».
согласия — она $0).
## 8. Resume: легальность reuse и безопасность re-classify (F1)
@ -582,38 +531,3 @@ per chunk×stage, а не рассуждает аналитически):**
каскада» роняет проекцию до ~½);** (е) миграция: онгоинг-книга под v3 не пере-пинается на `add-chapters`;
**(ж, v3.1-b) `Context.CacheTTL` вне `wireSnapshotID` (правка «5m»→«1h» не re-bill-ит); `prov.CacheTTL`
в `wireSnapshotID` (правка re-bill-ит Anthropic-стадию).**
## 13. Решённые вопросы (ответы D20.2 — вписаны в текст v3)
Все три §13-вопроса v2 РЕШЕНЫ D20.2; сохранены здесь как запись контракта.
1. **Пер-стадийная гранулярность `wireSnapshotID`/`guard_hash` — ПОДТВЕРЖДЕНА** (D20.2-Q1). Живой
довод оркестратора: вероятная смена редактора glm-5→grok-4.3 при book-global пере-оплатила бы весь
translator-бэклог; поверхность мала — `snapshotID` уже итерирует стадии. Взято пер-стадийно (§3.1).
2. **Порог dry-run и флаг — ПРИНЯТЫ** (D20.2-Q2) с поправками: порог `min($0.50, 5%×ProjectedBookUSD)`,
флаг `--accept-rebill[=usd]` (опциональный потолок суммы, отдельный от `--resnapshot`-алиаса),
fallback-флор при `ProjectedBookUSD=0`. ⚠ Порядок: **фикс каскада §7.1-бис ПЕРВЫМ** (иначе порог
считается от заниженной вдвое суммы). Форма вписана в §7 п.3 / §12 п.8.
3. **`Adult` остаётся wire/вердикт-НЕЙТРАЛЬНЫМ, слой НЕ назначается** (D20.2-Q3). Вместо хеша —
**load-time линт консистентности**: `adult:true` без единой `channel:adult`-стадии → громко. Линт
**реализован бэкенд-пакетом №3** (`Pipeline.CheckAdultChannel`, вызывается в `NewRunner`; обратная
сторона — `channel:adult` при `adult:false` — сознательно НЕ гейтится, Adult нейтрален до первого
рантайм-потребителя, который обязан ЯВНО назначить слой). §4 (строка `brief_hash`).
---
**Резюме для оркестратора.** Дефект — `snapshotID` в ключе вызова смешивает wire-, вердикт- и
избыточные входы. Фикс: три хеша на трёх слоях — `wireSnapshotID` (пер-стадийный, в `RequestHash`),
`verdictSnapshotID``chunk_status`, не в ключе), `guard_hash` (пер-стадийный суперсет wire-плана,
гейт fast-path). Три дыры v2 закрыты + **три правки v3 (D20.1)**: **(a)** `guard_hash` покрывает
прямые wire-поля (temp/reasoning/maxTokens/escalate) **И `stageName`+`role`** → строгий суперсет, нет
ре-открытия stale-serve ни на wire-, ни на вердикт-оси (контрпример с флипом роли — §5); **(b)**
`memory_version` расщеплён ОБЯЗАТЕЛЬНО — inject-часть в `content_hash`, post-check+gate+decl+**target-norm**
`verdictSnapshotID` (пересчёт бесплатен); dry-run §7 несёт **аналитическое правило editor-каскада**
(любая стадия ниже re-bill-юнита = re-bill, иначе консент занижен ~2×); **(c)** loud-гейт согласия
заменён pre-flight dry-run «N чанков, ~$X» с порогом `min($0.50, 5%×ProjectedBookUSD)` и флагом
`--accept-rebill[=usd]` (Р6 сохранён); маппинг §4 доукомплектован (`style_check_version`,
YoPolicy/StyleAllowlist, `jobs.snapshot_id`, coverage-фолд-при-enabled); `Adult` нейтрален — линт
`CheckAdultChannel` (уже в пакете №3). Безопасность F1 усилена. Онгоинг-мина D15 снята: append
re-bill-ит только реально затронутые чанки×стадии. Реализация мала и хирургична, но **остаётся
ЗАБЛОКИРОВАННОЙ до ратификации v3** (реализацию НЕ начинать без подписи оркестратора — D20.1).

View file

@ -7,14 +7,9 @@
> ниже; она сужает полномочие, а не расширяет его.
> ⚠ Честная цена, названная при ратификации: **закон загейчен примерно на 40%** — см. §7.
>
> Ниже — текст дизайна, каким он уехал на ратификацию, с поправками, снятыми адверсариальным проходом и
> оркестратором. Прежняя шапка:
> **ДИЗАЙН, НЕ НОРМА.** Написан бэкенд-сессией `textmachine-main-8a` по промту
> `docs/BACKEND_MONEY_HONESTY_SESSION_PROMPT.md` §1. **Сессия его НЕ ратифицирует.** Уезжает пингом
> оркестратору №21 → владельцу. До ответа правки классов A0/A2/A3/A4/A5/A6/A7/A8/A10 не вносятся.
>
> ⚠ **Каждый факт ниже снят ИСПОЛНЕНИЕМ на HEAD `9d27c0b`**, командой, названной рядом. Где я
> опровергаю промт — говорю это прямо и показываю чем.
> Написан бэкенд-сессией `textmachine-main-8a` (промт — `docs/archive/prompts/BACKEND_MONEY_HONESTY_SESSION_PROMPT.md` §1),
> ратифицирован владельцем через оркестратора №21.
> ⚠ **Каждый факт ниже снят ИСПОЛНЕНИЕМ на HEAD `9d27c0b`**, командой, названной рядом.
---
@ -57,7 +52,7 @@
статье приёмке проще всего: она не вводит новой философии, а даёт имя и гейт тому, что зона уже признала
правильным на одном поле и не распространила на остальные.
**Не больше того, и первая редакция здесь себе польстила (снято адверсариальным проходом).** У §2.1 и
**И не больше того — разделение своего и заимствованного (ратифицировано, D39.181 §2).** У §2.1 и
§2.2 родословная другая — норма «unknown, not zero» и дисциплина причин; §2.4 и §2.5 родословной в этом
коде НЕ имеют вовсе, это НОВЫЕ нормы, и приёмка обязана судить их как новые. ⚠ И сам `RebillBasis` богаче
§2.3: он ЧЕТЫРЁХзначен, и три его значения (`pending`/`stored`/`none`) означают «факт известен», различая
@ -124,13 +119,12 @@ OutcomeOf reads off an exit code. The two are one vocabulary because they are on
twice.»* Значения помечены номерами: `clean`=0 · `failed`=1 · `flagged`=2 · `bank_stop`=3 ·
`ceiling`=4 · `stopped`=5.
**ПОПРАВКА К МОЕЙ ЖЕ ПЕРВОЙ ФОРМУЛИРОВКЕ, снята адверсариальным проходом и проверена мной.** Я написала
«пара БИЕКТИВНА, новое значение её разваривает». Это НЕВЕРНО: `OutcomeRefused` уже существует и
counterpart'а в потоке НЕ имеет — та же константа, дословно: *«OutcomeRefused is the band, and it has no
**Довод «пара БИЕКТИВНА» НЕВЕРЕН и не годится** (D39.181 п.2 стоит на другом): `OutcomeRefused` уже
существует и counterpart'а в потоке НЕ имеет — та же константа, дословно: *«OutcomeRefused is the band, and it has no
`finished` counterpart: an invocation that was turned down did no work, so the engine writes no verdict
about work»*. Асимметрия уже узаконена, и аргумент «так нельзя, потому что нельзя» падает.
**Точный аргумент — другой, и он сильнее.** Асимметрия сегодня односторонняя: у КАЖДОГО значения
**Точный аргумент — другой.** Асимметрия сегодня односторонняя: у КАЖДОГО значения
`finished.outcome` есть код-двойник, а у кодовой стороны есть одно лишнее. Значение, живущее ТОЛЬКО в
потоке, было бы первым в обратную сторону — и тогда для ОДНОГО прогона два канала назвали бы РАЗНЫЙ
исход: поток сказал бы `volume`, а `OutcomeOf(0)` на том же прогоне — `clean`. Это ровно то, что
@ -175,7 +169,7 @@ about work»*. Асимметрия уже узаконена, и аргумен
*Экземпляры: A7 · A2 · **A12 (одиннадцатый, §6)**.*
**§2.4 — КАНАЛ НАЗНАЧАЕТ ПОТРЕБИТЕЛЬ, А НЕ УДОБСТВО ПИШУЩЕГО.**
**Отношение к §2.1 названо, а не оставлено читателю (снято адверсариальным проходом):** §2.4 — не
**Отношение к §2.1 названо, а не оставлено читателю:** §2.4 — не
независимая обязанность, а ПРОЦЕДУРА исполнения §2.1. §2.1 говорит «факт обязан быть на канале своего
потребителя»; §2.4 говорит, КАКОЙ это канал и как его выбрать. Строка корпуса нарушает §2.1 всегда, когда
нарушает §2.4; отдельный номер у §2.4 есть потому, что нарушают его по-разному — молчанием (§2.1) или
@ -203,8 +197,8 @@ about work»*. Асимметрия уже узаконена, и аргумен
| **НОВОЕ СЛОВО в замкнутом словаре** | значение `Outcome` · тип кадра · значение `flag_reason`, пересекающее шов · код выхода ВНЕ полосы (05) | ратификация ВЛАДЕЛЬЦА |
| **НОВЫЙ КЛАСС ОТКАЗА ВНУТРИ полосы 1019** | новая константа в `refusalExit` + `pipeline.RefusalClass`; незнакомый номер у потребителя поглощается ЧЛЕНСТВОМ в полосе и читается «refused», а не «failed» — полоса спроектирована ровно под это | ратификация ОРКЕСТРАТОРА (прецедент — код 13) |
**ПОПРАВКА, снята адверсариальным проходом и проверена мной.** Первая редакция валила все коды выхода
в один класс «слово владельца». Это неверно для ПОЛОСЫ: `platform/internal/ingest/exit.go` дословно —
**Коды выхода НЕ валятся в один класс «слово владельца» — ПОЛОСА растёт иначе:**
`platform/internal/ingest/exit.go` дословно —
*«It is a BAND and not a list because the vocabulary is the engine's and it will grow. A consumer looks the
number up; a class this build has never heard of still lands inside the band and reads as "refused"»*, а
`17-seam-inbound-law.md` называет прецедент: код **13** встал в полосу «без нового решения ВЛАДЕЛЬЦА»,
@ -274,8 +268,7 @@ number up; a class this build has never heard of still lands inside the band and
`classifierVersion`, а он — поле снапшота (`snapshot.go`, греп `ClassifierVersion versions the intrinsic`)
⇒ ярус B (и ровно это записано ценой в строке бэклога 105); `degraded=sanitizer_stripped` у редактора — 1 строка, $0.01865424,
текст **ОТГРУЖЕН**; и лишь $0.09531838 действительно выброшено. **Наивная поверхность назвала бы оператору
на 29.2% больше потерь, чем было** ($0.12316089 против истинных $0.09531838; ⚠ испр. — первая
редакция делила на НАИВНУЮ цифру и получала 22.6%, что отвечает на другой вопрос) — то есть сама была бы экземпляром §2.2.
на 29.2% больше потерь, чем было** ($0.12316089 против истинных $0.09531838) — то есть сама была бы экземпляром §2.2.
⇒ Разложение обязано печатать не «впустую», а КЛАССЫ: `отброшено · отгружено-с-флагом · мис-вердикт`.
### A2 — фаза стартует, зная, что не влезает
@ -284,11 +277,11 @@ number up; a class this build has never heard of still lands inside the band and
* **Делает:** печатает `estimate_usd` и `budget_usd` рядом, следующий оператор — проверка ошибки ЧТЕНИЯ
бюджета, **сравнения нет**; обрывается на середине пер-батчевым гейтом, оставив оплаченную частичную работу.
* **Нарушено: §2.3.** Частичный результат подаётся как результат, без базиса «пасс оборван бюджетом».
**Поправка, снятая адверсариальным проходом и проверенная мной: §2.5 здесь НЕ нарушен.** §2.5 —
**§2.5 здесь НЕ нарушен.** §2.5 —
правило ПОРЯДКА ОТЧЁТА, а фаза как раз отчитывается: пер-батчевый гейт печатает WARN, НАЗЫВАЮЩИЙ
необратимую трату (`spent_usd`, `next_batch_usd`, `batches_left`), и только потом `break` — ошибка не
возвращается, отчёт не подавляется. То, что мне хотелось назвать §2.5, — это «решение принято позже,
чем могло быть», а это другое правило, и его в законе нет. **Заводить его я не стала: одна строка
возвращается, отчёт не подавляется. Похожее на §2.5 здесь — «решение принято позже, чем могло быть»,
а это ДРУГОЕ правило, и его в законе нет. **Заводить его я не стала: одна строка
корпуса — недостаточное основание для пятой обязанности, и честнее оставить дыру названной.**
* **Потребитель:** оператор (поднять бюджет) + деньги.
* **Канал:** существующий — план фазы усекается до влезающего ДО первого вызова, и строка называет
@ -339,16 +332,14 @@ number up; a class this build has never heard of still lands inside the band and
`prompt_sha256`, `model`, `temperature`, `reasoning` и все прочие побайтно равны.
Гард сказал бы «конфиг/промпты изменились» — и это ложь, доказанная его же хранилищем.
* **Нарушено:** §2.2.
* **Потребитель — ДВОЕ, и это поправка к моей первой редакции («только оператор»), снятая адверсариальным
проходом и проверенная мной по реестру платформы.** `PD-422` дословно: движковый джоб-гард останавливает
* **Потребитель — ДВОЕ, не «только оператор»** (проверено по реестру платформы). `PD-422` дословно: движковый джоб-гард останавливает
прогон, и платформа видит **`exit 1``failed`** — то есть ложную причину читает не только человек,
причём резолвится она как «инфра-сбой», а не как «нужен `--resnapshot`». ⚠ Строка помечена там же как
сегодня БЕСПРЕДМЕТНАЯ: проводка `--max-units` на платформе загейчена, и вторая покупка этой формы пока
не возникает.
* **Канал:** оператору — текст гарда (stderr, класс «новое число»); платформе — отдельный вопрос, и он НЕ
этого пака: её половина уже заведена строкой `PD-422` в чужой зоне.
* ⚠ **Ловушка для исполнителя, снятая адверсариальным проходом и проверенная мной по коду:** готового
дифа полей НЕТ, вопреки моей первой формулировке. `classifySnapshotMove` (`repin.go`) ходит по КАРТЕ и
* ⚠ **Ловушка для исполнителя (проверена по коду):** готового дифа полей НЕТ. `classifySnapshotMove` (`repin.go`) ходит по КАРТЕ и
делает `return moveOther` на ПЕРВОМ несовпавшем ключе — он не собирает МНОЖЕСТВО разошедшихся полей, а
какой ключ встретится первым, у Go-мапы недетерминировано. Диф для сообщения обязан быть НОВЫМ и
ДЕТЕРМИНИРОВАННЫМ (сортировка ключей), а не этим циклом.
@ -375,8 +366,8 @@ number up; a class this build has never heard of still lands inside the band and
пару «код выхода ↔ кадр», а кода заводить нельзя. Числа словаря не имеют. Форма:
кадр `finished` получает поля леджера доставки (`max_units · delivered · reworked · flagged · free ·
left_fresh · left_rework`).
**ПОПРАВКА, снятая адверсариальным проходом и проверенная мной по коду.** Я предложила потребителю
выводить стоп предикатом `delivered+reworked == max_units && left_fresh+left_rework > 0`. **Он ЛОЖЕН
**ЛОВУШКА ПОТРЕБИТЕЛЯ (проверена по коду).** Напрашивающийся предикат
`delivered+reworked == max_units && left_fresh+left_rework > 0` **ЛОЖЕН
ровно на том прогоне, ради которого A5 заведён:** `reconcile()` (`volume.go`) ПОСЛЕ волн переносит
оплаченный, но флагнутый юнит ИЗ `Delivered`/`Reworked` в `Flagged`, а улика A5 — это `--max-units 3`
с одним флагнутым чанком, где `delivered+reworked = 2 ≠ 3`. Потребитель прочёл бы объёмный стоп как
@ -451,14 +442,14 @@ number up; a class this build has never heard of still lands inside the band and
при сдвиге снапшота реально пере-покупаются.
* **Нарушено:** §2.1.
* **Потребитель:** оператор (согласие) + деньги.
* ⛔ **Канал — НЕ тот, который я назвала первой редакцией, и это проверено на данных.** Я написала
«существующие `projected_book_usd` / `rebill_*`». Обе фигуры считаются ИЗ `chunk_status`
* ⛔ **Канал — НЕ «существующие `projected_book_usd` / `rebill_*`», и это проверено на данных.** Обе
фигуры считаются ИЗ `chunk_status`
(`projectRebill` принимает `statuses []store.ChunkStatus`), а `chunk_status` этого прогона содержит
РОВНО draft(20) + edit(3) строк и НИ ОДНОЙ терминологической, тогда как `checkpoints` держит 9
терминологических. ⇒ контур $0.04980482 для этих носителей **структурно невидим**, и положить его туда
нельзя, не сменив саму деривацию. **Носитель придётся строить от `checkpoints ⋈ jobs`** (там роль и
стадия есть) — то есть A10 дороже, чем «новое число», и это надо знать ДО планирования правки.
* **Канал (исправлено):** новая деривация поверх `checkpoints ⋈ jobs`, публикуемая рядом с
* **Канал:** новая деривация поверх `checkpoints ⋈ jobs`, публикуемая рядом с
`projected_book_usd`.
* **Класс правки:** новое число (сессия вправе). ⚠ Замер этого прогона: терминологический контур —
$0.04980482 из $0.43610966 = **11.42% захода**, и ни цента из этого в проекцию не входило.
@ -545,11 +536,9 @@ a CLASS it knows, never on band membership». Платформенная коп
**Диспозиция:** правка платформенного файла — ЧУЖАЯ ЗОНА, я её не трогаю. Уезжает пингом.
⚠ **И честная оговорка о его отношении к ЗАКОНУ, снятая адверсариальным проходом и мной принятая:
A15 — НЕ экземпляр закона.** Ст. 1 определяет подотчётный факт как ЗНАЧЕНИЕ, которое движок вычислил или
**И честная оговорка о его отношении к ЗАКОНУ: A15 — НЕ экземпляр закона.** Ст. 1 определяет подотчётный факт как ЗНАЧЕНИЕ, которое движок вычислил или
выводит из своих строк; «два рукописных комментария о словаре разъехались» — не значение, не выводится ни
из одной строки и не имеет канала. Первая редакция назвала его «экземпляром §2.4 на уровне словаря» — это
натяжка, и я её снимаю. **A15 остаётся находкой первого класса и НЕ становится доводом за закон.** Что из
из одной строки и не имеет канала (натяжка «экземпляр §2.4 на уровне словаря» снята). **A15 остаётся находкой первого класса и НЕ становится доводом за закон.** Что из
него следует для закона: у Г1 обязана быть сверка с ЗЕРКАЛОМ чужого словаря (§7), иначе следующий разъезд
будет так же тихим, — но это требование к гейту, а не подведение факта под статью.
@ -598,11 +587,10 @@ translation»*, и `LogRequest` — *«insert, WARN on failure, never propagate
**Почему рефлексия, а не анализатор:** список полей опубликованного документа — это в точности набор
json-тегов его типа, и рефлексия читает его без единой эвристики.
⛔ **И ЧЕГО РЕФЛЕКСИЯ НЕ УМЕЕТ — названо, потому что первая редакция этого не сказала и гейт читался
сильнее, чем он есть (снято адверсариальным проходом, проверено мной):**
⛔ **ЧЕГО РЕФЛЕКСИЯ НЕ УМЕЕТ — названо, иначе гейт читается сильнее, чем он есть:**
* **рефлексия перечисляет ПОЛЯ названного типа, но не ТИПЫ.** Список документов остаётся РУЧНЫМ, а
«кто-то забыл дописать тип» — та же тихая дыра, ради которой гейт заводится. Первая редакция это уже и
продемонстрировала: её собственный перечень пропустил `PhaseProgress`, `WaveCounter` и `ManifestChapter`;
«кто-то забыл дописать тип» — та же тихая дыра, ради которой гейт заводится (первый же перечень этого
дока пропустил `PhaseProgress`, `WaveCounter` и `ManifestChapter`);
* **payload'ы потока спрятаны за `Envelope.Data json.RawMessage`** — обход по `Envelope` не видит НИ ОДНОГО
payload'а, а НОВЫЙ тип кадра (ровно туда садится рекомендация A5) невидим полностью;
**честная форма Г1: реестр перечисляет ТИПЫ руками, а рефлексия проверяет ПОЛЯ каждого.** Ручная
@ -621,8 +609,7 @@ json-тегов его типа, и рефлексия читает его бе
Для каждого поля payload'а `buildSnapshotID`, КОТОРОЕ ФИКСТУРА МОЖЕТ СДВИНУТЬ: сдвинуть ровно его
снять сообщение гарда → утверждать, что оно НАЗЫВАЕТ это поле. Это машинная форма §2.2.
⚠ **Оговорка, без которой гейт описан сильнее, чем он есть (снята адверсариальным проходом, проверена
мной):** «~30 плоских осей» — неверно дважды. Payload — ДЕРЕВО (`contextSnap` · `segmentationSnap` ·
**Оговорка, без которой гейт описан сильнее, чем он есть:** «~30 плоских осей» — неверно дважды. Payload — ДЕРЕВО (`contextSnap` · `segmentationSnap` ·
`coverageSnap` · `sanitizerSnap` · `banknoteSnap` · `repairSnap` · `[]stageSnap`), и заметная часть его
листьев — КОДОВЫЕ КОНСТАНТЫ (`chunker_version` · `estimator_version` · `max_tokens_policy` ·
`classifier_version` · `style_check_version` · `render_format_version` · `embedded_version` ·
@ -641,8 +628,8 @@ json-тегов его типа, и рефлексия читает его бе
### ⛔ ГЛАВНОЕ ОГРАНИЧЕНИЕ ГЕЙТА, и первая редакция его не назвала
**Гейты покрывают НЕ ТЕ каналы, в которые закон маршрутизирует большинство корпуса.** Снято
адверсариальным проходом, проверено мной сверкой §5 против §7: шесть строк из десяти закон отправляет в
**Гейты покрывают НЕ ТЕ каналы, в которые закон маршрутизирует большинство корпуса** (сверка §5 против
§7): шесть строк из десяти закон отправляет в
канал, за которым не стоит НИ ОДИН из четырёх гейтов — A0 → секция `report`, A2 → строка об усечении,
A3(а) → отчёт + WARN, A3(б) → строка сборки, A4 → текст гарда (stderr), A8 → репо-гейт (свой собственный).
Г1/Г2 стерегут JSON-структуры и колонки БД, Г3 — одну ось одного сообщения.
@ -650,8 +637,7 @@ A3(а) → отчёт + WARN, A3(б) → строка сборки, A4 → те
**И ЧЕТВЁРТЫЙ ГЕЙТ НАЗЫВАЮ НЕПОСТРОЕННЫМ ВСЛУХ, потому что Ст. 4 этого требует буквально.** **Г4
(анализатор «необратимое до ошибки») НЕ ПОСТРОЕН** — он спроектирован и отложен, и §2.5 остаётся
**НЕЗАГЕЙЧЕННОЙ статьёй**: сегодня её держит одна точечная посадка на `build` (A3а), а не машина, которая
поймала бы СЛЕДУЮЩИЙ экземпляр в другом месте. Первая редакция этого абзаца сказала «Г4 предложен
последним» — это не то же самое, что «не построен», и приёмка справедливо потребовала назвать прямо.
поймала бы СЛЕДУЮЩИЙ экземпляр в другом месте.
Статус на 31.08: **Г1 не построен · Г2 не построен · Г3 построен точечно (A4, по снапшотной оси) ·
репо-гейт A8 построен · Г4 не построен.**
@ -684,19 +670,10 @@ A3(а) → отчёт + WARN, A3(б) → строка сборки, A4 → те
что закон и предписывает. Условие — «ни одна фигура, которую читает платформа, не смеет сменить смысл» —
выполнено и предъявлено тестом (`committed_usd` остаётся `SUM(checkpoints)` до микро-доллара).
### Прежний текст пункта (как уезжал)
1. **A5 — поля леджера доставки в кадре `finished`** (класс «новое поле», ст. 3). Без них признак
объёмного стопа не выражается ни одним разрешённым классом.
2. **A7/A12 — поле `config_drift_basis` в `status --json`** (класс «новое поле»). Внутри движка базис
можно посчитать сразу, но опубликовать его — расширение шва.
3. **Сам закон** — норма зоны; ратифицирует владелец через оркестратора.
## 9. Что я в этом дизайне НЕ проверила
* **Не проверяла, согласится ли платформа** декодировать предложенные поля — чужая зона, вопрос уехал
пингом вместе с дизайном.
* **Не строила** ни одного из четырёх гейтов — фаза 1 их проектирует, а не пишет.
* **A13 и A14 помечены PLAUSIBLE**: они выведены из кода и комментариев, живьём не воспроизводились.
* Реестр Г1 оценён по составу документов, но **не написан**; цена «одна запись на поле» — оценка, а не
замер.

View file

@ -1,10 +1,11 @@
# Отчёт: бэкенд-пак «ДЕНЬГИ И ЧЕСТНОСТЬ ВЫДАЧИ»
Сессия `textmachine-main-8a`, роль БЭКЕНД. Промт `docs/BACKEND_MONEY_HONESTY_SESSION_PROMPT.md`
(оркестратор №21, 31.08). Записка-план — `backend/docs/MONEY_HONESTY_PLAN-NOTE.md`, написана до первой правки.
Дизайн фазы 1 — `backend/docs/DISCLOSURE_LAW_DESIGN.md`.
Сессия `textmachine-main-8a`, роль БЭКЕНД, 31.08, оркестратор №21. Промт —
`docs/archive/prompts/BACKEND_MONEY_HONESTY_SESSION_PROMPT.md`. Записка-план (пре-регистрация, до первой
правки) — `backend/docs/MONEY_HONESTY_PLAN-NOTE.md`; дизайн фазы 1 — `backend/docs/DISCLOSURE_LAW_DESIGN.md`.
**Старт:** HEAD `9d27c0b`, дерево чисто. **Пак $0: платных вызовов ноль**, ни одного ключа не открыто.
**Заленджен `bb541a8`, принят нотой D39.182; закон ратифицирован D39.181.**
---
@ -39,25 +40,7 @@
**38 новых тестов, каждый проверен КРАСНОТОЙ под своей мутацией. Удалённых тестов НОЛЬ.**
⚠ **ПАК ПРОШЁЛ ПРИЁМКУ И БЫЛ ВОЗВРАЩЁН НА ДОРАБОТКУ по ТРЁМ блокерам — все три подтверждены мной
исполнением и закрыты; разбор §9.11.** Два из них — дефекты В МОЁМ ЖЕ ЛЕКАРСТВЕ: разложение денег A0
называло потерей весь глоссарный контур (завышение 59.8% — хуже наивного среза, который тот же файл
отвергает за 29.2%), а починка A6 работала на читающей поверхности и была мертва на денежной. Третий —
вердикт «предъявлено» поверх неработающего пути. Плюс пять дофиксов, из которых Д1 — я добавила поле в
поток и не бампнула его версию, при том что правило записано в комментарии над самой константой.
**Мид-флайт ратификация — отдельным пунктом, как требует промт §12.** Ответ пришёл релеем через
оркестратора №21 (он же нёс дизайн владельцу), я его эхо-подтвердила отдельным сообщением до первой
правки фазы 2. Ратифицировано три вещи: закон как норма зоны · поля леджера доставки в кадре `finished` ·
`config_drift_basis` в `status --json`. A10 оркестратор отдельно отнёс к пункту 1 (исправление ДЕРИВАЦИИ
существующей фигуры, не новое поле) с проверяемым условием — условие выполнено и предъявлено тестом.
**И одна поправка к МОЕМУ ЖЕ закону, которую внёс оркестратор при чтении дизайна:** Ст. 3 удешевляла
рост полосы 1019 до подписи оркестратора, ссылаясь на то, что незнакомый номер поглощается членством в
полосе. Это верно по оси НОМЕРА и неверно по оси ОБЕЩАНИЯ — что доказала моя же находка A15. Оговорка
дописана: пока копии словаря расходятся, новый класс вводится только вместе со сверкой обеих копий.
## 0.1 Батарея и дифф `^func Test` — ИСПОЛНЕНИЕМ (промт §7.2/§7.3), а не по памяти
## 0.1 Батарея — ИСПОЛНЕНИЕМ (промт §7.2), а не по памяти
**`make battery` гонялась СЕМЬ раз; последняя — ПОСЛЕ доработки по приёмке:**
```
@ -69,66 +52,17 @@ MAKE_EXIT=0 # снят с САМОГО make (echo "MAKE_EXIT=$?"), не
--- SKIP: TestHelperKillLoop
```
Корпусные измерения (`TM_MINER_PARITY=1`, `TM_CHECKER_LABELS=1`) НЕ гонялись.
**Первый прогон был КРАСНЫМ** — моя правка A11 уронила чужой ратифицированный тест (разбор §4.3).
Клейм «зелено» без пере-прогона после последней правки был бы ровно тем, за что этот пак заведён.
**Фоновая обёртка печатает `[exited with code 0]` и на КРАСНОЙ батарее** — я это наблюдала лично.
Это та же мина, на которой ошиблась сессия холодного прогона.
**Дифф тестов, снят ЧЕТЫРЕЖДЫ (после фазы 2, после адверсариального прохода, после десяти экземпляров,
после доработки по приёмке):**
```
$ git grep -h '^func Test' HEAD -- 'backend/**/*_test.go' | sort > tests-head.txt # 1045
$ grep -rh '^func Test' backend --include=*_test.go | sort > tests-now.txt # 1083
$ comm -23 tests-head.txt tests-now.txt # УДАЛЁННЫЕ
(пусто)
$ comm -13 tests-head.txt tests-now.txt | wc -l # ДОБАВЛЕННЫЕ
38
```
**Удалённых НОЛЬ** — ни один тест не снят и не переименован за весь пак, включая доработку по приёмке.
Добавлено **38**, поимённо они стоят при своих пунктах в §3, §4, §9 и §9.11.
## 1. Сверка с заказом по пунктам (промт §2 и §8) — вердикты по трёхисходной шкале §6
| пункт | вердикт §6 | посадки | где |
|---|---|---|---|
| **Фаза 1 — закон + гейт** | **предъявлено** (ратифицирован D39.181) | — | `DISCLOSURE_LAW_DESIGN.md` |
| **Фаза 1 — 11-й экземпляр** | **предъявлено ×4** (A12·A15·A13·A14) | — | закон §6 |
| **A0** хвост неудач | **предъявлено** | 3 | §9.1 |
| **A1** дефолт эхо-регена | **предъявлено** | 4 | §3 |
| **A2** фаза стартует, не влезая | **предъявлено** | 2 (+1 чужая предпосылка) | §9.2 |
| **A3(а)** `build` лжёт кодом выхода | **предъявлено** | 1 | §9.3 |
| **A3(б)** `book_files` | **ОПРОВЕРГНУТО** как дефект `book_files`; настоящий дефект рядом — **предъявлено** | 1 | §9.3 |
| **A4** гард врёт о причине | **предъявлено** | 4 | §9.4 |
| **A5** объёмный стоп | **предъявлено** (после ратификации) | 3 | §9.5 |
| **A6** строка 238 | **предъявлено** | 2 | §9.6 |
| **A7** строка 239 | **предъявлено** | 2 (+2 на ловушку) | §9.7 |
| **A8** `prompt_version` | **предъявлено** | 1 | §9.8 |
| **A9** строка 233 | **диагноз предъявлен, кода нет — решение с доводом**; долг назван | 0 | §5, §8 п.3 |
| **A10** строка 194 | **предъявлено**; ось строки **ОПРОВЕРГНУТА** | 2 | §9.9 |
| **A11** строка 232 | **предъявлено** | 4 | §4 |
| **A12** (11-й) `config_drift` без базиса | **предъявлено** | 4 | §9.7 |
| строка 197 (ФЧ-1…ФЧ-8) | **не взята** — решение с доводом | — | §6 |
## 2. Вердикт по снапшоту (промт §7.1 — первым пунктом) — МАШИННЫЙ, не грепом
**Ручка `regenerate_echo_before_escalate` снапшот НЕ ДВИГАЕТ.**
Грепом (недостаточно, но с него начала):
```
$ grep -c 'Retries' backend/internal/pipeline/snapshot.go
0
```
**Машинно, как велит §5 промта** — snapshot ID на фикстуре ДО и ПОСЛЕ, и это не разовый замер, а
постоянный тест `TestEchoRegenBudgetMovesNoSnapshot` (`internal/pipeline/echoregen_test.go`): рендерит
`snapshotIDForWave` обеих волн при ручке 0, затем 1, затем 7, и сравнивает байты.
**Посадка проверена ИСПОЛНЕНИЕМ мутации** — я свернула ЗНАЧЕНИЕ ручки в payload `buildSnapshotID` и получила:
```
--- FAIL: TestEchoRegenBudgetMovesNoSnapshot
a re-generation budget of 1 moved a wave snapshot — turning it on would re-buy every paid checkpoint
```
после чего файл восстановлен (`git diff --stat internal/pipeline/snapshot.go` → пусто).
**И ЧЕСТНАЯ ГРАНИЦА, которую я обязана назвать, потому что промт спрашивает именно о ней.** Ручка вне
снапшота ⇒ два прогона с РАЗНЫМ числом ре-генов дают ОДИН снапшот и по нему неразличимы. Это:
* **честно для ДЕНЕГ**`attempt` входит в request_hash (`checkpoints.attempt`, DDL: «регенерации Фазы 1:
@ -166,9 +100,8 @@ A0, и свёртку трогать не нужно. Свёртку я не т
| эхнутые черновые попытки (`ok=0`) | 5 шт, $0.01070788 |
| эхо-частота свежих черновых вызовов | **5 из 20 = 25%** |
⛔ **И ГЛАВНАЯ ПОПРАВКА К ЭТОМУ ПУНКТУ, снятая адверсариальным проходом и проверенная мной по конфигам:
в ПЯТИ файлах, которые я правила, эскалация ВЫКЛЮЧЕНА.** Все пять несут `escalation.budget_usd: 0`, а
комментарий самого `pipeline-c1.yaml` говорит, что это значит: «budget_usd=0 ⇒ эскалация НЕ исполняется
**ГЛАВНОЕ ПО ЭТОМУ ПУНКТУ: в ПЯТИ файлах, которые я правила, эскалация ВЫКЛЮЧЕНА.** Все пять несут
`escalation.budget_usd: 0`, а комментарий самого `pipeline-c1.yaml` говорит, что это значит: «budget_usd=0 ⇒ эскалация НЕ исполняется
(НИ chains, НИ stage.escalate_to)». ⇒ **в этих файлах ре-ген не ЗАМЕНЯЕТ хоп — хопа там нет.** Число 5.40×
снято на КНИЖНОМ конфиге прогона, где стояло `budget_usd: 0.08`, и этого конфига среди пяти НЕТ.
@ -177,22 +110,20 @@ A0, и свёртку трогать не нужно. Свёртку я не т
budget_usd>0, иначе echo-эскалация черновика не выстрелит») — ре-ген заменяет хоп ценой 5.40× ⇒ экономия;
* **книга с `budget_usd: 0`** (дефолт шаблона, держащий CI зелёным без чужих ключей) — эхо сегодня просто
ФЛАГАЕТСЯ, и чанк уезжает дырой в `--partial`. Ре-ген там не экономит, а **выкупает дыру за один дешёвый
вызов flash** (≈$0.0038). Это тоже выгодная сделка, но это ДРУГАЯ сделка, и первая редакция комментария
в конфигах называла только первую.
⇒ Комментарии в пяти конфигах исправлены: они называют оба режима.
вызов flash** (≈$0.0038). Это тоже выгодная сделка, но это ДРУГАЯ сделка.
⇒ Комментарии в пяти конфигах называют ОБА режима.
**Я НЕ повторяю «7.6×» из квирков**: то число снято на ИЮЛЬСКИХ ценах при `effort:low`. Моё — с этого
прогона, в сегодняшних ценах, на конфиге, которым он куплен. Оба указывают в одну сторону; в конфиг
вписано МОЁ, с командой.
**И честная граница арифметики выгоды, ИСПРАВЛЕННАЯ:** 25% — книжная частота, не независимая
вероятность на вызов; в этом прогоне эхнули ОБА чанка главы 8, что согласуется с по-чанковой
предрасположенностью. ⛔ Я писала «направление выгоды переживает любое p<1» **это неверно, снято
адверсариальным проходом.** Когда ре-ген эхает ПОВТОРНО, цикл выходит и `maybeEscalate` всё равно
стреляет: платится ре-ген И хоп. Направление выживает, только пока вероятность восстановления с ре-гена
выше ена_регенаена_хопа ≈ 1/5.4 ≈ 18.5%`. Замеренные 25% дефолт оправдывают, но порог существует, и
**Честная граница арифметики выгоды:** 25% — книжная частота, не независимая вероятность на вызов;
в этом прогоне эхнули ОБА чанка главы 8, что согласуется с по-чанковой предрасположенностью. Когда
ре-ген эхает ПОВТОРНО, цикл выходит и `maybeEscalate` всё равно стреляет: платится ре-ген И хоп ⇒
**направление выгоды выживает только пока вероятность восстановления с ре-гена выше
ена_регенаена_хопа ≈ 1/5.4 ≈ 18.5%`.** Замеренные 25% дефолт оправдывают, но порог существует, и
не-восстанавливающееся плечо в паке НЕ протестировано. Я не заявляю «сэкономит 56%».
### 3.4 Посадки — ТРИ, каждая проверена КРАСНОТОЙ под своей мутацией
### 3.4 Посадки — ЧЕТЫРЕ, каждая проверена КРАСНОТОЙ под своей мутацией
| посадка | мутация | результат мутации |
|---|---|---|
| `TestEchoRegenReplacesTheHop` | убрать ветку `FlagCJKArtifact && attempt < echoRegen` | `--- FAIL … primary draft calls = 1, want 2 (echo_regen=1)` |
@ -200,13 +131,6 @@ A0, и свёртку трогать не нужно. Свёртку я не т
| `TestShippingPipelinesRegenerateEchoBeforeEscalating` | снять ключ из `pipeline-arm-glm.yaml` | `--- FAIL … pipeline-arm-glm.yaml: … = 0, want 1` |
| `TestEchoRegenFiresONLYForEcho` | снять проверку ПРИЧИНЫ, оставив `attempt < echoRegen` | `--- FAIL … primary calls = 2, want 1` |
**Четвёртая добавлена ПОСЛЕ адверсариального прохода** (§6.2 находка 2): без неё реализация, ре-генящая
на ЛЮБОМ флаге — то есть покупающая гарантированный повторный отказ (D2.2) на каждом отказанном чанке
каждой книги, — оставляла всю батарею зелёной.
**И различение, которое §6 требует, а первая редакция стирала:** буквально «красная на состоянии ДО
правки» здесь только ОДНА — пин конфигов, потому что правка A1 есть ДАННЫЕ. Остальные три держат
МЕХАНИЗМ, которым правка пользуется, и краснеют на мутациях кода. Разбор — §6.3.
**Ручка была БЕЗ ЕДИНОГО ТЕСТА** до этого пака (`grep -rn RegenerateEchoBeforeEscalate --include=*_test.go` → пусто).
Включать в шиппинг ручку на денежном пути, которую держит только чтение кода, я не стала.
@ -214,10 +138,9 @@ A0, и свёртку трогать не нужно. Свёртку я не т
* **Фикстурная мина, найденная собственной падающей проверкой:** общий `newJSONProvider` всегда отвечает
`"model":"fake-model"`, а деньги считаются по ОТВЕТИВШЕЙ модели ⇒ хоп в фикстуре стоил ровно как
праймари. Локальный `newPricedProvider` (только в моём файле) отвечает моделью ЗАПРОСА. Общий хелпер не
тронут. ⚠ **Поправка к моей же формулировке:** я написала «ассерт на цену ПРОШЁЛ БЫ на двух равных
числах» — это НЕВЕРНО и снято адверсариальным проходом: ассерт строгий (`spend[1] < spend[0]`), поэтому
на равных числах он КРАСНЕЕТ, громко, с обоими числами в тексте — что и произошло. Комментарий в самом
тесте это описывает правильно; неверна была фраза в отчёте.
тронут. ⚠ Ассерт цены строгий (`spend[1] < spend[0]`) — на двух равных числах он КРАСНЕЕТ громко, с
обоими числами в тексте; различие двух провайдеров описано в докстринге `newPricedProvider`
(`internal/pipeline/echoregen_test.go`).
* **Бюджет эхо-регена — порог на ТОЙ ЖЕ оси попыток**, что `regenerate_before_escalate`: чанк, уже
регенерированный по `length`, приходит к эхо-проверке на `attempt=1` и второго шанса не получает.
Записано в комментарий конфига и в тест, поведение не менялось.
@ -245,25 +168,16 @@ A0, и свёртку трогать не нужно. Свёртку я не т
reported as delivery
```
### 4.3 ⚠ ГЛАВНОЕ ПО ЭТОМУ ПУНКТУ: моя первая редакция уронила ЧУЖОЙ тест, и я его НЕ ПРАВИЛА
Первая версия считала анонс достаточным. `make battery` дала:
```
--- FAIL: TestARunThatRePaysNothingIsNotAskedForConsent
volume_test.go:907: the grant should have gone entirely to fresh delivery
```
Тот тест «раз-доставляет» два юнита через `ResetChunkStages` и ожидает, что они снова СВЕЖИЕ.
**По §5 промта тест — не мой, править его ради зелени недопустимо.** Я пошла разбираться, кто прав, и
ответ оказался в коде: `ResetChunkStages` **УДАЛЯЕТ и `chunk_status`, и `checkpoints`**
(`internal/store/chunkstatus.go`, два `DELETE FROM` в одной транзакции). ⇒ после редрайва отгруженного
текста у юнита НЕТ ВООБЩЕ — экспорт по нему пуст. Называть такой юнит «уже доставленной пере-делкой»
было бы зеркальной ложью, ровно того класса, который эта ось и убирает.
**предикат сужен по СУЩЕСТВУ, а не подогнан под зелень**: доставлен = *читателю сказали* **И** *то, о
чём сказали, ещё существует*. Обе половины теперь в докстринге `deliveredUnits`, вместе с признанием, что
первая версия была шире и что поймала её именно батарея.
**Отдельная запись для приёмки: чужой тест не изменён ни байтом** (`git status` не показывает
`volume_test.go`).
### 4.3 Почему предикат сужен ПО СУЩЕСТВУ, а не подогнан под зелень
Первая версия оси считала АНОНС достаточным и уронила чужой ратифицированный
`TestARunThatRePaysNothingIsNotAskedForConsent` (он «раз-доставляет» два юнита через `ResetChunkStages` и
ожидает, что они снова СВЕЖИЕ). Тест не мой, править его ради зелени недопустимо (§5 промта) — ответ
нашёлся в коде: `ResetChunkStages` **УДАЛЯЕТ и `chunk_status`, и `checkpoints`**
(`internal/store/chunkstatus.go`, два `DELETE FROM` в одной транзакции) ⇒ после редрайва отгруженного
текста у юнита НЕТ ВООБЩЕ, экспорт по нему пуст, и звать такой юнит «уже доставленной пере-делкой» было
бы зеркальной ложью того же класса, который эта ось убирает.
⇒ предикат сужен: доставлен = *читателю сказали* **И** *то, о чём сказали, ещё существует*. Обе половины
стоят в докстринге `deliveredUnits` (`internal/pipeline/volume.go`). Чужой тест не изменён ни байтом.
### 4.4 ВТОРАЯ дыра того же предиката, найденная мной ПОСЛЕ первой — и тоже не рассуждением
Разобравшись с редрайвом, я прошла путь анонса до конца и нашла зеркальную дыру: **`unit_done` пишется
@ -310,11 +224,8 @@ payload'а, а не условием. ⇒ анонс сам по себе не
термы — консолидируется всё, каждый раз.
**И это ОПРОВЕРГАЕТ ось, записанную в строке 194** («на сдвиге снапшота они реально пере-покупаются»).
⚠ **Первая редакция этого абзаца доказывала опровержение через П3 («там снапшот сдвинулся, а терминология
стоила $0») — это БЫЛО НЕВЕРНО, снято адверсариальным проходом и проверено мной.** Терминология адресована
под ЧЕРНОВЫМ снапшотом (`internal/pipeline/mining.go`, греп `runTerminologist(ctx, draftSnapshot`), а в П3
сдвинулся РЕДАКТОРСКИЙ — то есть П3 к вопросу об оси отношения не имеет.
⚠ Считать по П3 нельзя: терминология адресована под ЧЕРНОВЫМ снапшотом (`internal/pipeline/mining.go`,
греп `runTerminologist(ctx, draftSnapshot`), а в П3 сдвинулся РЕДАКТОРСКИЙ.
**Опровержение стоит на П2 и на прямой улике из таблицы `jobs`:**
```
sqlite3 'file:…?mode=ro' "SELECT stage,COUNT(*),COUNT(DISTINCT snapshot_id) FROM jobs GROUP BY stage;"
@ -333,7 +244,7 @@ WARN'а, процитированного промтом. Следующий б
⇒ **ответ на прямой вопрос промта («накопленное это или трата ЭТОГО прохода — пере-выведи»): НАКОПЛЕННОЕ,
по книге и роли, через покупки.** Пере-выведено арифметикой из строк, а не принято со слов.
⚠ **ДВЕ ПОПРАВКИ К ЭТОМУ ЖЕ АБЗАЦУ, обе сняты адверсариальным проходом и проверены мной:**
⚠ **ДВЕ ГРАНИЦЫ ЭТОГО АБЗАЦА, обе проверены по леджеру:**
* **Исчерпан был бюджет КЛАССИФИКАТОРА, а не терминолога.** У них РАЗНЫЕ ключи: `classify_budget_usd`
($0.02, пробит) и `gates.terminology.budget_usd` ($0.05, при пожизненной трате терминолога
`0.01570228 + 0.02491426 = $0.04061654`НЕ пробит). Формулировка «книга исчерпывает свой
@ -349,17 +260,13 @@ WARN'а, процитированного промтом. Следующий б
терм, консолидированный по трём главам, при десяти главах может быть отрендерен лучше. Пропускать
уже консолидированные термы — значит фиксировать ранний рендер. Это решение владельца, не сессии, и
пак прямо не про качество.
3. ⛔ **ТРЕТИЙ ДОВОД Я СНИМАЮ САМА.** Я написала: «лечение A9 и лечение A2 — ОДИН механизм, а A2 за
стоп-точкой» (на момент написания она ещё стояла). Адверсариальный проход это опроверг, и я
проверила — он прав: рычаги РАЗНЫЕ и не
пересекаются. Рычаг A2 — одно сравнение внутри `runBankRoleBatches` (`spent+want > plan.budgetUSD`).
Рычаг A9 — ВЫШЕ по течению и другой: КАКИЕ кандидаты попадают в батчи (`buildBankCandidates`) и КАК
они пакуются (`terminology.Batch` — жадная пере-упаковка по всему упорядоченному списку). Правка
второго бюджета не трогает. **Довод был удобным, а не верным, и он был назван решающим — это худший
вид ошибки в отчёте, поэтому называю прямо.**
**Что остаётся после снятия третьего довода:** первые два (калибровка запрещена паком; вторая половина
диспозиции — качественная развилка владельца). Их ХВАТАЕТ, чтобы не писать код, но НЕ хватает, чтобы не
писать посадку — см. §8 п.3, где я называю этот долг прямо.
3. ⚠ **A9 и A2 — РАЗНЫЕ рычаги, и A9 чинится ОТДЕЛЬНО** (проверено по коду; обратный довод первой
редакции — «один механизм» — снят): рычаг A2 — одно сравнение внутри `runBankRoleBatches`
(`spent+want > plan.budgetUSD`); рычаг A9 — ВЫШЕ по течению: КАКИЕ кандидаты попадают в батчи
(`buildBankCandidates`) и КАК они пакуются (`terminology.Batch` — жадная пере-упаковка по всему
упорядоченному списку). Правка второго бюджета не трогает.
⇒ Двух первых доводов ХВАТАЕТ, чтобы не писать код, но НЕ хватает, чтобы не писать посадку — долг назван
в §8 п.1.
⇒ Отдаю пингом ГОТОВЫЙ диагноз с числами и названной развилкой. Это дороже, чем «≈1.5×» в реестре.
---
@ -373,8 +280,8 @@ WARN'а, процитированного промтом. Следующий б
— исключение из эхо-правила выдано только терминологу, хотя формат ответа классификатора тот же.
Плюс одна строка редактора `degraded=sanitizer_stripped` ($0.01865424) — её текст ОТГРУЖЕН.
⇒ наивная поверхность назвала бы оператору **на 29.2% больше потерь, чем было**
(`0.12316089 / 0.09531838 = 1.2921`). ⚠ Испр.: первая редакция делила на НАИВНУЮ цифру и печатала
22.6% — это доля завышения В САМОЙ наивной цифре, ответ на другой вопрос. Слагаемые не менялись.
(`0.12316089 / 0.09531838 = 1.2921`). ⚠ «22.6%», стоящие в записке-плане, — доля завышения В САМОЙ
наивной цифре, ответ на ДРУГОЙ вопрос; слагаемые те же.
**Не чинила и не могла:** правка эхо-правила двигает `classifierVersion` — поле снапшота ⇒ ярус B
(и это ровно цена, записанная в строке бэклога 105).
@ -388,136 +295,29 @@ WARN'а, процитированного промтом. Следующий б
---
## 6.1 Собственные посадки и находки ВНЕ списка промта (§7.4 требует не меньше трёх)
## 7. ПИНГИ оркестратору №21 — доставлены; открытыми остались ДВА
**Посадки, которых промт не заказывал:**
1. `TestEchoRegenBudgetMovesNoSnapshot` — промт просил РАЗОВЫЙ замер snapshot ID до/после. Я сделала его
постоянным гейтом: он краснеет, если КТО-ТО ПОЗЖЕ свернёт ручку в снапшот. Разовый замер этого не ловит.
2. `TestAnAnnouncedButFLAGGEDUnitIsStillNewBook` — целиком моя находка: `unit_done` пишется и для
ФЛАГНУТОГО юнита, значит анонс сам по себе не значит «читателю есть что открыть». В тексте промта про
флагнутые юниты нет ничего.
3. `TestShippingPipelinesRegenerateEchoBeforeEscalating` — промт заказывал РЕШЕНИЕ о дефолте; гейт на само
решение не заказывал. Без него данные откатываются молча.
Отработаны и потому здесь не пересказываются: **A15** (две копии полосы 1019 обещают разное) — строка
бэклога **246**, разбор в законе §6; **A5 · A3(б) · A4 · A7** построены (§9); ось строки **194**
опровергнута (§5.1, ратифицировано D39.181 §3); пинг «A9 и A2 — один механизм» снят как неверный
(§5.3 п.3).
**Находки вне списка, не оформленные тестами:**
4. Срез `ok=0 AND cost_usd>0` контаминирован мис-вердиктом классификатора — предъявлено ПРЯМОЙ уликой
(ответы в `checkpoints.response_text`), §5.4.
5. **A15** — две копии полосы 1019 обещают разное (пинг 1).
6. **A12**`config_drift` без базиса, и он портит `stale` в `build` (пинг 3).
7. Комментарий `export.go` называет потребителя (`полигон`), которого в дереве НЕТ (пинг 3).
8. **Дефект в моём собственном диффе, найденный собственным чтением диффа:** хелпер `unitShipped` попал
ВНУТРЬ докстринга `deliveredUnits`, из-за чего вся объяснительная часть прицепилась не к той функции.
`gofmt`, `go vet` и линтер это пропускают. Исправлено, порядок блоков предъявлен командой.
## 6.2 Адверсариальный проход по СВОЕЙ ГОТОВОЙ работе (промт §7.5) — обязательный артефакт
**Модель:** ⚠ **явно я её НЕ задавала** — агенты унаследовали модель сессии (Opus 5). Промт §7 велит
задавать модель ИМЕНЕМ; я этого не сделала и называю это отступлением, а не деталью. Канон просит Fable 5;
по записке `textmachine-36` в канале `claude-fable-5` на этом аккаунте недоступен, но проверять это я не
пробовала, так что оправданием не считаю.
**Сколько агентов и что дано на вход. Два веера, 29 агентов суммарно.**
* **Веер 1 — грунтовка корпуса (22 агента):** 2 картографа каналов (движок / платформа) + 10 читателей по
строке корпуса + 10 адверсариальных верификаторов ПОВЕРХ отчёта каждого читателя. Вход: код HEAD обеих
зон read-only, БД прогона `?mode=ro`, текст промта КАК ПРИОР с прямым мандатом «автор трижды ошибся
сегодня, проверяй каждый якорь».
* **Веер 2 — проход по ГОТОВОЙ работе (7 агентов):** шесть линз (посадки · каждое число · закон против
своего корпуса · код-дифф · заказ против выдачи · решение по A9) + седьмой, механическая сверка с
заказом по трёхисходной шкале §6 — **это и есть интервальная сверка, отданная субагенту с явными
критериями (промт §7.8)**. Вход: мои три дока, `git diff`, новые тесты, текст промта; мандат дословно —
«найди, где она выдаёт непроверенное за проверенное». Линзе посадок было велено КОПИРОВАТЬ дерево в
`/tmp` и мутировать копию; реальное дерево агенты не трогали (проверено ими же по md5).
**Вердикты линз:** 2 × `MATERIALLY_WRONG` (закон против корпуса; код-дифф), 4 × `SOUND_WITH_CORRECTIONS`.
Ноль линз не нашли ничего.
### Находки и диспозиции — каждая, дословно по существу
| # | находка линзы | диспозиция |
|---|---|---|
| 1 | **БЛОКЕР (код).** `unitShipped` сканировал ВСЕ строки юнита, включая draft ⇒ обычная форма «draft ok + edit флагнут» читалась как уже доставленная пере-делка, хотя экспорт по ней пуст | **ПРИНЯТО, ПОЧИНЕНО.** `unitShipped` читает строку ОТГРУЖАЮЩЕЙ стадии; добавлена посадка `TestAUnitWhoseEDITFlaggedIsStillNewBook`, красная на пред-фиксной форме (`Delivered=0 Reworked=1`) |
| 2 | **БЛОКЕР (тест).** Посадка эхо-регена держала только БЮДЖЕТНУЮ половину ветки: снятие проверки ПРИЧИНЫ оставляет всю батарею зелёной, а такая реализация ре-генит на любом флаге (D2.2 «re-refuse and re-bill») | **ПРИНЯТО, ЗАКРЫТО.** `TestEchoRegenFiresONLYForEcho`; мутация даёт `primary calls = 2, want 1` |
| 3 | **БЛОКЕР (тест).** `TestEchoRegenBudgetMovesNoSnapshot` тыкал ПОЛЕ СТРУКТУРЫ; фолд, вычисленный НА ЗАГРУЗКЕ, проходил насквозь. Заказанный промтом замер «ДО и ПОСЛЕ СВОЕГО ДИФФА» (а дифф — пять YAML) не выполнялся | **ПРИНЯТО, ПЕРЕПИСАНО.** Плечо 1 теперь грузит ДВА конфига-фикстуры и сравнивает id; проверено мутацией — фолд значения ручки красит именно это плечо |
| 4 | **БЛОКЕР (код+довод).** В пяти правленых конфигах `escalation.budget_usd: 0` ⇒ эскалации там НЕТ, и ре-ген ничего не заменяет; 5.40× снято на КНИЖНОМ конфиге прогона, которого среди пяти нет | **ПРИНЯТО.** §3.3 переписан, комментарии в пяти конфигах называют ОБА режима. Решение не откатываю: во втором режиме ручка выкупает дыру за один дешёвый вызов — довод другой, но выгода реальна |
| 5 | **БЛОКЕР (A11).** Развилка `finalStageWave` — та, которую промт приказал решить, — не имела посадки вовсе; удаление ветки оставляло батарею зелёной | **ПРИНЯТО, ЗАКРЫТО.** `TestDeliveryIsReadFromTheSHIPPINGWaveOnADraftOnlyPipeline`; мутация даёт `Delivered=1 LeftFresh=2`, и это ЕДИНСТВЕННЫЙ тест в репозитории, который её ловит |
| 6 | **БЛОКЕР (A9).** «В П3 снапшот сдвинулся, а терминология стоила $0» — неверно: терминология адресована под ЧЕРНОВЫМ снапшотом, а сдвинулся редакторский | **ПРИНЯТО, ДОВОД ЗАМЕНЁН НА СИЛЬНЕЙШИЙ.** Опровержение теперь стоит на таблице `jobs`: `draft|10|1` — черновой снапшот не двигался НИ РАЗУ, при этом П2 пере-купила контур целиком |
| 7 | **БЛОКЕР (A9).** «Лечение A9 и A2 — один механизм» не подтверждается кодом: рычаги (бюджетное сравнение против отбора/упаковки кандидатов) не пересекаются | **ПРИНЯТО, ДОВОД СНЯТ МНОЮ** (§5.3 п.3). Он был назван решающим — худший вид ошибки, поэтому снят вслух, а не тихо |
| 8 | **ВАЖНОЕ (A9).** Пред-регистрированное в записке-плане обязательство «воспроизведение перерасхода фикстурой + посадка» не выполнено и в obstacles не названо | **ПРИНЯТО ЧАСТИЧНО, см. §8 п.3.** Посадку не пишу: она пиннила бы ДЕФЕКТ, и её пришлось бы удалять при починке — в зоне, где удаление теста запрещено правилом. Долг назван прямо |
| 9 | **ВАЖНОЕ (числа).** «16 термов» — чужое число из отчёта прогона, без команды и без пометки, а промт называет именно его примером | **ПРИНЯТО.** Помечено «со слов прогона», вывод на нём не строится |
| 10 | **ВАЖНОЕ (числа).** «на 22.6% больше потерь» — база не та: правильная величина завышения 29.2% | **ПРИНЯТО, исправлено в обоих доках** |
| 11 | **ВАЖНОЕ (закон).** §2.4 — частный случай §2.1, критерия разделения нет, перечни экземпляров расходятся | **ПРИНЯТО.** Отношение названо явно: §2.4 — процедура исполнения §2.1, перечни пересекаются НАМЕРЕННО |
| 12 | **ВАЖНОЕ (закон).** A2 не нарушает §2.5 как он написан: фаза как раз отчитывается о трате до `break` | **ПРИНЯТО.** A2 переведён на §2.3; пятую обязанность заводить не стала — одной строки корпуса мало, дыра названа |
| 13 | **ВАЖНОЕ (закон).** Предикат вывода объёмного стопа ЛОЖЕН на самом прогоне A5: `reconcile` выносит флагнутое из `Delivered`/`Reworked` | **ПРИНЯТО.** Рекомендация переписана: слать готовый признак числом, а не заставлять потребителя воспроизводить арифметику `reconcile` |
| 14 | **ВАЖНОЕ (закон).** Канал A10 (`projected_book_usd`/`rebill_*`) факт НЕ несёт: обе фигуры считаются из `chunk_status`, где терминологических строк НОЛЬ | **ПРИНЯТО.** Канал исправлен на деривацию от `checkpoints ⋈ jobs`; названо, что A10 дороже класса «новое число» |
| 15 | **ВАЖНОЕ (закон).** Г1 на рефлексии не перечисляет ТИПЫ, payload'ы потока спрятаны за `json.RawMessage`, а собственный перечень дока уже забыл три типа | **ПРИНЯТО.** Ограничение вписано в §7; названо, что ручная половина полной защиты не даёт |
| 16 | **ВАЖНОЕ (закон).** Г3 не реализуем как описан: payload — дерево, часть листьев — кодовые константы, фикстурой не сдвигаемые | **ПРИНЯТО.** Область Г3 сужена до конфигурируемого подмножества; для константных осей предложено требование к сообщению, а не тест |
| 17 | **ВАЖНОЕ (закон).** Набор гейтов не покрывает шесть из десяти рекомендованных каналов | **ПРИНЯТО, и это самая дорогая правка дока.** Вписано вслух: «закон загейчен на 40%» |
| 18 | **ВАЖНОЕ (закон).** A15 не подводится ни под одну статью: это не значение и не выводится из строк | **ПРИНЯТО, натяжка снята.** A15 остаётся находкой первого класса и перестаёт быть доводом ЗА закон |
| 19 | **ВАЖНОЕ (закон).** «Закон есть обобщение `RebillBasis`» — лесть: родословная есть только у §2.3, а сам `RebillBasis` богаче | **ПРИНЯТО, формулировка сужена до §2.3** |
| 20 | **ВАЖНОЕ (заказ).** Пинг A5 приказан в `docs/PROGRESS.md` и там отсутствует | **ПРИНЯТО, добавлен** |
| 21 | **ВАЖНОЕ (заказ).** Слово «не предъявлено» не использовано ни разу; семь строк несли «разобран» — слово вне шкалы | **ПРИНЯТО.** Восемь строк переведены на «не предъявлено (стоп-точка)» |
| 22 | **ВАЖНОЕ (заказ).** §7.5 и §7.8 не имели артефакта в отчёте | **ПРИНЯТО — это она** |
| 23 | **МЕЛКОЕ.** «Мутация: добавить поле в payload» — неверно: константа тест не красит | **ПРИНЯТО, описание мутации исправлено** |
| 24 | **МЕЛКОЕ.** §3.5 утверждал, что на общем провайдере ассерт цены «прошёл бы» — на деле он краснеет громко | **ПРИНЯТО, инверсия исправлена** |
| 25 | **МЕЛКОЕ.** `TestEchoRegenReplacesTheHop` на состоянии ДО правки ПРОХОДИТ — он ловит мутацию КОДА, а не откат данных | **ПРИНЯТО, см. §6.3** |
### 6.3 Что из требований §6 я НЕ выполняю буквально — с доводом
§6 требует, чтобы посадка для A1A5 краснела на состоянии **ДО правки**. Для A1 правка — это ДАННЫЕ, и
буквально этому условию отвечает ОДНА посадка из трёх: `TestShippingPipelinesRegenerateEchoBeforeEscalating`
краснеет на конфигах HEAD пятью строками, по одной на файл. Две другие (`TestEchoRegenReplacesTheHop`,
`TestEchoRegenFiresONLYForEcho`) на состоянии ДО правки ЗЕЛЁНЫЕ — они держат МЕХАНИЗМ, которым правка
пользуется, и краснеют на мутациях кода. **Это не подгонка, а разные предметы**, но отчёт обязан их
различать, а первая редакция лила все три в одну строку «проверены краснотой».
## 7. ПИНГИ оркестратору №21
1. ⛔ **A15 — САМЫЙ СРОЧНЫЙ, и он на шве. Две копии полосы 1019 обещают РАЗНОЕ.** Движок
(`backend/cmd/tmctl/main.go`, греп `The refusal band`): «**«Nothing was written» is NOT the band's
promise any more** … exit 15 legitimately answers with files on disk». Платформа
(`platform/internal/ingest/exit.go`, греп `The refusal band`): «… **and nothing this process would
have written was written**». Потребитель держит гарантию, которую производитель отозвал, а по `PD-196`
интейк действует по полосе РАЗРУШИТЕЛЬНО. Правка — чужая зона.
⚠ Денежная половина («nothing was spent») цела в обеих копиях; запрет отказывать в A2 классом полосы стоит.
⚠ Хвост: движок объявляет `exitBookIncomplete = 16`, у платформы константы с этим номером нет вовсе.
2. **A5 — пинг с домашней заготовкой** (полная таблица носителей — закон §5 A5). Кратко: новый код выхода
нельзя; **новое значение `Finished.Outcome` тоже нельзя** — но НЕ потому, что пара биективна (она не
биективна, `OutcomeRefused` двойника не имеет), а потому, что значение только в потоке заставило бы два
канала назвать РАЗНЫЙ исход одного прогона. ⇒ рекомендую **ЧИСЛА**: леджер доставки
(`max_units · delivered · reworked · flagged · free · left_fresh · left_rework`) полями кадра `finished`.
Класс — «новое поле» ⇒ ваша ратификация.
⚠ И поправка к срочности: **платформа сегодня `--max-units` НЕ ШЛЁТ вообще** (`TranslateArgs`, вся зона
`platform/` — ни одного вхождения вне комментария). Это дефект будущего потребителя.
3. **Комментарий `export.go` называет несуществующего потребителя.** Он утверждает, что `ConfigDrift`
читает полигон; `grep -rn 'config_drift\|ConfigDrift' eval/` даёт НОЛЬ хитов. Настоящие потребители —
человеческий рендер и **`build`** (`staleUnits` начинается с `if exp.ConfigDrift { return nil, true }`),
а значит тихое `config_drift=false` заставляет `build` печатать `stale: 0` вместо `stale: UNKNOWN`:
поле БЕЗ базиса портит поле, у которого базис есть.
4. **A3(б) — ОПРОВЕРГНУТО как дефект `book_files`.** Контракт «карта МЕСТ, не присутствия» написан ОБЕИМИ
сторонами с доводом (гонка check-then-open), и платформа `book_files` не декодирует вовсе. Настоящий
дефект в том же месте: `build --format epub` МОЛЧА удаляет соседний `.txt`, различая «файл был» от
«файла не было» и выбрасывая различение.
5. **Строка 194: названная в ней ось ОПРОВЕРГНУТА** живыми данными (§5.1). Ось — байты батча, не сдвиг снапшота.
6. **A7: наивная правка заводит НОВУЮ ложь.** `projectRebill` пропускает ровно те строки, на которые
сработает новое правило ⇒ `config_drift=true` при `rebill_units=0`, а человеческий рендер превращает
этот флаг в утверждение о пере-оплате, которой не будет. Правка A7 обязана идти вместе с базисом.
7. **A4: готового дифа полей НЕТ.** `classifySnapshotMove` возвращает `moveOther` на ПЕРВОМ несовпавшем
ключе, ходя по Go-мапе ⇒ множества разошедшихся полей не собирает, и порядок недетерминирован. Диф для
сообщения — новый и сортированный.
8. ⚠ **ЭТОТ ПИНГ БЫЛ НЕВЕРЕН И СНЯТ МНОЙ ЖЕ.** В первой редакции он гласил «A9/A2 — один механизм,
разводить их по разным пакам значит чинить половину». Адверсариальный проход это опроверг, я проверила
и согласилась (§5.3 п.3): рычаг A2 — одно сравнение бюджета внутри `runBankRoleBatches`; рычаг A9 —
выше по течению и другой (отбор кандидатов и упаковка батчей). Они НЕ пересекаются, и A9 можно чинить
отдельно. Пинг пережил снятие довода, на котором стоял, — поймано приёмкой; исправляю, а не удаляю,
чтобы след ошибки остался.
9. **Вопрос про `books/`:** предыдущая сессия спрашивала, кто коммитит репозиторий книг. Я туда не писала;
вопрос остаётся открытым.
1. **Комментарий `export.go` называет несуществующего потребителя — ОТКРЫТО.** Он утверждает, что
`ConfigDrift` читает полигон; `grep -rn 'config_drift\|ConfigDrift' eval/` даёт НОЛЬ хитов (контроль:
тот же греп по `export` в `eval/` хиты даёт — инструмент в этой области работает). Настоящие
потребители — человеческий рендер и **`build`** (`staleUnits` начинается с
`if exp.ConfigDrift { return nil, true }`). Комментарий на месте до сих пор
(`internal/pipeline/export.go`, греп `polygon's extraction, which uses ConfigDrift`); полный разбор —
закон §6 A12 п.4.
2. **Вопрос про `books/` — ОТКРЫТ:** предыдущая сессия спрашивала, кто коммитит репозиторий книг.
Я туда не писала.
---
## 8. Obstacle reporting — что НЕ удалось и что НЕ проверено
1. ⛔ **Пред-регистрированная посадка A9 не написана.** Моя же записка-план обещала «воспроизведение
перерасхода фикстурой + посадка». Ратификации она не требует, так что три довода §5.3 её НЕ покрывают.
перерасхода фикстурой + посадка». Ратификации она не требует, так что доводы §5.3 её НЕ покрывают.
Причина, которую даю вместо них: она пиннила бы ДЕЙСТВУЮЩИЙ дефект, и при починке следующей сессии
пришлось бы её удалить — в зоне, где удаление теста запрещено правилом, это ловушка. Довод слабее
остальных, и я подаю его таким. Живая улика (`jobs`: `draft|10|1`) сильнее фикстуры, но она не гейт.
@ -533,12 +333,10 @@ WARN'а, процитированного промтом. Следующий б
7. **Корпусные тесты батареи не гонялись** (три скипа названы).
8. **Экономика A1 — направление доказано, величина нет**; порог выгоды ≈18.5%, не-восстанавливающееся
плечо не протестировано.
9. ⚠ **Модель субагентам я НЕ задавала явно** — унаследовали модель сессии. Промт §7 велит задавать именем.
10. **A10 добавляет к проекции УЖЕ ПОТРАЧЕННОЕ, а не прогноз контура** — это нижняя граница, и названа
9. **A10 добавляет к проекции УЖЕ ПОТРАЧЕННОЕ, а не прогноз контура** — это нижняя граница, и названа
таковой в коде. Прогноз контура требует калибровки, запрещённой этому паку.
11. **Живого платного прогона не было ни одного** — пак $0. Всякий клейм о рантайме здесь либо снят с
10. **Живого платного прогона не было ни одного** — пак $0. Всякий клейм о рантайме здесь либо снят с
ЛЕДЖЕРА чужого прогона read-only, либо получен исполнением ТЕСТА.
12. **Ничего не закоммичено.** Лендит оркестратор.
## 9. ФАЗА 2 — десять экземпляров закона, по одному
@ -559,6 +357,22 @@ bought-nothing-shippable …` и, когда есть потери, называ
Плюс тест сверяет сумму разложения с `committed` — чтобы разложение не стало вторым, дрейфующим
определением траты книги.
⚠ **ЧЕТВЁРТЫЙ КЛАСС — BANK («купил терминологию, не текст»), и без него разложение несло ХУДШУЮ версию
болезни, от которой построено** (найдено приёмкой): банк-роли `chunk_status` **не пишут вовсе**
(`grep -c UpsertChunkStatus terminologist.go` → 0 при контроле `stagerun.go` → 2), а «отгружено»
определялось через `FinalHash` из `chunk_status` ⇒ весь терминологический контур ПО ПОСТРОЕНИЮ уезжал в
потери: $0.04980482 из «потерь» $0.133 — **завышение 59.8%**, хуже наивного среза `ok=0` (29.2%), который
этот же файл в шапке за это и отвергает; на ЗДОРОВОЙ книге оператор читал «85.7% did NOT become shipped
text». Класс опознаётся по синтетической стадии и исключён из `LostUSD`
(`internal/pipeline/paidtail.go`, греп `BANK — it bought the book's TERMINOLOGY`).
⚠ Суперсед банк-батча ОСТАЁТСЯ потерей — контур пере-покупается с ростом черновика (строка 233).
**Посадки:** `TestAHealthyGlossaryPassIsNotReportedAsALoss` (мутация «убрать класс» даёт
`WorstPosition:book/batch0/terminology` — сурфейс указывает оператору на здоровый батч) ·
`TestAReboughtGlossaryBatchIsStillALoss`.
`TestTheDecompositionIsNotTheOkColumn` ПИННИЛ дефект (требовал `WithheldUSD == 0.009` про успешный
вызов классификатора). Смена его утверждения **разрешена оркестратором ЯВНО при приёмке** (D39.182 §2 п.2),
ссылка стоит в комментарии самого теста — это не подгонка под зелень и не прецедент «так можно».
### 9.2 A2 — фаза стартовала, зная, что не влезает
**Стало:** план режется до влезающего **ДО первого вызова**; строка называет `batches_planned`,
`batches_running`, `batches_dropped`, `budget_usd`; уже оплаченные батчи допускаются всегда.
@ -570,6 +384,10 @@ consolidated» — потому что `unanswered` в одиночку чита
Вторая — это ось «никакая частичная работа не оплачена», предъявленная фикстурой, а не рассуждением:
мутация «стартовать всё равно» даёт `--- FAIL … a plan that fits nothing must spend NOTHING … got $0.005460`.
⚠ Обрезка называется ДВАЖДЫ: у классификатора СВОЙ бюджет (`classify_budget_usd`), и инцидент прогона был
именно на нём, поэтому в результате отдельное поле `ClassifyBatchesDropped`
(`internal/pipeline/terminologist.go`) и оба числа в логе.
### 9.3 A3 — `build`
**(а) Стало:** сбой уборки больше не возвращается ошибкой. Файлы уже закоммичены, уборка — housekeeping,
и её сбой не смеет превратить записанную книгу в «инфра-сбой, ничего не записано». Отчёт выживает и несёт
@ -599,6 +417,10 @@ consolidated» — потому что `unanswered` в одиночку чита
(без него «присутствие = признак» ничего не значит) · `TestTheLedgerIsOmittedNotNulled` (ключ ОТСУТСТВУЕТ,
а не `null`).
⚠ Новое поле кадра бампает МИНОР потока: `StreamVersion` = `1.2` (`internal/runevents/runevents.go`) —
правило записано в комментарии над самой константой, и первая редакция правки его нарушила. Платформенное
зеркало не тронуто: мажор совпадает.
### 9.6 A6 — деньги слепы к правке исходника на месте
**Стало:** ветка совпавшего снапшота сверяет контент-хеш — тот же предикат, что применяет сам прогон.
**Дорогая сверка гейтится дешёвым зондом:** валидность сохранённого манифеста (его ключ фолдит SHA
@ -607,6 +429,14 @@ consolidated» — потому что `unanswered` в одиночку чита
**Посадки:** `TestTheProjectionSeesAnInPlaceSourceEdit` (плюс проверка, что прогон РЕАЛЬНО платит — иначе
проекция была бы права) · `TestAnUntouchedSourceStillCostsNoReChunk` (зонд).
**Ответ зонда обязан быть ЗАФИКСИРОВАН ДО перезаписи сайдкара.** Зонд спрашивает о ВАЛИДНОСТИ
сохранённого манифеста, а `translate` персистит манифест РАНЬШЕ гейта согласия — значит к моменту вопроса
сайдкар уже описывал НОВЫЙ исходник: зонд отвечал «ничего не двигалось», дорогая сверка пропускалась, и **гейт согласия на пере-оплату не срабатывал на правке исходника на месте** — ровно на
сценарии, ради которого A6 заведён. Найдено приёмкой; первый тест этого не ловил, потому что бил в
`Status`, а не в `TranslateBook`. Лечение — `noteSourceVintage()` перед `persistManifest`
(`internal/pipeline/bookrun.go`) плюс кэш ответа, который заодно снял хеширование всего исходника ПО
КАЖДОЙ СТРОКЕ на $0-пути. **Посадка написана ДО правки и была красной:** `TestTheCONSENTGateSeesAnInPlaceSourceEdit`.
### 9.7 A7 + A12 — одним касанием, как потребовал оркестратор
**Стало:** правило осиротевшей стадии поднято в ОДНО определение (`orphanStageRows`), общее для `status` и
`export`. ⛔ Свёртка банка НЕ унифицирована — `export.go` объявляет это расхождение сознательным и просит
@ -622,6 +452,10 @@ basis="none"` — дословно дефект прогона) · `TestACleanBo
`TestABookWithNoRowsCannotHaveItsDriftChecked` · `TestTheDriftBasisVocabularyIsClosed`; плюс в `cmd/tmctl`
`TestDriftDoesNotClaimARePaymentItNeverComputed` · `TestAnUnknownDriftBasisIsSaidOutLoud`.
Обе поверхности спрашивают ОДНУ предпосылку `driftCheckable` (`internal/pipeline/driftbasis.go`): без
неё базис расходился на одной книге (`unknown` против `none`) — то есть строка 239 воспроизводилась В
ЛЕКАРСТВЕ ОТ НЕЁ. Посадка `TestBothSurfacesReachTheSameBasisOnTheSameBook` на трёх состояниях книги.
### 9.8 A8 — метка не отслеживала байты промпта
**Стало:** репо-гейт `TestPromptLabelsPinTheirBytes` с реестром `testdata/prompt-labels.json`, ключ
`(пара, роль, метка)` → sha256 канонического промпта. Обновление легитимного бампа —
@ -645,6 +479,12 @@ basis="none"` — дословно дефект прогона) · `TestACleanBo
`SUM(checkpoints)`; `TestABookWithNoBankRolesProjectsExactlyAsBefore` — на книге без банк-ролей ничего не
сдвинулось.
**Порог согласия берётся ДО слагаемого A10** (решение оркестратора при приёмке, D39.182 §3): порог
управляет ПЕРЕ-оплатой, а контур ею не является, и слагаемое в базе порога сделало бы денежный гейт слабее.
Публикуемая проекция контур сохраняет (`internal/pipeline/rebill.go`, греп `contour before the threshold
was taken`). Посадка `TestTheContourDoesNotWeakenTheConsentGate`: мутация «свернуть контур в базу порога»
даёт `got: <nil>` — пере-оплата проходит НЕСПРОШЕННОЙ.
### 9.10 ⚠ ЧУЖОЙ ТЕСТ, КОТОРОГО Я КОСНУЛАСЬ — называю сама
`TestTerminologistBudgetCutIsNotReportedAsAnEmptyReply` (`bankfixpack_test.go`) проверял свою
ПРЕДПОСЫЛКУ грепом по логу — искал `budget would be exceeded`. Правка A2 эту строку не переживает по
@ -654,15 +494,11 @@ basis="none"` — дословно дефект прогона) · `TestACleanBo
Я не стала печатать старую фразу рядом с новой ради совпадения грепа: это было бы вводить в заблуждение
ради зелени, то есть нарушать закон, который я же пишу. Предпосылка переведена с подстроки на ФАКТ
(`lastTerminology.BatchesDropped == 0`) — строго сильнее и ровно то, что предписывает D39.171.
**Отправлено пингом оркестратору с прямым предложением откатить.** Он правку ОСТАВИЛ, проверив дифф сам:
удалены ровно две строки предпосылки, утверждение стоит контекстной строкой диффа. Его разбор:
запрет канона бьёт по УТВЕРЖДЕНИЮ теста, а я поменяла МЕХАНИЗМ ПРЕДПОСЫЛКИ — и поменяла с механизма,
который проект ратифицированно считает дефектным (D39.171), на структурный факт. Общую норму «правка
предпосылки ≠ правка утверждения» он сам не устанавливает и несёт владельцу как вопрос.
**И ПОРЯДОК БЫЛ НЕВЕРНЫЙ — фиксирую без смягчения.** Правило говорит «пинг, НЕ правка», а я сделала
правку и пинг. То, что я вынесла это сама и предложила откатить, объясняет, почему ответ соразмерный, но
не отменяет, что порядок нарушен. На будущее: пинговать ДО правки, даже когда уверена.
Оркестратор правку ОСТАВИЛ, проверив дифф сам (удалены ровно две строки предпосылки, утверждение стоит
контекстной строкой): запрет канона бьёт по УТВЕРЖДЕНИЮ теста, а сменён был МЕХАНИЗМ ПРЕДПОСЫЛКИ — с
механизма, который проект ратифицированно считает дефектным (D39.171), на структурный факт. ⚠ Общей нормы
«правка предпосылки ≠ правка утверждения» он НЕ устанавливает — она унесена владельцу вопросом (D39.182
§2). ⚠ Порядок был нарушен: правка, потом пинг, а правило говорит «пинг, НЕ правка».
⚠ **ВТОРАЯ ПОЛОВИНА ТОГО ЖЕ ТЕСТА ОСТАВЛЕНА НА ТОЙ ЖЕ БОЛЕЗНИ — СОЗНАТЕЛЬНО, и это строка для следующего.**
Сохранённое утверждение `strings.Contains(out, "EMPTY completion")` стоит на ТОМ ЖЕ механизме, который я
@ -671,83 +507,6 @@ basis="none"` — дословно дефект прогона) · `TestACleanBo
Найдено оркестратором при чтении диффа; названо здесь, чтобы следующий, кто придёт сюда по D39.171, видел,
что половина известна и оставлена намеренно, а не пропущена.
## 9.11 ПРИЁМКА: три блокера и пять дофиксов — что было не так и что сделано
Приёмка вернула пак на доработку. **Все три блокера подтверждены мной исполнением, а не приняты на слово**,
и каждый закрыт с посадкой.
### Блокер 1 — A0 содержал ХУДШУЮ версию болезни, от которой построен
Банк-роли `chunk_status` **не пишут вовсе** (`grep -c UpsertChunkStatus terminologist.go` → 0; контроль:
`stagerun.go` → 2, инструмент работает). А `shipped` определялся через `FinalHash` из `chunk_status`
**весь терминологический контур по построению не мог быть «отгружен»** и целиком уезжал в потери:
$0.04980482 из «потерь» $0.133 — **завышение 59.8%**, хуже, чем 29.2% наивного среза `ok=0`, который мой
же файл в шапке за это отвергает. На ЗДОРОВОЙ книге оператору печаталось «85.7% did NOT become shipped text».
**Сделано:** четвёртый класс **BANK** («купил терминологию, не текст»), опознаваемый по синтетической
стадии; `LostUSD` его исключает; текст сменён на «bought NOTHING — neither text nor terminology».
⚠ Суперсед банк-батча ОСТАЁТСЯ потерей — контур пере-покупается с ростом черновика (строка 233), и
амнистии тут быть не должно.
**Посадки:** `TestAHealthyGlossaryPassIsNotReportedAsALoss` (мутация «убрать класс» даёт
`WorstPosition:book/batch0/terminology` — сурфейс указывает оператору на здоровый батч) ·
`TestAReboughtGlossaryBatchIsStillALoss`.
**САНКЦИЯ НА СМЕНУ УТВЕРЖДЕНИЯ.** Мой `TestTheDecompositionIsNotTheOkColumn` ПИННИЛ дефект — требовал
`WithheldUSD == 0.009` про успешный вызов классификатора. Смена утверждения **разрешена оркестратором
явно 31.08** при приёмке (тест фиксировал поведение, признанное неверным); ссылка стоит в комментарии
самого теста. Это не подгонка под зелень, и запись здесь — чтобы следующая сессия не прочла это как
прецедент «так можно».
### Блокер 2 — A6 был починен на ЧИТАЮЩЕЙ поверхности и мёртв на ДЕНЕЖНОЙ
Зонд `sourceMovedUnderTheRows()` спрашивал о ВАЛИДНОСТИ манифеста, а `translate` персистит манифест
**раньше** гейта согласия (`bookrun.go:187` против `:222` — проверено). ⇒ к моменту вопроса сайдкар уже
описывал НОВЫЙ исходник, зонд отвечал «ничего не двигалось», дорогая сверка пропускалась, и **гейт
согласия на пере-оплату не срабатывал на правке исходника на месте** — то есть ровно там, ради чего A6 и
заведён. Мой первый тест этого не поймал, потому что бил в `Status`, а не в `TranslateBook`.
**Сделано:** ответ зонда ФИКСИРУЕТСЯ до перезаписи сайдкара (`noteSourceVintage()` перед
`persistManifest`) и кэшируется — это же закрывает **Д4** (зонд спрашивался ПО СТРОКЕ и каждый раз хешировал
весь исходник, O(строк × байт) на $0-пути).
**Посадка написана ДО правки и была красной:** `TestTheCONSENTGateSeesAnInPlaceSourceEdit`.
### Блокер 3 — вердикт «предъявлено» против неработающего пути
Снят вместе с блокером 2; в §1 вердикты A0 и A6 стояли «предъявлено» на неполной проверке. Оба
пере-предъявлены посадками выше.
### Дофиксы
* **Д1 — `StreamVersion` не был бампнут.** Я добавила поле `volume` в кадр `finished` и оставила `"1.1"`
при том что правило «поле бампает минор» записано ДОСЛОВНО в комментарии НАД этой же константой, в
файле, который я правила. Поток нёс факт, о котором его собственная версия говорила, что его там нет —
ирония по существу пака. **`"1.2"` + абзац.** Платформенное зеркало не тронуто (мажор совпадает).
* **Д2 — базис расходился между `status` и `export`** на одной книге: `unknown` против `none`. Это строка
239, воспроизведённая В ЛЕКАРСТВЕ ОТ НЕЁ. **Сделано:** одна общая предпосылка `driftCheckable`.
Посадка `TestBothSurfacesReachTheSameBasisOnTheSameBook` на трёх состояниях книги; мутация даёт ровно
`status="unknown" export="none"`.
* **Д3 — носитель обрезки доехал только для рендера**, а инцидент прогона был на КЛАССИФИКАТОРЕ (у него
свой бюджет `classify_budget_usd`, и пробит был именно он). **Сделано:** отдельное поле
`ClassifyBatchesDropped`, оба названы в логе.
* **Д4** — закрыт вместе с блокером 2 (кэш); слово «cheap» из комментария убрано.
* **Д5** — пинг 8 пережил снятие довода §5.3 п.3: исправлен НА МЕСТЕ, а не удалён, чтобы след ошибки
остался. «Незагейченность» названа полностью: **Г1 не построен · Г2 не построен · Г3 построен точечно ·
A8 построен · Г4 НЕ ПОСТРОЕН**, и §2.5 объявлена НЕЗАГЕЙЧЕННОЙ статьёй.
### Решение оркестратора, которое я НЕ принимала сама: порог согласия БЕЗ контура
Мой слагаемый A10 попал не только в проекцию, но и в БАЗУ порога согласия — 5% считались от суммы с
контуром, то есть **денежный гейт стал слабее, и я этого не назвала.** Решение оркестратора: порог
governs ПЕРЕ-оплату, а контур ею не является. **Сделано:** порог берётся до слагаемого; публикуемая
проекция контур сохраняет. Посадка `TestTheContourDoesNotWeakenTheConsentGate` — мутация «свернуть контур
в базу порога» даёт `got: <nil>`, то есть пере-оплата проходит НЕСПРОШЕННОЙ.
### ⚠ ТРИ ЧУЖИХ ТЕСТА — САНКЦИОНИРОВАННАЯ ПРАВКА СЦЕНАРИЯ (не утверждения)
Починка блокера 2 меняет поведение денежного пути, и три ратифицированных теста падали — все на одном:
их сценарий правит исходник и ждёт, что `translate` пройдёт. Ни один не про согласие.
**Я пингнула ДО правки** (в отличие от первого случая за этот пак) и предложила три варианта с доводами,
включая довод против собственного предпочтения. Оркестратор выбрал (а) с двумя условиями, обе выполнены:
* **условие 1 — тронут только сценарий.** Предъявлено диффом: `runner_test.go` и `bookbuild_test.go`
**0 удалённых строк**, ровно по одной добавленной строке `AcceptRebill` каждая; `bankfixpack_test.go`
та же одна строка (его 2 удалённые относятся к ОТДЕЛЬНО санкционированной правке предпосылки, §9.10).
Ни одна строка `t.Error`/`t.Fatal` не тронута.
* **условие 2 — гарантия стала явной.** Все три ссылаются в комментарии на
`TestTheCONSENTGateSeesAnInPlaceSourceEdit`, чтобы читатель видел, КУДА уехала гарантия.
**Санкция оркестратора от 31.08, при приёмке этого пака.** Три касания чужих тестов за пак — много, и
каждое обязано читаться как разрешённое, а не как прецедент.
## 10. Изменённые файлы
| файл | что |
@ -769,19 +528,22 @@ governs ПЕРЕ-оплату, а контур ею не является. **С
| `internal/store/outbox.go` | A11: `AnnouncedOnceKeys()` — читающий метод строки 232 |
| `cmd/tmctl/render.go` | A7: строка дрейфа больше не утверждает пере-оплату; A12: базис вслух; A0: секция денег |
| 11 новых `*_test.go` + `testdata/prompt-labels.json` | 33 посадки |
| `internal/pipeline/bankfixpack_test.go` | ⚠ ЕДИНСТВЕННОЕ касание чужого теста — ПРЕДПОСЫЛКА, разбор §9.10 |
| `internal/pipeline/bankfixpack_test.go` | ⚠ касание чужого теста — ПРЕДПОСЫЛКА, разбор §9.10 |
| `backend/docs/*.md` (3) | записка-план, дизайн закона, этот отчёт |
| `docs/PROGRESS.md` | секция «Бэкенд» — единственное исключение канона |
**Чужого не тронуто, кроме одной предпосылки, названной в §9.10 и оставленной оркестратором.**
Ни `platform/`, ни `frontend/`, ни `eval/`, ни `books/`, ни остальное в `docs/`.
**Касаний ЧУЖИХ тестов три класса, все санкционированы ЯВНО и ни одно не тихое** (условия и разбор —
D39.182 §2): ПРЕДПОСЫЛКА `bankfixpack_test.go` (§9.10) · УТВЕРЖДЕНИЕ `TestTheDecompositionIsNotTheOkColumn`
(§9.1) · СЦЕНАРИЙ трёх ратифицированных тестов, которых починка A6 заставила проходить гейт согласия
(`runner_test.go`, `bookbuild_test.go`, `bankfixpack_test.go` — по ОДНОЙ добавленной строке `AcceptRebill`,
ноль удалённых строк, ни одна `t.Error`/`t.Fatal` не тронута; каждый ссылается в комментарии на
`TestTheCONSENTGateSeesAnInPlaceSourceEdit`, куда уехала гарантия).
Ни `platform/`, ни `frontend/`, ни `eval/`, ни `books/`, ни остальное в `docs/` не тронуто.
## 11. Последний абзац — состояние, не план
## 11. Состояние на закрытии пака
Пак закрыт обеими фазами. Закон ратифицирован и применён к десяти экземплярам; корпус вырос до
Обе фазы отработаны. Закон ратифицирован (D39.181) и применён к десяти экземплярам; корпус вырос до
четырнадцати, и четыре лишних нашла мерка, а не заказ. Тридцать три посадки, каждая красная под своей
мутацией; батарея зелёная с кодом, снятым с самого `make`, и тремя названными скипами. Три довода я сняла
как неверные — по A9, по биективности словарей и по 22.6% — и все три были моими собственными и
названными сильными. Один порядок действий я нарушила и зафиксировала это без смягчения. Долг по посадке
A9 и вторая половина чужого теста на дефектном механизме оставлены НАЗВАННЫМИ, а не закрытыми. Ничего не
закоммичено; лендинг за оркестратором.
мутацией; батарея зелёная с кодом, снятым с самого `make`, и тремя названными скипами. Открытое оставлено
НАЗВАННЫМ, а не закрытым: долг по посадке A9 (§8 п.1) и вторая половина чужого теста на дефектном
механизме (§9.10). Пак принят и заленджен 31.08 — `bb541a8`, нота D39.182.

View file

@ -1,80 +1,20 @@
# План дофикса-2 входной двери шва (промт BACKEND_SEAM_FIX2_SESSION_PROMPT.md)
# Записка-план дофикса-2 входной двери шва (13 пунктов §4)
> Записка-план по §7 промта. Комплектность против §4 сверена механически:
> `grep -cE '^### 4\.' docs/BACKEND_SEAM_FIX2_SESSION_PROMPT.md`**13**.
> Ответ на развилку §7 (один пак или два): **один пак**, довод промта принимаю и добавляю свой —
> §4.3 (авто-провод при поднятом флаге) меняет ту же ветку `runBankMiningStop`, чьё сообщение стопа
> переписывает §4.12, а отчёт двери (§4.5) обязан узнать класс §4.7 — раздельные паки резали бы
> одну функцию и один enum пополам.
> **Записка ОТРАБОТАВШЕЙ сессии, а не задание: не исполнять.** Дофикс-2 закончен, принят и залендён —
> D39.158 (стоп банка — флажок; полоса в два яруса и класс 15; отчёт двери v2; пути решений — только
> конвенцией). Что реально сделано, с доводами и остатками — `backend/docs/SEAM_PACK_FINDINGS.md`:
> состав и пофайловый список §36, решения §4.1§4.3 с доводами §37, замеры §38, живые пробы §39,
> «что НЕ удалось» §41, попунктный чек всех тринадцати §43. Промт архивирован:
> `docs/archive/prompts/BACKEND_SEAM_FIX2_SESSION_PROMPT_2026-08-27.md`.
## Порядок работ (зависимость §4.2 соблюдена: атомарность карты — первой)
## Названные заранее развилки — и что с ними стало
1. **§4.10** — карта подписи: `writeFileAtomic` вместо голого `os.WriteFile`; конверт
`seed.SignatureMap{map_version, map_id}` с инлайн-телом прежней схемы; `map_id` = sha256 от
отрендеренного тела (идентичность содержимого, не криптодоказательство). Легаси-карта без
конверта читается (страховка апгрейда посреди подписи). Карта НЕ накопитель — слияния нет.
2. **§4.9** — (а) `filepath.Clean` в резолве абсолютных путей `LoadBook` (Join и так чистит);
(б) ключи `mined_delta`/`mined_rejects` СНЯТЫ: retired-key отказ по прецеденту `adult:`
(громкое имя лечения, а не голый strict-decode), пути — только конвенцией от каталога и
`book_id`; `*Declared` умирает, `decisionFilePresent` упрощается.
3. **§4.1§4.3** — миграция v16: таблица `bank_stop_presented (book_id, surface)` — плоское
множество нормализованных поверхностей всех предъявленных кластеров (Src + Aliases); рост —
только вширь, обрезки нет. Предикат «есть ли новое» = существует терм ИТОГОВОЙ карты (обе
секции, после потолков), чей кластер не пересекается с памятью. Стоп и авто-провод — ДВА
решения: стоп = флаг ∧ editWave ∧ новое; авто-провод (авто-банк + пере-сид + экспорт) — на
ЛЮБОМ неостановившемся прогоне, включая «флаг поднят, нового нет» (новая явная строка лога).
Порядок записи: карта (атомарно, §4.10) → память, в момент СТОПА. Развилки, решённые здесь:
отказ записи памяти НЕ валит состоявшийся стоп (громкая строка; повтор — доброкачественный
лишний стоп); остановившийся прогон авто-банк НЕ пишет (граница владельца; возобновление без
решений догонит на следующем прогоне — названное решение, не случайность); черновая волна без
editWave: карта пишется, стопа и памяти нет (доброкачественно, названо).
4. **§4.5 + §4.7 + §4.8** — отчёт делится на вердикт (`decisionVerdict`) и исход; исходы —
явный тип (projection · refused · noop · stopped · written · write-failed), ОДИН конструктор
`finishReport` заполняет исходные поля свитчем; для «записано» пост-состояние — перечитанные
файлы. Отчёт получает `written_delta`/`written_rejects` (пофайловая правда §4.7) и честные
значения `mode`; версия отчёта → v2 (словарь mode расширился — конверт обязан двинуться).
Полоса: пол переформулирован («нет работы на откат; повтор безопасен»), классы объявляют свои
инварианты, новый класс `write_incomplete` (exit 15 — ратификация значения на оркестраторе при
лендинге) на оба исхода записи; отказ по обоим капам печатает отчёт (сведение контракта к
одному носителю). Запись: подготовить ОБА temp-файла (write+sync), потом два rename, потом
fsync каталога; отказ каталожного синка — класс `write_incomplete` с оговоркой долговечности.
5. **§4.6** — `SignatureState` пере-документируется: `undecided` считает нерешённые поверхности
последней карты (информация для экрана подписи) и БОЛЬШЕ НЕ отвечает на вопрос о стопе —
ответ требовал бы намайненного набора СЛЕДУЮЩЕГО прогона, которого нет ни у двери, ни у
памяти; память дверь не читает (и потому read-only контракт не трогается). `Unreadable`
расширяется до «не измерено» (нужно кап-отчётам §4.7). Поле не удаляется.
6. **§4.12** — снос наследия СПИСКОМ: шапка mining.go · комментарий у loadMinedRejects ·
хвост unsignedEngineSurfaces · сообщение стопа · renderSignatureStop · doc WaveSignatureStop;
цепной тест переписывается под новую семантику (премисс-проверки фикстуры сохраняются) +
вторая цепь «владелец не решил ничего — стоп гаснет памятью»; пинги чужим зонам с ПОРЯДКОМ
(сброс флага платформой умирает только ПОСЛЕ движкового лендинга §4.3) и миграционная нота
(пустая память → один доброкачественный стоп; v16 → `tmctl migrate`).
7. **§4.11 + §4.4 + §4.13** — посадки и пины по таблице слепых пятен + пин семантики §4.1
(краснеет при возврате старой формулы); §4.4 не лечится — пере-замер своей командой (числа
промта без первоисточника) и пинг; платных вызовов ноль — фейковый провайдер, харнесс
miningstop_join, живая проба на копии стенда.
## Пофайлово
- `internal/seed/seed.go|decode.go``SignatureMap` + `DecodeSignatureMap` + `SignatureMapID`.
- `internal/miner/miner_emit.go``DeltaFile` (структура), `DeltaYAML` = рендер её же.
- `internal/pipeline/mining.go` — конверт+атомарная запись карты; предикат новизны; разделение
стопа и авто-провода; запись памяти; новые строки лога; снос формул старой модели.
- `internal/store/migrate.go` + новый `stopmemory.go` — v16, `StopPresentedSurfaces`,
`MarkStopPresented`.
- `internal/pipeline/bankdecisions.go` — вердикт/исход/конструктор; кап-отчёты; staged-запись;
`written_*`; версия отчёта v2; пере-док SignatureState.
- `internal/pipeline/refusal.go` + `cmd/tmctl/main.go` — ярусы полосы, `write_incomplete`/15.
- `internal/pipeline/artifact.go` — staged-вариант атомарной записи + fsync каталога.
- `internal/config/book.go` — Clean; retired-ключи; конвенция путей решений.
- `cmd/tmctl/render.go|bankapply.go` — тексты и контракт печати отчёта.
- Тесты: `bankchain_test.go` (переписать) · пины §4.11 · пин семантики §4.1 ·
`cmd/tmmutate/mutations.json` (+записи на каждую правку §4).
## Развилки, которые вижу заранее
- Конверт ломает жанр карты для внешних читателей (`eval/bank_autonomy/parse_common.py`) — пинг,
не правка (чужая зона); движковые читатели переведены все (единственный — signatureState).
- `seed-lint` карту с конвертом не примет (его предмет — сид и дельта владельца, не карта);
названо, не чинится.
- Числа §4.4 пере-меряю своей командой до пинга; если картина та же — пинг в прежней формулировке.
- **Конверт карты подписи ломает её жанр для внешних читателей** (`eval/bank_autonomy/parse_common.py`
парсит `mined-signature.yaml`) — чужая зона, ушло пингом полигону, а не правкой; пинг записан в
`docs/archive/PROGRESS-2026-08-backend.md` (искать `parse_common`). Движковые читатели переведены все.
- **⚠ Живой остаток: `seed-lint` карту подписи с конвертом не примет** — его предмет сид и дельта
владельца, а не карта. Названо и сознательно НЕ чинилось; носителя этого факта, кроме этой строки,
нет.
- Числа хвоста за потолком, которые заказ цитировал без первоисточника, сессия пере-мерила своей
командой (§38) и положила первоисточник в дерево постоянным якорем-пином
`internal/miner/emissioncap_test.go`; сам хвост заведён строкой бэклога 223 (D39.158 п.9).

View file

@ -1,30 +1,12 @@
# План фикс3 (промт BACKEND_SEAM_FIX3_SESSION_PROMPT.md)
# Записка-план фикс3 «входная дверь шва» (6 пунктов)
> Комплектность: `grep -cE '^### 4\.' docs/BACKEND_SEAM_FIX3_SESSION_PROMPT.md`**6**.
> ⚠ **Записка ОТРАБОТАВШЕЙ сессии, а не задание: не исполнять.** Пак закончен, принят и залендён —
> D39.158. Что реально сделано по каждому из шести пунктов, с командами, остатками и проверкой
> закрытия — `backend/docs/SEAM_PACK_FINDINGS.md` §46. Промт архивирован:
> `docs/archive/prompts/BACKEND_SEAM_FIX3_SESSION_PROMPT_2026-08-27.md`.
Порядок — сначала два пина (воспроизведение мутации ДО пина, правило «сначала посадка»), потом снос,
потом тексты, потом §4.6.
1. **§4.1** — воспроизвести мутацию приёмки (отказ записи карты → WarnContext) и убедиться в зелёной
батарее; пин приёмом приёмки: путь карты занят непустым каталогом → прогон обязан вернуть ошибку,
`StopPresentedSurfaces` книги обязан остаться пуст. Запись каталога FX23.
2. **§4.2** — воспроизвести утверждение панели (docsReRead → docsFromResult на исходе write_incomplete
→ существующие пины зелёные); пин: карта с нерешённой поверхностью + документ, решающий её + отказ
записи (не легло ничего) → `signature.undecided` обязан быть 1 (диск), не 0 (намерение). FX24.
3. **§4.5** — снос машинной таблицы `.bank-stop.json`: писатель, дисциплина отказа, пути, поле
`TableJSONPath`, строка рендера, json-теги DTO, тесты таблицы. `.bank-stop.txt` не трогаю.
Проверка «читателя нет» — свой греп по всем зонам до сноса.
4. **§4.4** — зачистка грепом: формы `re-fire`/`re-fires`/`refire` + `stop clears`/`clears the stop` +
`never.*stop` рядом с decline/reject + русские следы; правильный текст — из D39.144 (отказ
исключает терм из ЭМИССИИ: он не возвращается в карту и в авто-банк; про стоп он не говорит ничего —
стоп это вопрос памяти).
5. **§4.3** — `runner.go:206` и `panic_exit_test.go:78` → двухъярусная формулировка.
6. **§4.6** — проверить утверждение консилиума по `bankexport.go`; если подтверждается — аддитивные
поля идентичности границы (run id + метка границы, обе уже есть у прогона, детерминизм не задет);
версию конверта не двигать (добавление аддитивно).
Развилки: у §4.5 возможен читатель, которого не нашла приёмка — ищу сам до сноса, нахожу → пинг, не
молчаливое сохранение. У §4.6 законен отказ с аргументом — решу после чтения фактуры.
Финал: батарея + стенд-батарея + tmmutate (60 записей) на копии + FINDINGS §46 + итог в журнал +
проверка закрытия отдельным не-чинившим агентом.
Порядок, выбранный до кода: два пина (мутация воспроизводится ДО пина — правило «сначала посадка,
потом пин») → снос машинной таблицы `.bank-stop.json` → зачистка отменённой формулы стопа в текстах →
якорь свежести банк-экспорта. Обе названные развилки решены исполнением и описаны в §46: читателя
сносимой таблицы сессия искала своим грепом по всем зонам ДО сноса, у §4.6 отказ с аргументом был
законен и не понадобился.

View file

@ -1,82 +1,15 @@
# План дофикса «входная дверь шва» (по промту `docs/BACKEND_SEAM_FIX_SESSION_PROMPT.md`)
# Записка-план дофикса «входная дверь шва» (16 пунктов §4)
Бэкенд-сессия 25.08.2026, продолжает незакоммиченное дерево пака. Заказ — 16 пунктов §4
(`grep -cE '^### 4\.'` → 16). Записка обязана быть комплектной механически, поэтому таблица ниже
перечисляет ВСЕ шестнадцать, включая те, которые заканчиваются аргументированным отказом.
> ⚠ **Записка ОТРАБОТАВШЕЙ сессии (25.08.2026), а не задание: не исполнять.** Дофикс закончен, принят и
> залендён — D39.158. Что реально сделано по каждому из шестнадцати пунктов, с командами и остатками —
> `backend/docs/SEAM_PACK_FINDINGS.md` §26 (попунктный чек), §15 (замер и механика корня формата
> `yaml.v3` — шире и точнее этой записки), §24 (что НЕ удалось), §23 (пинги). Промт архивирован:
> `docs/archive/prompts/BACKEND_SEAM_FIX_SESSION_PROMPT_2026-08-27.md`.
## Базовая точка (снята ДО правок)
## Чего этот дофикс НЕ делал — и это осталось верным
`make battery` на нетронутом дереве пака: `EXIT=2`, красен ровно
`TestKillMinus9LosesAtMostOneCall` — один из двух названных промтом пред-существующих `-race`-флейков
(`internal/store`). То есть флейк воспроизвёлся на дереве, к которому я ещё не прикасался; это моя
собственная атрибуция, а не наследованная.
## Что уже замерено до кода (§4.1, корень)
`gopkg.in/yaml.v3 v3.0.1`, проба вне рабочего дерева, матрица «значение → рендер → разбор» на обоих
документах. Итог шире, чем говорит промт, и шире, чем говорила записка прошлой сессии:
| значение поля | что рендерится | round-trip |
|---|---|---|
| `"\nтекст"` | `note: \|4-` + строка | **не разбирается** (`did not find expected key`) |
| `"\n\nтекст"` | `\|4-` + пустая строка | **не разбирается** |
| `"\n\tx"` | `\|4-` + таб | **не разбирается** |
| `"\n"` | `\|4+` | разбирается, но **значение `""`** — тихая потеря |
| `"\n x"` | `\|4-` + 10 пробелов | разбирается, но **значение `"x"`** — тихая потеря |
| `"текст\n"`, `"a\nb"`, `" x"`, `"\tx"`, `"\r\nx"` | скаляр/блок | ок |
Два вывода, оба несущие:
1. **Клейм промта «бьёт по ЛЮБОМУ свободному текстовому полю» ВЕРЕН**, а поправка записки прошлой
сессии («`dst` и `sense` не ломаются») — НЕТ: ломается любое строковое поле обоих документов
(проверено на `src`/`dst`/`sense`/`note` и на `rejects[].note`). Триггер — ведущий `\n`, а не поле.
2. **«Документ разбирается» — недостаточный инвариант.** Две формы разбираются и МЕНЯЮТ значение.
Заказанная §4.1 проверка `render → parse` их пропустит: дверь отчитается `applied`, файл загрузится,
а заметка владельца будет другой. Инвариант обязан быть «прочиталось И совпало».
Механика (тоже замерена): дефект — рассогласование индикатора отступа блочного скаляра с реальным
отступом родителя внутри элемента последовательности; на глубине 1 (`{note: "\nx"}` без `- `) тот же
вход рендерится и читается корректно, а `SetIndent(2)` случайно попадает в согласие. **Путь «починить
рендер стилями `yaml.Node`» закрыт:** `(*yaml.Node).Encode` внутри сам гоняет marshal→unmarshal и
падает на том же входе — узел построить нельзя.
## Порядок работ
Волнами, снизу вверх: формат → чистая семантика → конфиг/CLI → обвязка → пины. Батарея после каждой
волны, `make battery-stand` в конце.
| № | что делаю | где |
|---|---|---|
| **4.1** | нормализация текстовых полей документа (`TrimSpace`, ОДНО правило на все поля обеих схем) + рендер, ДОКАЗЫВАЮЩИЙ себя: `parse(render(A))` обязан совпасть с нормализованным `A`, иначе ошибка. Проверка распространяется на ОБА документа; байты, которые дверь проверила, она же и пишет (рендер один, не два). Фаззер пути ДВЕРИ: произвольные строки полей `Decision``ApplyDecisions` → рендер → разбор | `seed`, `membank` |
| **4.2** | пред-существующая проблема опознаётся по СУБЪЕКТУ: многосубъектный вердикт загружаемости раскладывается по термам до сравнения `до/после` | `membank` |
| **4.3** | `config.LoadBook` отказывает при совпадении путей. Шире буквы заказа на одну строку: попарно различны должны быть четыре пути, которые движок пишет или читает как РАЗНЫЕ форматы (`project_db`, `glossary_seed`, `mined_delta`, `mined_rejects`) — `project_db == mined_delta` сегодня означало бы YAML поверх sqlite. Проверю на всех `book.yaml` репозитория и стенда, что живых нарушителей нет | `config` |
| **4.4** | оба гарда — на ПРИСУТСТВИЕ флага через `fs.Visit` (прецедент `ceilingGiven` в том же файле) | `cmd/tmctl` |
| **4.5** | кап на размер документа решений + строка отказа полосой. Число выбираю после СВОЕГО замера памяти (методика будет названа); хвост про exit 2 при OOM — пингом | `pipeline` |
| **4.6** | конверт путей на поверхности, которую потребитель читает (`manifest --json`). Компромисс назову вслух: манифест — про дерево глав, путь артефакта там гость | `pipeline` |
| **4.7** | правило алиасов судит НАБОР: одобряемые тем же вызовом термы участвуют в проверке `aliasOwner` | `membank` |
| **4.8** | перевёрнутое окно глав (`until < since`, оба ненулевые) — отказ | `membank` |
| **4.9** | **вероятно отклоню как предсказание и заменю на факт.** Дверь не знает, сколько поверхностей осталось за капом реверс-секции — это знает майнер на следующем проходе. Кандидат: отчёт называет, что решил ЭТОТ вызов, и не читается как «стоп погас». Решу замером, видит ли дверь остаток вообще | `pipeline` |
| **4.10** | обработчик `SIGTERM`: бросить работу, отпустить лок, не записать ничего. **Сначала замерю длительность вызова ПОСЛЕ капа §4.5** — если максимум по капу это десятки миллисекунд, скажу это аргументом | `pipeline`/`cmd` |
| **4.11** | десять посадок (строка (д) — три), пин на каждую, каждый краснеет на своей мутации. Харнесс мутаций пишу заново с отпечатком целей до/после; кладу его в репозиторий — приёмка сажает мутации тоже, а инструмент каждый раз пишется с нуля | все |
| **4.12** | тест цепи на фейковом провайдере (`miningstop_join_test.go`-харнесс): стоп → `bank-apply` → резюм → стоп погас, редакторская волна пошла. Плюс явная фиксация, что карта подписи после погасшего стопа остаётся на диске | `pipeline` |
| **4.13** | восемь мелочей: по каждой решение и довод, чиню или отклоняю — списком в отчёте, без «кажется» | разные |
| **4.14** | довод отказа `--keys-file` покрывает всех, кого ветка отвергает (`redrive` — платный, `backup`/`migrate` — деплой-шаги). Развилку про канал ключей `redrive` НЕ решаю | `cmd/tmctl` |
| **4.15** | три существующих пина: вердикт `Preexisting` исполнить всеми четырьмя проверками · четвёртый оракул фаззера снять с тавтологии · `TestDecisionByBankID` сверять с НЕЗАВИСИМО посчитанным id | тесты |
| **4.16** | платных вызовов ноль. Рантайм по платному пути остаётся PLAUSIBLE | — |
## Развилки, которые понесу владельцу/оркестратору (§11)
1. **§4.1 шире заказа.** Заказанный round-trip `render → parse` не ловит две тихие потери значения;
делаю строже («прочиталось И совпало») и говорю об этом прямо, потому что это меняет ответ двери
на входы, которые заказ считал безопасными.
2. **§4.3 шире буквы** на одну проверку — см. таблицу.
3. **§4.9** — скорее всего отказ от предсказания в пользу факта.
4. **§4.10** — возможен ответ «механизм без носителя после §4.5»; решит замер, не рассуждение.
5. **§4.6** — сделаю, компромисс назову.
## Чего НЕ делаю
Пинги приложения «дофикс НЕ исполняет» (exit 2 при OOM, `readEngine` платформы, канал ключей
`redrive`, `rubyToCandidates`, слепота `TestDispatchCommandsCoversTheSwitch` к константам, схема сида
без `omitempty`, лок на проекции, карта подписи после стопа) — не «допиливаю попутно». Механизм против
капа реверс-секции не строю. Пере-именование банк-экспорта не трогаю. Словарь решений не расширяю.
Пинги, которые дофикс сознательно НЕ исполнял: `exit 2` при OOM · `readEngine` платформы · канал ключей
`redrive` · `rubyToCandidates` · слепота `TestDispatchCommandsCoversTheSwitch` к константам · схема сида
без `omitempty` · лок на проекции · карта подписи после стопа. Тела и диспозиции — FINDINGS §10 и §23.
Механизм против капа реверс-секции не строился, пере-именование банк-экспорта не трогалось, словарь
решений не расширялся.

File diff suppressed because it is too large Load diff

View file

@ -1,70 +1,22 @@
# Записка-план: бэкенд-пак «входная дверь шва» (движковая половина D39.156)
Сессия бэкенда, 24.08.2026. Промт — `docs/BACKEND_SEAM_PACK_SESSION_PROMPT.md`.
Доказательная база и посадки — `backend/docs/SEAM_PACK_FINDINGS.md` (пишется по ходу).
> ⚠ **Записка ОТРАБОТАВШЕЙ сессии (24.08.2026), а не задание: не исполнять.** Пак закончен, принят и
> залендён — D39.158. Что реально построено, чем доказано и что осталось — доказательная база
> `backend/docs/SEAM_PACK_FINDINGS.md` (состав §0, попунктный чек заказа §12, пинги §10). Промт
> архивирован: `docs/archive/prompts/BACKEND_SEAM_PACK_SESSION_PROMPT_2026-08-27.md`.
## 0. Точка отсчёта, снятая ИСПОЛНЕНИЕМ (до правок)
## Развилки, которые сессия решала своим доводом
| факт | команда |
|---|---|
| `go build ./...` · `go vet ./...` · `go test ./... -count=1` — зелены | из `backend/` |
| стенд-батарея с переопределениями: красен ровно `TestMinerFullBookParity`, причина — отсутствующий `eval/exp16/data/jieba_dict_general_zh.txt` | `TM_MINER_PARITY=1 TM_CHECKER_LABELS=1 TM_CHECKER_LABELS_DIR=<repo>/books/gu-zhenren/labels TM_MINER_PARITY_RECORDS=<repo>/books/gu-zhenren/rerun/records.json TM_MINER_PARITY_SEED=<repo>/books/gu-zhenren/guzhenren-seed-v2.yaml go test ./... -count=1` |
| рабочее дерево `backend/` чистое; незакоммиченная чужая работа — только в `eval/` | `git status --porcelain` |
**(а) Имена файлов решений — каталог книги, а не сайдкар при базе.** Все банк-сайдкары движка висят на
`project_db` (`.bank.json`, `.auto-bank.yaml`, `.mined-signature.yaml`), но `project_db` может указывать
КУДА УГОДНО (в т.ч. за пределы каталога книги), а решения пользователя — это его данные, они обязаны
ехать с книгой в бэкапе и экспорте каталога. Отсюда `<dir книги>/<book_id>.mined-delta.yaml` и
`<book_id>.mined-rejects.yaml`: префикс `book_id` повторяет механизм `<book_id>.db` и не даёт двум
книгам в одном каталоге столкнуться. ⚠ Объявляемые ключи `mined_delta`/`mined_rejects`, которые эта
записка ещё предполагала, СНЯТЫ фикс2 — путь стал чистой функцией каталога и `book_id` (D39.158 п.5);
довод конвенции пережил снятие ключей, форма — нет.
## 1. Комплектность против §4 промта (сверка МЕХАНИЧЕСКАЯ)
| § | заказ | форма, которую я выбрал | где |
|---|---|---|---|
| 4.1 | дефолты пустых ключей `mined_delta`/`mined_rejects` по конвенции каталога книги | `<dir книги>/<book_id>.mined-delta.yaml` и `<book_id>.mined-rejects.yaml`; дефолт ТОЛЬКО на пустом ключе; отсутствующий дефолтный файл = «решений нет» | `internal/config/book.go`, `internal/pipeline/mining.go` |
| 4.2 | $0-глагол приёма правок банка | `tmctl bank-apply --config <book.yaml> --decisions <decisions.json> [--dry-run]` | `internal/membank/decisions.go` (чистая логика), `internal/pipeline/bankdecisions.go` (обвязка), `cmd/tmctl/bankapply.go` |
| 4.3 | путь к файлу ключей аргументом | `--keys-file <path>` ТОЛЬКО у `translate`; грузится ПЕРВЫМ; названный и нечитаемый = отказ конфигом (10) | `cmd/tmctl/invocation.go`, `cmd/tmctl/dotenv.go`, `cmd/tmctl/main.go` |
| 4.4 | строгость загрузчика сида + тест на unknown-key | strict-декодер схемы в `internal/seed` (`seed.DecodeFile`), им же читается reject-файл | `internal/seed/decode.go`, `internal/membank/memseed.go`, `internal/pipeline/mining.go` |
| 4.5 | путь артефакта и версия документа в `status --json` | `status_version` + аддитивный объект `artifacts{project_db,bank_export}` (абсолютные пути) | `internal/pipeline/status.go` |
| 4.6 | дефолт стендового корпуса в трёх тест-хелперах | маркерный резолв корня репозитория (`repoFile`), корпус — `<repo>/books` | `internal/miner/miner_parity_test.go`, `internal/checks/labelharness_test.go`, `internal/membank/labelharness_test.go` |
| 4.7 | платных вызовов ноль | глагол $0 по построению; `--keys-file` — юнит-тесты + фейковые ключи | — |
## 2. Порядок работ
1. §4.6 (дешёвая, независимая) → стенд-батарея зеленеет без переопределений.
2. §4.4 строгость схемы — она нужна ДО глагола: глагол читает и пишет те же файлы, и нестрогий
читатель обесценил бы строгого писателя.
3. §4.1 дефолты путей — без них глаголу некуда писать на книге, которая ключей не объявляла.
4. §4.2 глагол (основной кусок).
5. §4.3 `--keys-file`.
6. §4.5 версия и пути артефактов в `status --json`.
7. Адверсариальные посадки, живая проба на копии стендовой книги, отчёт.
## 3. Развилки, которые я вижу, и как решаю
**(а) Имена дефолтных файлов — каталог книги или сайдкар при базе?**
Все банк-сайдкары движка висят на `project_db` (`.bank.json`, `.auto-bank.yaml`, `.mined-signature.yaml`).
Беру всё-таки КАТАЛОГ КНИГИ с префиксом `book_id`, ровно как `project_db` по умолчанию:
`project_db` может указывать КУДА УГОДНО (в т.ч. за пределы каталога книги), а решения пользователя —
это его данные, они обязаны ехать с книгой в бэкапе и экспорте каталога. Префикс `book_id` повторяет
механизм `<book_id>.db` и не даёт двум книгам в одном каталоге столкнуться.
**(б) Форма проекции до мутации.** `--dry-run`, прецедент — `redrive --dry-run` и строка 124(в)
(`resnapshot --dry-run` поименован там дословно). Проекция получается БЕЗ мутации: с флагом не
пишется ни один байт.
**(в) Код выхода для отвергнутого набора решений.** Занятый лок — ровно 12 (заказано). Для
отвергнутых решений НЕ завожу новый номер полосы: беру 10 (`config_invalid`). Довод по оси, которая
у D39.134 несущая, — КТО ДЕЙСТВУЕТ: при 12 действует время («подожди и повтори»), при 10 действует
человек. Набор решений, конфликтующий с подписанной строкой сида, чинит человек, а не время.
Отчёт при этом печатается в stdout ВСЕГДА и называет каждый отказ поимённо (прецедент: `status --json`
печатает отчёт и выходит 2).
**(г) Словарь входа глагола.** Ровно словарь УЖЕ публикуемой поверхности банк-экспорта
**(б) Словарь входа глагола.** Ровно словарь УЖЕ публикуемой поверхности банк-экспорта
(`id`/`src`/`dst`/`kind`/`sense`/`since_chapter`/`until_chapter`/`aliases`) плюс `note`. Поля, которого
движок не публикует, во входе нет — в частности `gender` (строка 210: ось мертва в производителях);
строить «на вырост» запрещено §4.3.
**(д) Канонизация файла.** Глагол — единственный писатель своей схемы, поэтому при изменении файл
пере-рендерится целиком из разобранной схемы (комментарии оператора не переживают первую правку).
Это ВИДНО до мутации: проекция несёт `canonical_rewrite`. Порядок термов сохраняется (замена — на
месте, добавление — в хвост), чтобы правка не выглядела как переписывание всего файла.
**(е) Что я НЕ делаю и почему.** Не проектирую «глубину до черновика» (§4.2, прямое слово владельца):
правка едет через `mined_delta` до РЕДАКТОРСКОЙ волны, и ответ глагола говорит это явным полем
`depth: edit_wave`. Не трогаю `redrive` флагом ключей (промт: «флаг только у `translate`») — наблюдение
о том, что `redrive` тоже платный путь, уходит ПИНГОМ, а не тихой правкой.
движок не публикует, во входе нет — в частности `gender`: ось мертва в производителях, а строить
«на вырост» запрещено заказом.