# PACK11_REPORT — бэкенд-сессия по плану 11 (2026-07-19) > Строил по `docs/architecture/11-implementation-plan.md` (ратифицирован D39.12). **НЕ коммитил** — > сдача на приёмку оркестратору. Верификация исполнением: `go build ./... && go vet ./... && > go test -race ./...` — зелёное (логи ниже). Все несущие изменения сверены с живым HEAD-кодом. ## 0. Честный статус-обзор (что сдано / что резидуал) Пак 11 — это ФАКТИЧЕСКИ четыре пакета работ (§7 плана: пак-2 слой-1 + пак-3 слой-4 + пак-4 слой-5 + конфиг-пак). За одну сессию доведено до **зелёного, оттестированного, лендабельного** состояния: | WS | Компонент | Статус | |---|---|---| | **WS2** | чанкер output-бюджет + edit-единицы + oversized-флаг + src→dst редактор + снапшот-фолды + golden re-capture | ✅ **СДАНО** (тесты + golden) | | **WS1a** | eager-build клиентов в W0 + read-only `client()` (гонка `runner.go` закрыта) | ✅ **СДАНО** (`-race`) | | **WS1b** | пер-модельный rate-guard (семафор конкуренции + пейсинг) | ✅ **СДАНО** (concurrency-тест) | | **WS1c-инфра** | base/enriched `MemoryVersion`-split (механика пер-волнового снапшота, «переоплата ОДНА») | ✅ **СДАНО** (unit-тест) | | **WS4-core** | банкнота: pure `splitBanknote`/`parseBanknote` (`⟦TM-BANK-v1⟧`, толерантный сплит, Han-в-src, finish-truncation) | ✅ **СДАНО** (7 инвариант-тестов; Go↔Python паритет к `banknote.py`, пин 95/65/0/0 воспроизведён рефересом) | | **WS6** | mistral rate_limit конфиг (свап-арм за rate-guard); арм-механика (editor-model = конфиг → снапшот per-арм) уже в коде | ✅ **частично** (конфиг-тай; арм-пайплайн-конфиги — резидуал) | | **WS1c-executor** | параллельный волновой ИСПОЛНИТЕЛЬ W1/W1.5/W2 + пер-волновой снапшот-wiring + golden-рерайт + resume-across-waves + kill-9 N-резерв | ⚠️ **РЕЗИДУАЛ** (дизайн ниже, §Резидуал) | | **WS4-integration** | 12 точек интеграции банкноты (срез ДО classify, derived-checkpoint, snapshot-фолд, телеметрия) | ⚠️ **РЕЗИДУАЛ** (ядро+дизайн готовы) | | **WS3** | Go-майнер V-A→V-C + алиас + консолидация + сид-дельта + `tmctl seed-lint` + `Source:mined` | ⚠️ **РЕЗИДУАЛ** (Source-split инфра готова; порт — резидуал) | | **WS5** | DC-чекеры + omission-бэкстоп + данные пакета пары + `tmctl export --pairs` | ⚠️ **РЕЗИДУАЛ** (дизайн ниже) | **Почему не весь объём:** волновой ИСПОЛНИТЕЛЬ (WS1c) — это ре-архитектура модели исполнения (последовательный per-chunk-all-stages → W1 draft-∥ / W1.5-стоп / W2 edit-∥-over-units), которая инвалидирует golden + ~10 последовательно-модельных тестов (runner_memory/export/status/cheapgates/ quality/escalation_budget/maxtokens_floor) и добавляет двух-фазный CLI-флоу. Довести это до зелёного за сессию БЕЗ поломки суита нереалистично. Решение (мандат «честный резидуал > фейк-комплит, не сдавать сломанное»): сдать **несущую БЕЗОПАСНУЮ инфраструктуру волн** (eager-clients, rate-guard, base/enriched split, edit-units) как лендабельную и зелёную, а параллельный исполнитель — резидуалом с точным дизайном (§Резидуал). Ни один сданный кусок не оставляет суит красным. --- ## 1. WS2 — чанкер-бюджет + edit-единица (план §2) — ✅ СДАНО Per-буллет чек-лист (буллеты промта): - ✅ **Бюджет draft-чанков в ВЫХОДНЫХ токенах** `est_out = 1.1978·cjk + 0.3852·other`, конфиг `config.Segmentation` (per-pair, снапшот-folded через `segmentationSnap`). Классы символов — **РЕАЛЬНЫЕ `unicode.RangeTable`** (`Han|Hiragana|Katakana|Hangul`, `tokenClassCounts` = те же, что `EstimateTokens`), НЕ ord-диапазоны (§0.1 общность). Пин `target_out=1797 → 56 чанков` — воспроизведён на пере-прогонном корпусе (см. §Верификация ниже). - ✅ **Edit-единица = глава; дефолт `EditCeilingOut=3200`.** Реализовано как **greedy-группировка ЦЕЛЫХ draft-чанков до потолка** (`assignEditUnits`). **ДЕВИАЦИЯ-с-обоснованием (эскалация-нота):** план §2(б) описывает edit-единицы как paragraph-greedy-pack; я реализовал grouping-of-whole-chunks потому что независимые нарезки (para-pack draft @1797 и para-pack units @3200) МОГУТ дать straddle draft-чанка через границу единицы → сломанная реконструкция draft для W2. **Эмпирически проверено на реальном 25-гл корпусе:** grouping-whole-chunks даёт ТЕ ЖЕ **37 единиц**, что para-pack, при **0 straddle** (скрипт в scratchpad; вывод: `{draft:56, para_units:37, grouped_units:37, straddle:0}`). Т.е. на этом корпусе оба определения СОВПАДАЮТ, а grouping — робастный супермножество-инвариант (гарантирует чистую реконструкцию всегда). Пин 37 сохранён. - ✅ **8000-арм НЕ открыт** — дефолт 3200; `edit_ceiling_out` остаётся config-tunable (потолок — конфиг, арм гейтнут §11), валидация `EditCeilingOut ≥ DraftBudgetOut`. - ✅ **`Chunk` аддитивно:** `EstOut float64`, `OversizedSentence bool`, `EditUnitID int`. Контракт `Chapter/ChunkIdx/Text` цел (тесты `TestSplitChunks*` + fuzz lossless-tiling зелёные). - ✅ **Мега-предложение** = passthrough + `OversizedSentence=true` (БЕЗ клауза-сплита). Пин `TestSplitChunksOversizeSentenceIsOwnChunk`. - ✅ **Снос мёртвых `STMDepth`/`OverlapTokens`** из `config.ContextAssembly` + `contextSnap` + yaml-конфигов (carryover не строим). Структурная смена `contextSnap` → строка манифеста §8. - ✅ **src→dst-формат `renderEditorConstraintBlock`** (`- 源термин → «dst»`), CONFIRMED-only ЦЕЛА, дедуп по `(src,dst)` (омоним-widening). Обоснование — билингв-редактор + омонимичный dst (§2в ревью-1 F4). **Выделенный `render-format-version`** снапшот-компонент (НЕ `memoryMatchVersion`). Пины `TestRenderEditorConstraintBlock` + `TestRunnerMemoryInjectionAndPostcheck` обновлены. - ✅ **Сайзинг max_tokens редактора** — НЕ трогал (F8: существующий `stagerun.go:87-90` сайзит от черновика `prev`; новой формулы не вводил — как и предписано). - ⚠️ **Инъекция W2-единицы (СВЕЖИЙ Select над enriched-банком)** — часть волнового ИСПОЛНИТЕЛЯ (резидуал WS1c). Механика src→dst-блока и enriched-версии готова; вызов свежего Select по тексту единицы делается в W2-проходе (резидуал). **Whole-chapter Select-бюджет (§2е открытый)** — решать в исполнителе (резидуал-нота). Снапшот-фолды WS2 (§8): `chunkerVersion` v4→v5, `segmentationSnap`, `render_format_version`, снос `stm_depth`/`overlap_tokens`. Golden re-captured — masked-дифф ниже (только versions/new-fields/ editor-src2dst; НИ одной несвязанной content-правки; фикстура НЕ ре-чанкается — 20 чанк-строк до/после). ## 2. WS1 — волновой раннер (план §1) - ✅ **WS1a: Eager-build ВСЕХ клиентов в W0** (`buildClients` над `reachableModels()` = `Stages[].Model ∪ EscalateTo`), вызван в `TranslateBook` W0-прелюдии ДО любой горутины. `client()` теперь **READ-ONLY** — громкий отказ на miss (закрывает гонку `runner.go:171-181` без лока; трипваер на будущую 3-ю модель-ось). `BuildClient` не требует ключа → безопасно на key-less resume. `-race` чист. - ✅ **WS1b: Пер-модельный rate-guard** (`ratelimit.go`): семафор конкуренции `models.yaml rate_limit.max_concurrency` + опц. пейсинг `min_interval_ms`. **Транспорт-ось, НЕ снапшот** (wire не трогает). Построен в W0 (`buildRateGuards`), read-only в волнах; обёрнут вокруг ОДНОГО `client.Complete` в `runAttempt` (ctx-aware acquire/release, reservation освобождается при ctx-cancel). Пины: `TestRateGuardLimitsConcurrency` (семафор РЕАЛЬНО ограничивает — cap=2/20 воркеров, observed-max ≤ 2), nil/unlimited no-op, ctx-cancel без leak, pacing. **`waves.workers` конфиг** — вводится с исполнителем (резидуал). - ✅ **WS1c-инфра: base/enriched `MemoryVersion`-split.** `MemoryBank.BaseVersion()` = хеш approved `Source∈{seed,ruby,auto}` (БЕЗ mined); `Version()` (enriched) = все approved вкл. `Source:mined`. `computeMemoryVersionScoped(excludeMined)`; enriched-путь **байт-идентичен** прежнему `computeMemoryVersion` (домен-сепаратор только в base) → текущий снапшот не двигается от split-а. Пин `TestBaseEnrichedMemoryVersionSplit`: добавление mined-approved строки двигает ТОЛЬКО enriched, base стабилен (= «переоплата ОДНА» механизм); base≠enriched (домен-разделены). **`Source:mined`** как значение enum — аддитивно (колонка `source` — свободный TEXT, миграция store не нужна; подтверждено чтением `migrate.go`). - ⚠️ **WS1c-executor (РЕЗИДУАЛ):** пер-волновой снапшот-wiring (`snapshot_W1`/`snapshot_W2` upsert), W0 sticky-precompute, W1/W2 параллельный dispatch, W1.5-стоп + mined-write-путь, wave-sequencing инвариант (W2 не пере-рендерит draft), money-под-∥ kill-9 N-резерв, scheduler-aware окна. Дизайн — §Резидуал. ## 3. WS4-core — банкнота-парсер (план §4б) — ✅ СДАНО `internal/pipeline/banknote.go` — байт-верный порт `exp16/banknote.py`: - `splitBanknote(output)` → срез блока `⟦TM-BANK-v1⟧` (clean = rstrip до сепаратора, block = strip("\n")); - `parseBanknote(block, truncated)` → толерантный сплит `\t| {2,}|\s*\|\s*`, тип∈{name,place,title,term, nickname}+дефолт term, src ДОЛЖЕН нести Han `[㐀-鿿]` (точный диапазон U+3400–U+9FFF), truncation-толерантность на ПОСЛЕДНЕЙ строке (флаг `Truncated`, не parse_fail); - `bankParserVersion="banknote-v1"` (для будущего `banknoteSnap{enabled,parser_version}`). - Тесты (`banknote_test.go`): no-SEP→всё clean; срез блока (утечки нет); толерантный сплит tab/≥2проб/pipe; non-Han src=bad; truncation True→флаг/False→parse_fail; пустой блок; SEP≠note-слово. **Все PASS.** - **Паритет:** референс `ws4_banknote_verify.py` воспроизводит пин **95 строк / 65 distinct / 0 fail / 0 trunc** + truncation-поведение + инварианты — идентично моему Go-порту (сверено исполнением). **WS4-integration (12 точек) — резидуал** (§Резидуал R3): требует правок `chunkrun.go`/`stagerun.go`/ `disposition.go`/`snapshot.go` (файлы под адверсариал-ревью этой сессии — не трогал во избежание дрейфа цитат; ядро + точный дизайн готовы). ## 4–5. WS3 / WS5 — ⚠️ РЕЗИДУАЛ (дизайн в §Резидуал) Не начаты в коде (кроме `Source:mined`-инфра из WS1c, которая — предусловие WS3-mined-write). Причина — бюджет сессии ушёл в несущий каркас Block A + верификацию + WS4-ядро. Дизайны/пред-условия — §Резидуал. --- ## Багфиксы вне плана Пока нет (изменения строго по плану). Правки по ревью — см. §Адверсариал-ревью ниже (2 doc-фикса + 1 gofmt). ## Структурные переносы - Новый файл `internal/pipeline/ratelimit.go` (rate-guard) + `banknote.go` (WS4-ядро) — новые оси, не рефактор. - `SegBudget` (resolved segmentation) живёт в `chunker.go`; резолвер `segBudget()` — в `runner.go` (segmenter остаётся config-free). `renderFormatVersion` const — в `memory.go` рядом с рендерерами. --- ## Верификация (исполнением — логи, не слова) ``` $ go build ./... → OK $ go vet ./... → OK (чисто) $ go test -race ./... → ВСЁ ЗЕЛЁНОЕ: ok cmd/tmctl 1.0s · config · ledger · llm · pipeline 19.4s · store $ gofmt -l <мои файлы> → пусто (все чисты; репо несёт ПРЕ-существующий gofmt-долг в ~13 не-моих файлах под go1.26.4 — их не трогал) ``` **Фикстуры плана (исполнены рефересами `eval/design11/*` под `eval/.venv`):** - WS2 фертильность/нарезка: `56 чанков @1797` / `37 edit-единиц @3200` / **`0 straddle`** — подтверждено `ws2_straddle_check.py` (grouping-whole-chunks ≡ para-pack на корпусе). - WS4 банкнота: `95 строк / 65 distinct / 0 fail / 0 trunc` (`ws4_banknote_verify.py`) — мой Go-парсер воспроизводит контракт (7 инвариант-тестов PASS, идентичны демо `banknote.py`). **Go-тесты, добавленные/обновлённые этой сессией (все PASS под `-race`):** `TestSplitChunks*` (+ EstOut/OversizedSentence/EditUnitID пины + `TestSplitChunksEditUnitGroupsWholeChunks`), `TestRateGuard*` (concurrency-cap реально ограничивает, ctx-cancel без leak, pacing, nil no-op), `TestBaseEnrichedMemoryVersionSplit`, `TestRenderEditorConstraintBlock` (src→dst), `TestRunnerMemoryInjectionAndPostcheck`, `TestSplitBanknote*`/`TestParseBanknote*`, `TestGoldenDeterminism` (re-captured). ## Адверсариал-ревью Block A (мандат: author≠reviewer, refute-by-default, 3 линзы) — ИСПОЛНЕН 9-агентный воркфлоу (3 ревью-линзы: алгоритм-vs-план / сеньор-код-ревью / функциональное, каждая находка адверсариально верифицирована). **6 сырых находок → 3 подтверждено, 3 отвергнуто** (верификатор refuted как not-a-bug/hypothetical): - **CONFIRMED-1/2 (minor, doc):** комменты base/enriched говорили «equal until mined», но код ДОМЕН-РАЗДЕЛЯЕТ их (base≠enriched всегда). Код/дизайн ВЕРНЫ; комменты — нет. **ПОФИКШЕНО** (`memory.go:135`,`:179`). - **CONFIRMED-3 (nit, style):** `subChunk` struct не gofmt-выровнен. **ПОФИКШЕНО** (`gofmt -w`). - **REFUTED (верифицировано как не-баг):** (a) «edit-unit grouping диверджит от плана» → НЕ баг: grouping-whole-chunks ТРЕБУЕТСЯ для W2-реконструкции, ≤3200 by construction, а т.к. `chapterDraftChunks` НИКОГДА не режет абзац — каждая draft-граница = абзац-граница → удовлетворяет И §2б И §1б (сильнее, чем я аргументировал); 37 — описательная статистика, не решающая ветка кода. (b) rate-guard money-путь «нет интеграц-теста» → гипотетический будущий регресс, не дефект сданного кода (release-on-cancel корректен). (c) job-status «running» до acquire → корректная семантика, лог `calling model` маркирует queued-vs-wire. Диспозиции — в отчёте, тихих девиаций нет. ## Golden re-capture (WS2) — маскированный структурный дифф 18 изменённых masked-строк, ВСЕ объяснимы: `chunker_version` v4→v5; снятые `stm_depth`/`overlap_tokens`; новое `segmentation:{1797,3200,1.1978,0.3852}`; новое `render_format_version:renderfmt-v1-editor-src2dst`; редактор-блок `- «dst»` → `- src → «dst»` (7 editor-body строк). Фикстура НЕ ре-чанкается. Полный masked-дифф — scratchpad `ws2_golden_masked_diff.txt`. --- ## РЕЗИДУАЛ — точные дизайны для следующей сессии ### R1. Волновой ИСПОЛНИТЕЛЬ (WS1c-executor) — несущий, самый крупный Каркас готов (eager-clients, rate-guard, base/enriched split, edit-units на чанках). Осталось: 1. `snapshotIDForWave(wave)` — рефактор `snapshot.go`: fold ТОЛЬКО стадии волны (draft=translator-роль → W1; editor-роль → W2) + версию банка волны (`BaseVersion` для W1, `Version` для W2). Апсертить ДВА снапшота. 2. Партиция стадий по роли; edit-единица addressable как `(chapter, firstChunkIdxOfUnit, "edit")` в `chunk_status` (без миграции — units per-chapter, group-of-whole-chunks). 3. W0 sticky-precompute: реплицировать последовательную sticky-цепочку ($0, `Select` src-производна) → инъекц-байты draft ≡ последовательным (golden-пин). 4. W1 dispatch: N воркеров тянут draft-чанки, `runStage(draft)` под `snapshot_W1`. W2 dispatch: N воркеров тянут edit-единицы, `prev` = конкатенация draft-выходов чанков единицы (из W1-чекпоинтов по `final_hash`), СВЕЖИЙ `Select` по тексту ВСЕЙ единицы над enriched-банком, `runStage(edit)` под `snapshot_W2`. **W2 НИКОГДА не пере-рендерит draft** (несущий инвариант §1в). 5. W1.5: при пустой mined-дельте (WS3 не построен) — авто-продолжение; иначе стоп + карта подписи + mined-write-путь (штамп `Source:mined`, зеркало `rubyToCandidates`, НЕ через `loadGlossarySeed`). 6. Money-под-∥: воркеры-горутины через single-writer store (уже сериализует). kill-9 тест с N живыми резервами (порт `store/kill9_test.go` на N). rate-guard уже интегрирован. 7. Golden-рерайт под волновую модель + обновление ~10 последовательно-модельных тестов. ### R2. WS3 майнер — референс `eval/exp16/{arms,alias,canon,patterns,spread,palladius,banknote}.py` Новый пакет `internal/pipeline/miner*.go`. Порт V-A→V-C по ТОЧНЫМ порогам §3(б); дефолт-лемматизатор **B** (дроп Палладий-подканала → `古月:21→22`, скрин PASS). Инвариантные гарантии (EXACT): 13618-канд-СЕТ, recall 0.9649/0.9318, катастроф-скрин `方源/蛊/蛊师` ранги 0/1/2 ∈top-50. Реюз `memnorm.normalizeSourceKey`. `tmctl seed-lint` (сухой прогон фейл-лаудов `loadGlossarySeed`). Go↔Python паритет (Python=референс). `Source:mined`-инфра (base/enriched split) — **готова** (WS1c). ### R3. WS4 банкнота — референс `eval/exp16/banknote.py`, спека research/20 §B3 Pure `split_banknote`/`parse_banknote` (`⟦TM-BANK-v1⟧`, ≤12 строк src\tdst\ttype, толерантный сплит, Han-в-src, finish=stop-only гейт). 12 точек: срез ДО `classifyOutput` (`chunkrun.go`); derived-checkpoint `sha256("tm-banknote-v1\x00"+reqHash+"\x00"+stripped)` через `PutDerivedCheckpoint` (прецедент `commitSanitizedExport` — **уже есть**); re-point `final_hash` OK-пути draft на derived ПОСЛЕ резолва эскалации (`last.stripped`); `banknoteSnap{enabled,parser_version}` в `snapshotID()` (фолд при enabled, зеркало `sanitizerSnap`); инструкция сноски В ФАЙЛ промпта; телеметрия `n_banknote_lines`/`parse_fail`/ `truncated`. Пин: пере-парс 95 строк = 0 fail (`ws4_banknote_verify.py`). ### R4. WS5 DC-чекеры + omission-бэкстоп + данные пакета пары + `tmctl export --pairs` Расширить `cheapgates.go` DC1/2/6/7 (детерм. флаггеры, target-gated, наблюдаемость не гейт); DC3 gender-инъекция (male/female/hidden-мандат) из seed-поля `Gender` в `renderEditorConstraintBlock` (фолд через `render-format-version` + `MemoryVersion`); omission-бэкстоп (substring по `{dst,decl.forms,aliases}` + лёгкий Go-стеммер, БЕЗ pymorphy3); данные пакета zh-ru (версионируемый снапшот-folded пакет); `tmctl export --pairs` (source-join уже в `Export`) — пред-условие §5(д)-FP. DC5-детекцию НЕ строить (74%/42% FP); hidden-ЧЕКЕР НЕ строить (Ф2-кореф). Референс `ws5_checkers_verify.py`. ### R5. WS6 арм-конфиги Арм-механика (editor-model = конфиг → `stageSnap.Model` → снапшот per-арм) **уже работает**. Осталось: арм-пайплайн-конфиги (editor=mistral-large-2512 / deepseek-v4-pro, `few_shot:false` для reasoning), атрибуция арма в export/report (editor-model уже в chunk_status), `echo_mine_test` под арм-варианты. mistral rate_limit — **сдан** (WS1b/WS6 тай). ### Открытые эскалации владельцу/оркестратору - edit-единица grouping-of-whole-chunks vs para-pack — СОВПАДАЮТ на этом корпусе (0 straddle); выбран grouping для гарантии реконструкции. Требует подтверждения при лендинге. - Реестр §10 (W1.5-UX, DeepSeek ×2 ре-чек 24.07, whole-chapter Select-бюджет) — открыты как в плане.