textmachine/docs/BACKEND_TRACKA_PACK1_SESSION_PROMPT.md

63 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Промт: сессия-БЭКЕНД — Трек 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 судьба) — списком оркестратору. **Не коммить.** Оркестратор верифицирует исполнением (второй рубеж) и лендит.