# Промт: сессия-БЭКЕНД — Трек A, пак-1 (структурная гигиена + память-корень + export-contract), 2026-07-13

> Рождён из арх-ресета **D39** (`docs/architecture/05-decisions-log.md`) и синк-аудита (`docs/architecture/08-sync-audit-ledger.md`, `09-target-architecture.md`). Это **build-now** трек: чистые фиксы, ресёрч НЕ нужен. Параллельно идёт трек B (полигон-ресёрч нарезки/когезии) — **не твоя зона, не трогай `eval/`**.

## Кто ты и зона
Бэкенд-сессия TextMachine (Go-пайплайн издательского перевода zh/ja/en→ru). **Зона записи — `backend/` только.** Читать можно всё; расхождения — пингом оркестратору в `docs/PROGRESS.md` (не правь чужие зоны). **Сессия не коммитит** — код ревьюит и лендит оркестратор после приёмки.

## Онбординг (порядок)
1. `CLAUDE.md` (гардрейлы) → `docs/architecture/09-target-architecture.md` (целевые слои, §2 слои 4/6/7/2-сеам, §4 трек A) → `docs/architecture/08-sync-audit-ledger.md` (находки этого пака: `L3-*`, `L5-normalization-fused-*`, `L8-*`).
2. `docs/architecture/05-decisions-log.md`: **D39** (арх-ресет), D30.1 (редактор БИЛИНГВ), D2 (диспозиции), D8/D24.4 (снапшот/база dst), D30.3/D33.1/D35.4a (санитайзер/strip-export), D38.3 (инфра-пак).
3. `docs/experiments/00-provider-quirks.md` (ПЕРЕД любыми правками, если тронешь вызовы — тут не должно, но правило).
4. `backend/README.md` + релевантный код (см. задачи).

## ЖЁСТКИЕ гардрейлы (из CLAUDE.md)
- **НИКОГДА не читать `.env`.** 18+ уровень 3 — не открывать `eval/data/*corpus*`, `/home/ubuntu/books/` без прямой задачи (тут не нужно).
- **Снапшот-дисциплина = деньги.** Любая правка, меняющая рендер запроса / выбор инъекции / текст выхода → меняет снапшот (Р6). Это ГРОМКИЙ `--resnapshot` (переоплата книги). **Ты НЕ пере-прогоняешь книгу** (это гейтится треком B). Твоя обязанность: (а) правильно **fold**-ить новую версию поведения в снапшот (`render.go`/`snapshot.go`, паттерн `coverageSnap`/`sanitizerSnap`); (б) обновить golden-фикстуры (`internal/pipeline/testdata/golden/`) под новое поведение — **golden = инвариант №8 (D23)**, обновление только под ратифицированную D39 смену поведения (она ратифицирована — этот пак); (в) в отчёте перечислить, какие снапшот-версии сдвинулись.
- **Мандат самопроверки (решение владельца, CLAUDE.md):** после кода — ЯВНЫЙ бэкенд-ревью **ИСПОЛНЕНИЕМ**: `go build/vet/gofmt/test -race ./...` зелёные; каждый новый класс/фикс запинен РЕАЛЬНЫМ тестом (firing + non-firing/FP-guard); каждую нетривиальную правку прогони на сконструированном входе и покажи ДО/ПОСЛЕ. Полигон/бэкенд регулярно багуют — не на веру.
- **Git-дисциплина мультисессий:** НЕ коммить; `git status` — твои правки только в `backend/`. Не трогай `START_PROMT.MD`, `docs/`, `eval/`.

## ЗАДАЧИ (4; независимы, порядок по ROI/риску)

### T1 — Память: код-корень терм-дрейфа (слой 4) · находки `L3-seed-workaround-not-code-root`, `L3-recurrence-*`, `L3-suppressor-disposition-blind-right-fix`, `L3-editor-block-stale-monolingual-D1`
**Проблема (CONFIRMED по коду):** терм-дрейф «починили» промоутом сида (D38.5), а **код-корень жив**. `suppressContained` (`memory.go:615-641`) **disposition-слепой**: `validSuppressor` гейтит только на `spoilerBlocked`, НЕ на доверии → более длинный **draft/ambiguous** ключ подавляет вложенный **approved** (напр. draft `四代族长` съедает approved `族长`), а сам не инъектится (editor-блок CONFIRMED-only, `memory.go:489`) → читатель получает ничего. Хуже: подавленный термин **не попадает** в `memSel.injected` → post-check и retrieval-state его НЕ видят (дроп тихий И нелогируемый — нарушает обещание research/13 «тихую деградацию → в громкую»).

**Сделать:**
1. **Disposition-gate на suppressor.** `validSuppressor` должен требовать, чтобы более длинный матч был **не ниже по доверию**, чем вложенный, который он подавляет: суппрессор с `dispositionFor` = AMBIGUOUS (draft/auto/collision-prone) **не подавляет** вложенный, чей `dispositionFor` = CONFIRMED (approved). Сохрани верный longest-match, когда длинный ≥ доверия короткого (approved `四代族长` по-прежнему бьёт approved `族长`). Реши краевые (equal-length, оба draft — как сейчас). Спот-верифицируй, что промоутнутые D38.5 термы теперь работали бы и БЕЗ промоута (regression-safety на будущее).
2. **Громкий лог дропа.** Подавление approved-по-draft фиксируй в retrieval-state (новое поле/счётчик — напр. `n_suppressed_by_lower_trust` + detail) ИЛИ в общий канал наблюдаемости, чтобы дроп был виден оператору. Не молчать.
3. **Убрать устаревшую D1-моно-рационализацию** в комментах `renderEditorConstraintBlock` (`memory.go:469-503`): редактор теперь БИЛИНГВ (D30.1). Это **коммент/рационализация**, поведение CONFIRMED-only оставь как есть (флаг открытого вопроса «давать ли билингв-редактору src→dst» — в отчёт оркестратору, НЕ реализовывать).
**Снапшот:** меняется выбор инъекции → bump `memnorm`/memory-версии → снапшот-fold + golden. Тест: сконструируй синт-чанк (draft-длиннее-approved) — покажи ДО (approved дропнут+тих) / ПОСЛЕ (approved инъектится ИЛИ дроп логируется). Зеркаль enforce-логику как в reseed-verify, но это КОД — верь Go.

### T2 — Export-contract: нормализация каждого финала (слой 6) · находки `L5-normalization-fused-to-flag-path`, `L5-stripcosmetic-duplicates-nfkc`, `L5-sanitizer-version-churn-*`, `L5-diacritics-class-lost`
**Проблема (CONFIRMED):** нормализация вплавлена во FLAG-путь санитайзера (`stripCosmetic`) — один и тот же косметический дефект (U+3000-отступы, полноширина) чинится на флагнутом чанке, но уезжает СЫРЫМ на чистом (`cmd/tmctl/render.go:31` пишет FinalText дословно). Плюс `stripCosmetic` руко-катает NFKC-fold, дублируя `norm.NFKC` из `memnorm.go`.

**Сделать:**
1. **Единый export-contract слой:** одна детерминированная нормализация, применяемая к КАЖДОМУ финальному тексту перед экспортом (NFKC-fold через `norm.NFKC` + пробелы/CJK-пунктуация/U+3000-нормализация + опц. combining-диакритика→NFC, закрывает потерянный класс `L5-diacritics-class-lost`). Это слой D29.1a, который D-лог откладывал.
2. **Санитайзер → чистый ДЕТЕКТОР:** он только ФЛАГует по-настоящему невосстановимое; нормализацию у него забирает export-contract. `stripCosmetic` перестаёт дублировать NFKC. Сократи churn-регексы (`L5-sanitizer-version-churn`) там, где их заменяет единый нормализатор.
3. Взаимодействие с D35.4a strip-and-export: сам strip-механизм ВЕРНЫЙ (`L5-strip-export-mechanism-sound`) — не ломай его; но нормализация теперь общая, а не только на флаге.
**Снапшот/golden:** меняется текст экспорта → снапшот-fold + golden обновить. ⚠ Не тронь легит-кейс `L5-legit-cjk-gloss-stripped` (гло́сса `(羅漢拳)` в скобках) — это **открытый вопрос владельцу** (в отчёт, НЕ решай сам: узкий whitelist или конвенция — за владельцем). Тесты: чистый чанк с U+3000/полуширина/диакритикой — ДО (уезжает сырым) / ПОСЛЕ (нормализован).

### T3 — Сеамы расширяемости + слой-2 сеам (слои 7, 2) · находки `L8-*`, `L4-adding-a-pair-is-a-rewrite-not-config`
**Проблема:** расширения, которые проект вот-вот делает (Ф2-annotator/voice-роль, пары языков), падают на грязные сеамы.
**Сделать (все — чистые рефакторы, поведение zh→ru НЕ меняется):**
1. **Реестр `role→инъекция`** (`chunkrun.go:134-140`): `map[role]injectionRenderer` (или стратегия из конфига) вместо рукоправимого switch — чтобы новая роль = данные + один renderer, не правка switch.
2. **Target-aware readability-гейты** (`L8-readability-gates-target-blind`): весь readability-набор (санитайзер + cheap-гейты) сейчас хардкодит target=ru и стреляет безусловно — любой не-ru target даст ложные флаги. Загейти за `TargetLang` (early no-op при target≠ru ИЛИ per-target реестр), зеркаля pair-aware `coverage/classify`. Прод zh→ru не меняется.
3. **Слой-2 СЕАМ — pair-keyed резолв промпта/правил** (решение владельца D39: сеам сейчас, контент zh→ru): стадия резолвит промпт из **pair-keyed слоя** (`src→tgt[×genre]`), а не из фикс-пути yaml. `Book.LangPair()` (мёртвый код `book.go:193`) оживает как ось. **Наполнить ТОЛЬКО zh→ru** (сегодняшний `translator.md`/`editor.md`-контент переносится в zh→ru-пакет). **Fail-loud** на отсутствующую пару (не молчаливый дефолт в zh — закрывает латентный баг: ja-книга сейчас едет через «китайский паратаксис»-промпт). **Язык промпта = свойство пакета** (сегодня русский — но как свойство, не хардкод). Снапшот обязан fold-ить биндинг пара→промпт. Прод zh→ru рендерит тот же SHA (поведение-нейтрально) — проверь исполнением.
4. **De-смазка strip-and-export** (`L8-sanitizer-strip-smeared-5-files`, `L8-stripcosmetic-recomputed-twice`): один resolver `classification→(disposition, exportText)`, считающий strip ОДИН раз; `runStage`/escalation/resume потребляют resolved-текст, а не каждый ре-branch-ится на `FlagSanitizerStripped`.
5. **Снести мёртвое:** сироты-промпты `analyst.md`/`terminologist.md`/дубль `editor-mono.md` (если реально не референсятся — проверь grep; если editor-mono живой фолбэк, wire его конфигом под snapshot/golden); мёртвый `gatesEnabled()` (`pipeline.go:164`, 0 вызовов, врёт); вынести `foldModelWire()` (`snapshot.go:235-280`, tripwire на 3-ю модель-ось уже стоит).
**Снапшот:** T3 — поведение-нейтрально для zh→ru; докажи исполнением (тот же снапшот/golden на прод-конфиге). Пункт 3 (pair-резолв) — самый тонкий: снапшот должен fold-ить пару, но SHA промпта zh→ru не измениться.

### T4 — Детерминированный per-run quality-report (слой 5, детерм. часть) · находка `H5-no-in-loop-quality-signal`
**Проблема:** владелец просил телеметрию качества с 1-го дня (п.25/п.31); research/18 §C1 #10 пометил авто-оценку «СЕЙЧАС», её молча спустили в Ф2. Сигналы УЖЕ считаются (cheap-гейты, regression-guard, glossary post-check, echo/CJK), но не агрегированы в читаемый per-run сигнал качества.
**Сделать (ЛЁГКО — переиспользуй существующее, НЕ строй судью):** агрегируй уже-считаемые детерминированные сигналы в **per-run quality-report** (напр. `tmctl report` / расширение `status`): предл./нарратив-абзац (KPI претензии-1), диалог-тире-консистентность, CJK-утечка rate, glossary-консистентность (D10), число-дрейф (regression-guard), echo-rate. Per-chunk + агрегат по книге. **Только наблюдаемость, НЕ гейт.** Семантический span-судья (inversion/omission-бэкстоп претензии-2) — НЕ здесь: он ресёрч-зависим (форму даст трек B), в пак-2.
**Снапшот:** read-only проекция (как `status`) → снапшот не трогает. Тест на живом store-срезе (можно синт).

## Что НЕ делать (в отчёт оркестратору, не реализовывать)
- Семантический inversion/omission-судья (пак-2, после трека B).
- Наполнение не-zh→ru пар (слой 2 — только сеам сейчас).
- Diff-based редактор / декаплинг edit-единицы / логическая нарезка — это **трек B** (ресёрч ПЕРЕД постройкой).
- Решение по CJK-глоссам в скобках (`L5-legit-cjk-gloss`) — вопрос владельцу.
- Пере-прогон книги — гейтится треком B.

## Deliverable / отчёт оркестратору
Отчёт в ответе (НЕ в PROGRESS — туда пишет оркестратор): по каждой T — что сделано, `file:line`, ДО/ПОСЛЕ на реальном/синт-входе, какие снапшот-версии сдвинулись, какие golden обновлены и ПОЧЕМУ, результат `build/vet/gofmt/test -race`. Явный **само-ревью исполнением** (мандат): перечисли, что пинал реальным тестом, что прогнал руками. Открытые вопросы (билингв-редактор src→dst; CJK-глоссы; editor-mono судьба) — списком оркестратору. **Не коммить.** Оркестратор верифицирует исполнением (второй рубеж) и лендит.
