# Записка-план: бэкенд-пак «входная дверь шва» (движковая половина D39.156) Сессия бэкенда, 24.08.2026. Промт — `docs/BACKEND_SEAM_PACK_SESSION_PROMPT.md`. Доказательная база и посадки — `backend/docs/SEAM_PACK_FINDINGS.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=/books/gu-zhenren/labels TM_MINER_PARITY_RECORDS=/books/gu-zhenren/rerun/records.json TM_MINER_PARITY_SEED=/books/gu-zhenren/guzhenren-seed-v2.yaml go test ./... -count=1` | | рабочее дерево `backend/` чистое; незакоммиченная чужая работа — только в `eval/` | `git status --porcelain` | ## 1. Комплектность против §4 промта (сверка МЕХАНИЧЕСКАЯ) | § | заказ | форма, которую я выбрал | где | |---|---|---|---| | 4.1 | дефолты пустых ключей `mined_delta`/`mined_rejects` по конвенции каталога книги | `/.mined-delta.yaml` и `.mined-rejects.yaml`; дефолт ТОЛЬКО на пустом ключе; отсутствующий дефолтный файл = «решений нет» | `internal/config/book.go`, `internal/pipeline/mining.go` | | 4.2 | $0-глагол приёма правок банка | `tmctl bank-apply --config --decisions [--dry-run]` | `internal/membank/decisions.go` (чистая логика), `internal/pipeline/bankdecisions.go` (обвязка), `cmd/tmctl/bankapply.go` | | 4.3 | путь к файлу ключей аргументом | `--keys-file ` ТОЛЬКО у `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`), корпус — `/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` повторяет механизм `.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` тоже платный путь, уходит ПИНГОМ, а не тихой правкой.