# 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-бюджет) — открыты как в плане.
