# План реализации пере-прогонного стека (слои 1+4+5 + данные слоя 2) — дизайн-синтез, 2026-07-19 > **Статус: РАТИФИЦИРОВАН — D39.12 (19.07, оркестратор №7); дизайн-оф-рекорд бэкенд-стройки.** Проектный > документ дизайн-синтез-сессии (D39.11): синтез всей исследовательской программы арх-ресета (research/19–20, > exp15/16, аудит 08, дефект-классы слепого чтения) в план стройки с верифицированными ($0) алгоритмами. > **Ревью-провенанс (три рубежа):** ревью-2 оркестратора (F1–F11, все внесены — 36 маркеров) → пост-фикс > 7-агентный адверсариал-воркфлоу сессии (5 находок впитаны, вкл. переписанный F8 и mined-write-путь) → > 3-агентный подтверждающий пас (2 резидуала закрыты). Бэкенд строит СТРОГО по этому плану > (`BACKEND_PLAN11_SESSION_PROMPT.md`, единый пак — решение владельца); **4 гейтнутых арма — §11**. > > **Источник истины:** при конфликте побеждает D-лог (`05-decisions-log.md`, D1–D39.11). Все `file:line` — > против HEAD `766a60e`. Все пороги/коэффициенты — число + источник. $0-верификации — `eval/design11/`, > результаты в `books/gu-zhenren/design11/` (вне git); сводка — `VERIFICATION_RESULTS.md`. > > **Дисциплина цитат:** аудит 08 и D-лог местами несут устаревшие `memory.go`-строки (~+11…+26 к HEAD: > `renderEditorConstraintBlock` :515 не :489, `suppressContained` :684, `dispositionFor` :441; санитайзер = v6 > не v4). Этот план цитирует HEAD-строки. --- ## §0. Рамка: доказанная база и зоны **Строим (ратифицировано D39.x; в план внесено, НЕ пере-открывается):** волновой раннер W0→W1(∥)→W1.5→W2(∥); бюджет чанкера в ВЫХОДНЫХ токенах (фертильность 1.20/0.39); банк W1.5 слоя 4 (детектор V-C WHICH + банкнота dst + Палладий + консолидация §C2 + алиас-ярус-1 + карта подписи); декаплинг draft=мелкий-чанк / edit=крупная-единица; свап редактора glm→mistral/deepseek-pro; дефект-класс чекеры + данные пакета пары. **НЕ строим (доказано/ратифицировано):** сцен-детекция и carryover-машинерия (D39.7/8, метрики под полом 0.126 И читатель); DP/logical-граница чанкера (Q1: «граница не рычаг»; Q1a 0/7); ко-оккуренция для dst (canon-recovery 0.20–0.68 < 70%); LLM-сегментация/ML-сцены/эмбеддинг-границы/RAG-оверлап; фронтир в дефолт; жанр-паки; локальный 9B-споттер (код ≥ локаль). **Парковки (не тестировано, консервативный дефолт — НЕ строим, НЕ хоронить):** lookahead (Q3b), diff-редактор §C2 (Q4b), reflow-отдельным-пассом (Q4c), широкое-окно/узкая-эмиссия (Q2c), W2.5-швейный пасс (Q5). Каждая парковка помечена «не тестировано» — диспозиции в §9 (lookahead/diff-редактор через §1(е)/E.7; Q4c/Q2c/Q5 — явные строки в §9). **Столпы (инварианты 1–8 `backend/README.md`, ломать нельзя):** деньги/durability (committed==SUM, kill-9); снапшот-дисциплина (правка wire/вердиктов = громкий resnapshot; все правки этого плана → ЕДИНЫЙ resnapshot §8); детерминированный банк (не вектора); эхо-мина DeepSeek (thinking НИКОГДА не off); 18+; конфиг-первое ядро; экспорт-контракт (внешняя экстракция ТОЛЬКО `tmctl export`); golden-гард детерминизма; инвариант общности §0.1 (любая книга/пара/структура; пара = ось данных). **Сводка $0-верификаций (все PASS, детали в каждом WS (г)):** | WS | Верификация | Результат | |---|---|---| | WS2 | фертильность независимо пере-выведена | `1.1978/0.3852, R²=0.9633` (Δ<3e-5); классиф.-mismatch на zh = 0 | | WS2 | нарезка на 25 главах | target_out=1797 → тот же СЧЁТ 56 чанков (4 ре-сегм / 2 абзац-сдвига, НЕ byte-identical); edit-единица: 8000-арм 25/25 в 2k–8k, **ратиф. дефолт 3200 → 37 единиц** (11 глав делятся; размер ГЕЙТИТСЯ Q2a) | | WS3 | майнер-v1 golden | катастроф `{方源:0,蛊:1,蛊师:2,古月:21}` EXACT (вариант A / Python-референс; при ратиф. дефолте **B** → `古月:22`, скрин PASS — §3(г)/(д) split-фикстура); 13618 канд.; recall 0.965/0.932 | | WS3 | сид-дельта + линт | 61 терм, 0 фейл-лаудов; флагает D38-дефект как draft ⟨проверить⟩ | | WS4 | банкнота пере-парс | 95 строк/65 src/0 fail/0 trunc EXACT; hash resume-стабилен | | WS5 | чекеры | 6/6 позитивов; DC1/2/6 n=1 (реальные, FP-rate §5д); DC3 3.5% (1 паратекст + 1 narrative-coref на hidden); **DC5 74%/42% → не гейт**; DC7 не покрыт $0 | | WS1 | деньги под ∥ + wall-clock | money-инвариант ALL PASS (границы ПАРАЛЛЕЛЬНЫ); within-wave compute ≈7.3× (end-to-end + W1.5-стоп) | | WS6 | COGS per-арм | glm-5 $0.335 / mistral $0.205 / deepseek-pro $0.117 (25-гл, warm/valley) | --- ## §1. WS1 — Волновой раннер (слой 1 / оркестровка) Целевой раннер: **W0** (чанкер + контекст-пакеты, $0) → **W1** черновики параллельно → **W1.5** (стоп-граница: банк-синк, подпись владельца, resnapshot) → **W2** редактуры параллельно. Сегодня перевод строго последователен: `bookrun.go` итерирует плоский список чанков, `translateChunk` (`chunkrun.go:90`) гонит ВСЕ стадии одного чанка по порядку под ОДНИМ `snapID`. Волны разносят: W1 = только draft-стадия по всем чанкам; W2 = только edit-стадия по всем единицам; W1.5 между ними. ### (а) Что меняем (модули, file:line) - **`runner.go`** — новый волновой драйвер (или `bookrun.go` расширяется режимом waves); **закрыть гонку lazy-init `Runner.client` (`runner.go:171-181`)**: `r.clients map[string]llm.LLMClient` читается+пишется без лока. - **`bookrun.go`** — цикл книги переходит на волны; per-chapter sticky-reset (`stickyWin=nil`, HEAD ~`bookrun.go:144`) переезжает в W0-precompute. - **`chunkrun.go`** — `translateChunk` расщепляется на per-волна проходы; sticky-цепочка (`chunkrun.go:107-115`: `memSel=r.memory.Select(...)` → `activeIDs` → следующий stickyPrev) precompute'ится в W0. - **`snapshot.go`** — `snapshotID()` (`:101-279`) становится **пер-волновым** (см. (в)). - **`stagerun.go`** — деньги/чекпоинт-путь (`Reserve`→`SettleWithCheckpoint`, `:312-449`) без изменений семантики, но вызывается из N горутин; `client(model)` предвычислен. - **`config/pipeline.go`** — новый конфиг `waves.workers` (число воркеров волны — НЕ `fanout.workers`: `Fanout` занят C2-кандидатами, `CheckRunnable` отвергает `candidates>1` `pipeline.go:208-209`; оси ортогональны) + `pricing_windows` провайдера + **пер-модельный rate-guard** (ревью-2 F4): `models.yaml` `max_concurrency`/опц. `min_interval` НА МОДЕЛЬ (конфиг, НЕ хардкод); rate-guard = транспорт-ось, НЕ снапшот (wire-байты не трогает). ### (б) Алгоритм (детерминизм явно) **W0 (детерминированный, $0):** 1. `SplitChunks` (draft-чанки, WS2) + сборка edit-единиц (WS2). 2. **Predвычислить sticky-цепочку:** один упорядоченный проход по чанкам КАЖДОЙ главы; для чанка `i` `stickyPrev_i = union предыдущих stickyDepth=2 чанков той же главы` (порт `bookrun.go` unionSticky), затем `memSel_i = memory.Select(chunk_i, chapter, stickyPrev_i, budget)`, `activeIDs_i` кормит `stickyPrev_{i+1}`. `Select` — ПУРЕ детерминированная функция `(chunk, chapter, stickyPrev, budget, frozen-bank)` (`memory.go:324`), src-производна, БЕЗ модель-выхода → precompute воспроизводит БАЙТ-в-БАЙТ ту же инъекцию, что онлайн-цепочка последовательного раннера. **Инвариант: precompute'нутые инъекц-байты ≡ байтам текущего раннера** (golden-тест в (д)) → sticky-переезд НЕ двигает request_hash, НЕ строка манифеста resnapshot. ⚠ Byte-identity держит только над BASE-банком и над НОВОЙ нарезкой (WS2 сдвигает 4 чанка); golden-тест (д) сверяет против нового чанк-сета, не старого. (Параллелизм байты НЕ меняет — `Select` src-производна, порядок фиксирован; ревью-1 CONFIRMED.) 3. **Eager-build клиентов:** множество моделей известно из `Pipeline.Stages[].Model` ∪ `EscalateTo` до старта → построить ВСЕ `llm.LLMClient` в W0 (`BuildClient`, `clients.go:15`), заполнить `r.clients` ДО любой горутины. Закрывает гонку `runner.go:171-181` без лока (карта только читается в волнах; `r.client()` вызывается ровно в одном месте `stagerun.go:343` с `model∈{st.Model, EscalateTo}` — перечисление ПОЛНОЕ). **Защита от будущей 3-й модель-оси** (channel B / annotator, `stagerun.go:476-478`): в волновом режиме `client()` делается READ-ONLY (panic/error на miss), чтобы не-перечисленная модель не пере-открыла гонку тихо. Прочие разделяемые хендлы: `templates` (построена раз в `loadTemplates`, read-only в волнах — safe), `memory *MemoryBank` (материализован раз, immutable — safe), `Store` (single-writer сериализует записи — safe), `Log` (slog thread-safe). **W1 (∥):** N воркеров тянут draft-чанки из очереди; каждый — `runStage(draft-стадия)` под `snapshot_W1`. Деньги: `Reserve`→`Complete`→`SettleWithCheckpoint` (`stagerun.go`) — сериализуются через single-writer store-пул (`store.go:71` `MaxOpenConns=1`, BEGIN IMMEDIATE). Ретрай чанка `i` изолирован: его вход — чистая функция `(source_i, frozen-bank, snapshot_W1)`, независим от чанка `j` (свойство волн — нет разделяемого мутабельного входа). **W1.5 (стоп-граница, $0 к провайдерам кроме опц. облачной Z1-адъюдикации):** банк-майнинг из W1-черновиков (WS3) → алиас-кластеризация ярус-1 → канон-предложения (draft) → карта подписи (сайдкар) → **подпись владельца** (approve/правка/draft/расклеить) → эмиссия сид-дельты → reseed → `snapshot_W2`. Механика стопа (см. (в)). **W2 (∥):** N воркеров тянут edit-единицы (главы, WS2); каждый — `runStage(edit-стадия)` под `snapshot_W2`, читая draft из W1-чекпоинта (`prev` = draft-текст единицы). W1-чекпоинты под `snapshot_W1` НИКОГДА не пере-гоняются → W1 не пере-оплачивается. **Инъекция редактора W2 (ревью-2 F3, специфицировано):** edit-стадия единицы получает СВЕЖИЙ `memory.Select` по тексту ВСЕЙ единицы (глава) над ОБОГАЩЁННЫМ банком (sticky-окно на уровне глав вырождено: единица = глава); констрейнт-блок `renderEditorConstraintBlock` = CONFIRMED-only, формат src→dst (решение WS2 §2(б)). **Явный запрет: НЕ реюзить per-чанк `memSel` из W0** (`chunkrun.go:107-114`) — тот посчитан над BASE-банком, W2 поехала бы БЕЗ майненого банк-энфорса. Это НЕ противоречит инварианту секвенирования (в): свежий Select кормит ТОЛЬКО edit-стадию (CONFIRMED-only enriched-банк), draft-стадия по-прежнему НЕ пере-рендерится/не пере-хешируется. Пин — (д): инъекция W2 содержит подписанные на W1.5 mined-термы. **Пер-модельный rate-guard (ревью-2 F4, НЕСУЩЕЕ для ∥):** пер-ВЫЗОВНЫЙ 429/`Retry-After` УЖЕ есть в адаптере (`httpllm.go:27-29,138-140`, читается+уважается, кап `maxRetryAfterWait=5мин` `:76`; тест `TestRetryOn429HonoursRetryAfter` `httpllm_test.go:168`) — до WS1 раннер БЫЛ последователен, проблема N-параллельности НЕ возникала. Гэп именно N-конкуренция. Волновой диспетчер добавляет **пер-модельный семафор конкуренции** (+опц. пейсинг `min_interval`) из `models.yaml` + телеметрию 429-плотности. Rate-guard **НЕ снапшот-ось** (транспорт, wire-байты не трогает). ⚠ Известный лимитер: `mistral-large-latest` ~48% retry-fails @1.3s интервала, max lat 64.7s (судейский eval-риг exp15 §7.6 REV.2; grok 0%); расширение 1.3→2.6s эффекта НЕ дало (49.5→48.2%) → похоже на токен-бакет/конкуренцию, тир-зависимо → **mistral-арм НЕ идёт через прод-путь до rate-guard** (WS6 пред-условие). Заготовка-блок для `00-provider-quirks.md` — §12 (вносит оркестратор при лендинге, зона полигона). **Money под параллелизмом (доказано верификацией):** потолок проверяется ВНУТРИ `Reserve` immediate-транзакции (`ledger.go:56-72`: `committed+reserved+estimate ≤ ceiling`) → конкурентные резервы N воркеров сериализуются single-writer'ом, overshoot ≤ одной макс-резервации. Урок D12 (гонка `Runner.clients` + commit-terms-on-boundary): клиенты предвычислены (закрыто); commit-терминов на границе джобы в этой архитектуре не применяется (банк консолидируется в W1.5, не на границе чанка). ### (в) Конфиг + снапшот-механика — **ПЕР-ВОЛНОВОЙ снапшот (несущее)** Сегодня `snapshotID()` — book-global, фолдится в КАЖДЫЙ `request_hash` (`stagerun.go:268-269`: `snapID` — прямой вход). Правка `snapID` = all-or-nothing re-bill (`snapshot.go:26-34`); content-addressed reuse (`msgsContentHash`) гейтится НЕ-изменённым снапшотом → через resnapshot не срабатывает (D15.2 — не построен). **Проблема:** банк входит в `snapshotID` через `MemoryVersion` (`snapshot.go:191,229`); наивный resnapshot после W1.5 сдвинул бы `snapID` для ВСЕХ стадий, включая W1 → пере-оплата W1 → «две переоплаты». **⚠ Коррекция после адверсариал-ревью-1 (несущее): «переоплата ОДНА» держит НЕ снапшот, а ВОЛНОВОЙ ИНВАРИАНТ СЕКВЕНИРОВАНИЯ.** `request_hash` фолдит рендер `msgs` НАПРЯМУЮ (`stagerun.go:268-269`), не только через `snapID`; resume-fast-path отдельно сверяет `cs.ContentHash == msgsContentHash(msgs)` (`stagerun.go:65,79`). W1-инъекция переводчика (`renderGlossaryBlock`, `memory.go:480,484-487`) эмитит ВСЕ строки с непустым dst, ВКЛЮЧАЯ AMBIGUOUS (auto/draft, помечены `⟨проверить⟩`) — а WS3-майнинг добавляет до 21 draft-строки с dst. ⇒ если W2-проход когда-либо ПЕРЕ-рендерит/пере-хеширует draft-стадию над обогащённым банком, её `msgs→contentHash→request_hash` меняются → miss → re-bill W1, даже при byte-identical `snapshot_W1`. **Несущий инвариант: W2-проход НИКОГДА не трогает рендер/хеш draft-стадии; редактор получает черновик из W1-ЧЕКПОИНТА (`final_hash→response_text`), не из свежего draft-рендера над обогащённым банком.** Это ПРЯМО противоречит текущему `translateChunk`, который считает `memSel` ОДИН раз и рендерит инъекции ОБЕИХ стадий из него (`chunkrun.go:107-114`) — наивный волновой сплит, пере-считывающий `memSel` в W2 для content-verification, пере-выведет draft-инъекцию над обогащённым банком и споткнётся ровно тут. Волновой раннер обязан разнести: W2-проход рендерит ТОЛЬКО edit-стадию, черновик читает по `final_hash`. **Роль пер-волнового снапшота (уточнено):** он НЕ предотвращает re-bill (это делает инвариант выше) — он **избегает громкого resnapshot-СТОПА** (`stagerun.go:45-49`): на резюме-после-W1.5, если владелец промоутнул майненые строки в approved (это ДВИГАЕТ `MemoryVersion`, `memory.go:264-282`), book-global снапшот сделал бы `snapshot_id` уже-готовых draft-джоб ≠ пере-вычисленному глобальному → hard-стоп. Пер-волновой снапшот держит W1-джобы на `snapshot_W1` (стабилен) → стопа нет. `snapshotID` вычисляется **пер-волна**, фолдя ТОЛЬКО стадии этой волны + состояние банка на момент волны. Хранение уже пер-стадийное — `jobs` UNIQUE(book_id, chapter, stage) (`migrate.go:37`), каждая джоба несёт свой `snapshot_id`. **Под-специфицировано (реальная ре-архитектура, не кноб — бэкенду построить):** сегодня `snapshotID()` фолдит ВСЕ стадии + ОДИН book-global `MemoryVersion` в ОДИН хеш (`snapshot.go:229`), `TranslateBook` апсертит РОВНО один снапшот (`bookrun.go:117-123`). План требует: раннер вычисляет ДВА `MemoryVersion` (base vs enriched, разбивка по `Source` ниже), апсертит ДВА снапшота (W1/W2), пинит стадии волны к своему. Сейчас все стадии получают ОДИН `snapID`; план делает `MemoryVersion` (и stage-plan) компонентом волны: - `snapshot_W1 = f(brief, chunkerVersion, draft-stage-plan, base-bank-version, coverage/sanitizer/classifier/…)`. - `snapshot_W2 = f(brief, chunkerVersion, edit-stage-plan, enriched-bank-version, …)`. W1.5-обогащение банка двигает ТОЛЬКО `enriched-bank-version` → ТОЛЬКО `snapshot_W2`. W1 стабилен. **Механизм стабильности W1 на РЕЗЮМЕ (несущее, закрывает re-bill-дыру):** после W1.5 сид ОБОГАЩЁН (дельта влита) → наивный пересчёт `snapshot_W1` из ТЕКУЩЕГО (обогащённого) банка дал бы enriched-memory-version → mismatch с замороженным в W1-джобах `snapshot_id` → resnapshot-стоп → re-bill W1. Фикс использует поле **`Source`** (`glossary.go:36`, `seed|ruby|auto` + новое `mined` WS3): **`base-bank-version` = хеш approved-строк с `Source∈{seed,ruby}` (БЕЗ mined); `enriched-bank-version` = хеш ВСЕХ approved-строк (вкл. `Source:mined`).** Майненые строки исключены из W1-фолда → `computeMemoryVersion`-вариант для W1 пере-вычисляется из НЕ-майненых строк, стабилен через W1.5-обогащение (майнинг только ДОБАВЛЯЕТ mined-строки, не трогает seed/ruby) → `snapshot_W1` воспроизводится идентично на резюме → W1-чекпоинты ХИТ, не пере-оплачиваются. Детерминировано (фильтр по `Source` + сортировка `GlossaryForBook`). Пиновый тест — (д). **⚠ Скоуп подписи W1.5 = ТОЛЬКО `Source:mined` (ревью-2 F2, инвариант — прямое следствие фикса выше):** mid-run подпись владельца на W1.5-границе трогает ИСКЛЮЧИТЕЛЬНО `Source:mined`-строки (промоут draft→approved, правка dst майненого терма). Правка/промоут **APPROVED** `Source∈{seed,ruby}`-строки ДВИГАЕТ `base-bank-version` → mismatch с замороженным в W1-джобах `snapshot_W1` → resnapshot-СТОП / re-bill W1 — поэтому разрешена ТОЛЬКО как ОТДЕЛЬНОЕ resnapshot-событие ВНЕ прогона (пред-прогонный reseed §8), НЕ на W1.5-границе. (Уточнение precision: DRAFT-status seed/ruby-строка НЕ фолдится в `base-bank-version` [gate off, `memory.go:264-266`], но W1-инъектится через `renderGlossaryBlock` → её правка = ТИХИЙ per-чанк re-bill через contentHash-путь `stagerun.go:72-79`, не громкий стоп; реалистично seed/ruby = approved, пин (д) это разделяет.) - **Write-path `Source:mined` (ревью-2 пост-верифай, НЕСУЩЕЕ — закрывает грунт-гэп критика):** `loadGlossarySeed` БЕЗУСЛОВНО штампует `Source:"seed"` (`memseed.go:131`) — наивный reseed W1.5-дельты через него проштамповал бы майненые термы `Source:seed` → они попали бы в `base-bank-version` → сдвинули `snapshot_W1` → сломали «переоплата ОДНА». ⇒ **персист майненой дельты после подписи идёт ОТДЕЛЬНЫМ mined-write-путём** (штамп `Source:mined`, зеркало `rubyToCandidates`'s `Source:"ruby"` `memseed.go:352`) — НЕ через немодифицированный `loadGlossarySeed` (хардкодит `Source:"seed"` `:131`) и НЕ расширением `seedTerm`-схемы (та остаётся БЕЗ новых полей — §3(б); Source-класс живёт в сайдкар-карте подписи). Пин — WS3 (д). - **Промоут `甲乙丙丁`/`四代族长` (DC-7):** майнер СЮРФЕЙСИТ их как `Source:mined` draft (флаг-для-видимости §3(б)); но их промоут в approved-канон = **ЭЛЕВАЦИЯ в `Source:seed`** (канон грейд/титул = base-seed-класс, не run-time mined) → поэтому строго в пред-прогонный reseed (§8, `MemoryVersion` двигается), НЕ в W1.5-подпись. Пин-тест — (д): правка approved seed-строки после W1 → ГРОМКИЙ resnapshot-стоп, не тихий re-bill. **Механика W1.5 для ОБОИХ состояний гейта (доказуемо «переоплата ОДНА»):** - **`PostcheckGate=false` (текущий дефолт):** `computeMemoryVersion(rows, gateOn=false)` фолдит ТОЛЬКО approved-строки (`memory.go:264-266`). Майненые auto/draft НЕ двигают снапшот; их вливание меняет лишь инъекцию стадий, которые их инъектят. На момент W1 банк = base (майнинг идёт ПОСЛЕ W1) → W1-инъекция = base. В W1.5 подпись владельца промоутит часть в approved → `enriched-bank-version` меняется → `snapshot_W2` двигается ОДИН раз. Auto/draft (AMBIGUOUS) в редакторский блок (`renderEditorConstraintBlock`, CONFIRMED-only, `memory.go:515`) НЕ входят. - **`PostcheckGate=true` (hard-gate, висит за пере-замером D24.4/D28.1):** `computeMemoryVersion(rows, gateOn=true)` фолдит ВСЕ строки вкл. auto/draft + `gate:`-флаг (`memory.go:251-266`, `snapshot.go:86-93`). Но на момент W1 майненых строк ещё НЕТ → `base-bank-version` реально base. В W2 банк обогащён → `enriched-bank-version` включает все майненые строки. Пер-волновой снапшот держит «одну переоплату» и здесь: W1 под base, W2 под enriched. **UX-решение (стоп посреди книги) — владельцу** (§10 вопрос W1.5-UX; §E-5 research/20). Дефолт плана: W1.5 — **явная стоп-граница** (не mid-run-append): раннер завершает W1, пишет карту подписи, ОСТАНАВЛИВАЕТСЯ; владелец подписывает; `tmctl` возобновляет с W2 под `snapshot_W2` (`--resnapshot` принимает сдвиг). Sticky/scene-инерция precompute'ится в W0 (src-производна) — не ломается параллелизмом. **Scheduler-aware диспетчер:** ценовые окна = **конфиг провайдера** (`providers.json`/`models.yaml`), не хардкод. Вендор-чеки ($0, разрешены): - **(а) DeepSeek пик/офф-пик — РАЗРЕШЁН (флаг D39.10-6):** два механизма из разных эр, НЕ конфликт. Легаси off-peak СКИДКА `16:30–00:30 UTC −50%/−75%` — только **V3/R1**, НЕ переносить на v4. Актуально для v4: **PEAK-надбавка ×2** в **UTC 01:00–04:00 ∪ 06:00–10:00** (пики Пекин 09–12 & 14–18); офф-пик = baseline (доп. скидки НЕТ). ⚠ **×2 подтверждён ТОЛЬКО вторичкой** (TechNode/explainx/KuCoin); офиц. `api-docs.deepseek.com/quick_start/pricing` на 2026-07-19 — ФЛЭТ, без peak/off-peak. Правило двух направлений: **держать ×2 как РИСК-ПИН**, ре-чек у катовера 24.07. Раннер: платные DeepSeek-джобы вне пик-окон; нельзя — закладывать ×2, не блокировать. (URL: docs §157–167.) - **(б) ZAI/GLM префикс-кэш — РАЗРЕШЁН (§E.8-бис):** implicit префикс-кэш подтверждён на `glm-5` (`docs.z.ai/guides/capabilities/cache`); cache-hit = **$0.20/1M (−80% от input $1.00)**, НЕ 50% из иллюстрации доки; хранение бесплатно (limited-time); TTL официально НЕ документирован (агрегаторные «5мин/512ток» НЕ подтверждены). Warm-then-fan оправдан: warming-вызов платит полный input, фан-аут-соседи — cache-hit. Инвариант байт-идентичного ПРЕФИКСА (`[система+мандаты]+[глоссарий сорт-ключи]→[волатильный чанк в хвост]`, research/19:506): любой timestamp/ счётчик-ретрая/«почему упало» — только в ХВОСТ (`:213`). Бэкенд УЖЕ читает `cached_tokens` (`httpllm.go:232-255`); реальную долю cache-hit верифицировать в ledger (эмпирика бьёт веру, TTL-риск). - **(в) Слаги DeepSeek 24.07 — РАЗРЕШЁН:** `deepseek-chat`/`deepseek-reasoner` депрекация **2026-07-24 15:59 UTC** → оба на v4-flash (chat=non-thinking, reasoner=thinking). Наш прод `deepseek-v4-flash`/`deepseek-v4-pro` — НЕ задет. **Действие бэкенду:** grep `configs/models.yaml` + eval-скриптов на legacy-слаги + live `GET /models`. (research/19 §E.8: eval-скрипты несут legacy-слаги — `exp15/preflight_models.py:32`, `local_bench.py`, `editor_bench.py:36` — это eval-долг, не прод.) ### (г) Верификация ДО стройки ($0) — `ws1_wave_cogs.py`, РЕЗУЛЬТАТ - **Money-инвариант (mock, моделирует single-writer admission-ось `ledger.go`; НЕ полное зеркало — см. каветы): ALL PASS** — `committed==SUM($0.448)`, `reserved=0`, **kill-9 без двойной оплаты** (идемпотентный settle `ledger.go:143` ON CONFLICT DO NOTHING). ⚠ **Границы — ПАРАЛЛЕЛЬНЫЕ, не последовательные** (адверсариал-ревью-1): (1) kill-9 теряет **≤N in-flight вызовов** (`recoverReservations` обнуляет ВСЕ `reserved_usd`, `store.go:219`), каждый пере-оплачен ОДИН раз — не «≤1»; (2) overshoot: реальный `Reserve` ОТКЛОНЯЕТ при `bookTotal+estimate>ceiling` (`ledger.go:67-71`) → reserved потолок не пробивает; единственный overshoot-источник = settle бронирует `cost` при `cost>estimate` (pricier `modelActual` `PriceForResponse` `stagerun.go:420`; reasoning-overflow) → под N резервами bound = `Σ(cost_i−est_i)`. Mock это НЕ проверяет (`cost=0.008estimate` (pricier modelActual): overshoot ≤ Σ shortfall. - Golden: **инъекц-байты W0-precompute sticky ≡ байтам текущего последовательного раннера** над НОВЫМ чанк-сетом WS2 (fresh↔resume; sticky не двигает request_hash над base-банком). - **НЕСУЩИЙ пин (ревью-1 F1): draft `request_hash`/`contentHash` БАЙТ-СТАБИЛЕН под ОБОГАЩЁННЫМ банком** — W2-проход не пере-рендерит draft-стадию; редактор читает черновик по `final_hash`. (Не просто «`snapshot_W2` двигается».) - Пер-волновой снапшот: промоут майненых→approved двигает ТОЛЬКО `snapshot_W2` (W1-джобы не ловят resnapshot-стоп); `base-bank-version`=хеш `Source∈{seed,ruby}`-строк стабилен через W1.5. - **Пин скоупа подписи W1.5 (ревью-2 F2):** правка/промоут `Source∈{seed,ruby}`-строки ПОСЛЕ W1 → ГРОМКИЙ resnapshot-стоп (не тихий re-bill W1); правка ТОЛЬКО `Source:mined`-строки → двигает лишь `snapshot_W2`, W1 стабилен. - Race-тест `Runner.clients` (D12-урок): `client()` read-only в волнах, panic на miss не-перечисленной модели. ### (е) Открытое / маршрутизация - **Соседи-черновики как контекст W2 — НЕ ратифицированы** (Q3a null + гипотеза CJK-протечки V2-брака §7.9: src- carryover → больше исходника → выше протечка). **Дефолт: без них.** Если бэкенд захочет — обоснуй + дешёвый гейт. - **Lookahead (Q3b) — парковка** (не тестировано). Не в дефолт. - **W1.5-UX (стоп посреди книги vs автопродолжение)** — вопрос владельцу (§10). - Пик-надбавка DeepSeek ×2 — риск-пин, ре-чек 24.07 (§10). --- ## §2. WS2 — Чанкер-бюджет + крупная edit-единица (слой 1) ### (а) Что меняем (file:line) - **`chunker.go`** — бюджет из `const targetChunkTokens = 1500` (`:47`, единица `estTokensFrom=cjk+other/3` `:173-181`) → **конфиг per-pair в ВЫХОДНЫХ токенах через фертильность**; `Chunk{Chapter,ChunkIdx,Text}` (`:32-36`) расширяется аддитивно (`EstOut float64`, `OversizedSentence bool`, `EditUnitID int`). Мега-ПРЕДЛОЖЕНИЕ (`packSentences:129-152`: своё оверсайз-под-чанком БЕЗ клауза-сплита и БЕЗ флага) — решить (см. (б)). - **`config/pipeline.go`** — снести мёртвые `STMDepth`/`OverlapTokens` (`:47-48`, carryover не строим); добавить `Segmentation{DraftBudgetOut int, EditCeilingOut int, Fertility{CJK,Other float64}}` (per-pair, снапшот-folded). - **`coverage.go`** — инвариант floor: draft-бюджет держит чанки над `Gates.Coverage.MinChunkChars` (`pipeline.go:159`, дефолт 500); связь-инвариант, не жёсткий (хвосты глав легально < floor — документ. граница `chunker.go:42-45`). - **`snapshot.go`** — `contextSnap` (`:15-21`) теряет STMDepth/OverlapTokens; добавляется `segmentationSnap` (бюджет+фертильность+edit-ceiling, фолд как `coverageSnap`). - **новый `slojpjat`-омиссия-бэкстоп** в слое 5 (см. WS5/(б)) — компенсатор зоны деградации. ### (б) Алгоритм - **Draft-чанк = мелкая единица (COGS/coverage/выравнивание):** greedy-упаковка абзацев (как сейчас, `appendChapterChunks`), но бюджет = `est_out(chunk) = 1.1978·cjk + 0.3852·other` (ВЫХОДНЫЕ токены) вместо input-est. Оверсайз-абзац → `packSentences`; НИКОГДА не резать предложение. (WS2 (г): на zh смена единицы near-нейтральна — тот же СЧЁТ 56 чанков при target_out=1797, НО **4 из 56 чанков ре-сегментируются (2 реальных абзац-сдвига** на границе накопления, где est_out other-вес 0.3852 vs input-est integer other/3 расходятся) → НЕ byte-identical → законный `chunkerVersion` bump §8. Ни один target_out не даёт 0 diff.) `strategy`-ось (`greedy|logical`) — заложить как СЕАМ (аддитивное поле), но наполнение `logical` НЕ строить (Q1 «граница не рычаг», Q1a 0/7). DP §B1.5 (штрафы B0:0/B1:1/B2:3/B3:6/B4:10, веса w_size=3/w_bound=60/w_dialog=200) — справочно в research/19, НЕ реализуем. - **Мега-предложение (решение плана):** `packSentences` пропускает мега-ПРЕДЛОЖЕНИЕ оверсайз-под-чанком без флага. **Решение: passthrough + флаг `OversizedSentence=true`** (не аварийный клауза-сплит ;:,、 из §B1.4). Обоснование: клауза-сплит = граничная машинерия (анти-скоуп); «деградация не молчит» удовлетворяется ФЛАГОМ (наблюдаемость). Мега-абзац уже покрыт (`packSentences`). Стихи/паратекст — как обычный текст (§0.1: не спец-кейсить структуру книги); стих-РЕНДЕР — пак-политика WS5-DC5. - **Edit-единица = глава** — ДЕКАПЛИНГ draft/edit ратифицирован (D39 п.4, exp14 претензия-1: редактору нужна КРУПНАЯ единица для меж-чанковой когезии). Глава = естественная дискурс-граница; глава > `EditCeilingOut` → деление по АБЗАЦНЫМ границам greedy до потолка (never-split-paragraph). На 25-гл книге при **дефолте 3200: 11 глав делятся → 37 edit-единиц** (при гейтнутом 8000-арме — 25 цельных глав, 0 > 8000, max 6528, все в 2k–8k; ревью-2 F1 — детали (г)). ⚠ **РАЗМЕР единицы ГЕЙТИТСЯ (ревью-1 F1, не «безопасно»):** span-омиссия Q2a НЕ гонялась; omission-бэкстоп WS5 — сеть ENTITY-уровня (сущность есть где-угодно), СТРУКТУРНО СЛЕП к пропуску клауза/сцены внутри 6.5k-единицы, чьи сущности повторяются (сам `reflow_omission.py:205-212`: «NOT evidence big chunks omission-safe… needs SPAN-JUDGES; Q2b cannot ratify edit=chapter, only Q2a»). **Решение:** глава-единица — ЦЕЛЬ; для пере-прогона `EditCeilingOut` — **консервативный дефолт ≤3200 out** (край нижней зоны деградации, а не 6.5k), большая-глава-арм ГЕЙТИТСЯ платными Q2a span-судьями (прогон-протокол WS6/(д)) + чтением владельца (2-претензии ловят span-омиссию). Owner-развилка §10. Единица маппится на чекпоинты: **деньги/resume per-СТАДИЯ-волна**, НЕ per-микро-чанк — против инварианта №1: чекпоинт = единица оплаты стадии, волна = последовательность. - **Сайзинг edit-единицы (ревью-2 F8, ИСПРАВЛЕНО пост-верифай):** unit-awareness сайзинга УЖЕ автоматическая — `stagerun.go:87-90` (D2.5) сайзит `max_tokens` от `sizingText=prev` (для стадии >0 = ЧЕРНОВИК всей единицы, НЕ источник): `EstimateTokens(prev)·MaxOutputRatio` + пер-модельный флор D24.3 (`applyModelFloor`); `Reserve` тоже text-proportional по msgs. ⚠ **НЕ сайзить от `est_out` единицы** (первичная формулировка F8 «от est_out» НЕВЕРНА): `est_out=1.20·cjk+0.39·other` ИСТОЧНИКО-производна и в glm-4.6 output-token-пространстве (≠ эвристика `EstimateTokens` cjk+other/3), а D2.5 прямо сайзит редактора от ЧЕРНОВИКА (~1.9× источника) — сайзинг от источника недо-бюджетит и ложно-триггерит length-ретрай. ⇒ единственное требование: edit-единица передаёт СВОЙ ПОЛНЫЙ черновик как `prev` (волновой дизайн W2 это и делает, §1(б)); НОВОЙ формулы сайзинга НЕТ → строки манифеста §8 F8 не требует. **Инъекция единицы (ревью-2 F3, детали §1(б)):** свежий `memory.Select` по тексту ВСЕЙ единицы над ОБОГАЩЁННЫМ банком (НЕ реюз per-чанк W0-`memSel` над base); формат src→dst, CONFIRMED-only. - **Формат банк-констрейнтов редактора (открытый вопрос D30.1, `memory.go:505-511` — решается ЗДЕСЬ, парковка запрещена):** сегодня `renderEditorConstraintBlock` (`:515`) даёт ГОЛЫЕ dst-формы (`- «dst»`, CONFIRMED-only, дедуп по dst). **Решение: перейти на src→dst-мэппинг** (`- 源термин → «dst»`), как у переводчика. Обоснование (единственное несущее — ревью-1 F4 снял два ложных): **редактор БИЛИНГВ (D30.1, `editor.md:1` видит источник) — привязка канона к src-терму устраняет неоднозначность при ОМОНИМИЧНЫХ dst** (одна ru-поверхность на две сущности). ⚠ Отвергнутые обоснования: «AMBIGUOUS-строк станет больше» — НЕ-секвитур (блок CONFIRMED-only `memory.go:519`, майненые AMBIGUOUS сюда НЕ входят); «инвариант кэш-префикса» — нет (блок из per-chunk `memSel.injected` = волатильный ХВОСТ, не стабильный префикс). Мех-безопасность: `RequestHash` хеширует `msgs` вкл. блок → формат-правка меняет request_hash, тихого false-hit нет; CONFIRMED-only СОХРАНЯЕТСЯ; дедуп по (src,dst) лишь РАСШИРЯЕТ список (безвредно). **Снапшот-фолд ЯВНЫЙ:** формат рендера `renderEditorConstraintBlock` НЕ схватывается `memoryMatchVersion` (тот = нормализатор/матчер) — добавить версию формата рендера как folded-компонент снапшота (иначе loudness держится дисциплиной, не механизмом). ### (в) Конфиг + снапшот - Фертильность `{cjk:1.1978, other:0.3852}` — конфиг слоя 2 (per-pair), снапшот-folded; **пересчёт = громкий resnapshot** (D30.9). Go грузит ТОЛЬКО коэффициенты (не токенайзер). - `DraftBudgetOut` дефолт **1797** (тот же СЧЁТ 56 чанков, что текущий input-est=1500 на zh; 4 чанка ре-сегментируются, 2 абзац-сдвига — НЕ byte-identical, WS2 (г)) — «tunable, ревизия по пере-прогону». - `EditCeilingOut` дефолт **3200 out** (ревью-2 F1: приведено к WS2(б)/§7/§10-13/§11 — прежнее «8000» ЗДЕСЬ противоречило консервативному дефолту; 3200 = край нижней зоны деградации) — tunable. **8000 out = потолок ГЕЙТНУТОГО крупно-главного арма** (верх зоны деградации research/19 §A.1.1), открывается ТОЛЬКО по платным Q2a span-судьям (§10-13), НЕ дефолт. - Снос STMDepth/OverlapTokens меняет `contextSnap`-структуру → **строка манифеста resnapshot §8** (снапшот двигается). - ⚠ **Классификатор CJK (WS2 (г) находка §1.6):** `fertility_calib.is_cjk` = ord-диапазоны БЕЗ Hangul; `EstimateTokens` (`render.go`) = `\p{Han}\p{Hiragana}\p{Katakana}\p{Hangul}`. На ЭТОМ zh-корпусе — **0 сдвиг коэффициентов** (verified — но КОРПУС-СПЕЦИФИЧНО: корпус не содержит спорных символов `々/〇`/CJK-радикалов, kana-марок `・ー`). ⚠ **ws2_fertility_verify классификатор B — НЕ верный Go-прокси** (ревью-1 F3: исключает `々/〇`/радикалы, что `unicode.Han` включает; включает `・ー`, что `unicode.Katakana` исключает) → скрипт НЕ доказывает ja/ko-общность. **Решение: Go-порт использует РЕАЛЬНЫЕ `unicode.RangeTable` `EstimateTokens`** (для общности §0.1); инвариантность задокументирована для ДАННОГО zh-корпуса, не как свойство классификатора; ja/ko-паритет — верифицировать Go-таблицами (после стройки). Коэффициенты не менять (impact на текущий zh = 0 подтверждён). ### (г) Верификация ДО стройки ($0) — `ws2_fertility_verify.py` + `ws2_chunk_sim.py`, РЕЗУЛЬТАТ - **Независимый пере-вывод фертильности** (НЕ доверяя `fertility_calib.json`): `f_cjk=1.1978, f_other=0.3852, R²=0.9633, MAPE 4.25%, n=52` — Δ vs артефакт `< 3e-5`. Классиф.-mismatch impact на zh = **0.0%**. - **Симуляция нарезки 25 глав:** гистограммы est_out; `target_out=1797` → **56 чанков** (mean 1408.8, max 1795) — тот же СЧЁТ, что текущий input-est=1500, но **4 из 56 чанков ре-сегментируются (2 реальных абзац-сдвига**; проверено прямым diff чанк-сетов: чанк 1 = 1777 vs 1778 симв, чанки 11↔12 и 54↔55 — абзац переезжает; ни один target_out не даёт 0 diff). Смена единицы near-нейтральна на zh, НЕ no-op/byte-identical. Floor: 4 чанка < 500 симв (хвосты). Edit-единица — **ДВА конфига симулированы (ревью-2 F1, пере-сим at 3200, `ws2_chunk_sim.py` ceiling∈{8000,3200}):** **(i) ГЕЙТНУТЫЙ 8000-арм** — 25/25 глав-единиц в зоне 2k–8k out (0 глав > 8000, max 6528, hist: 14×[2k–3.2k]/10×[3.2k–5k]/ 1×[5k–8k]); **(ii) РАТИФИЦИРОВАННЫЙ дефолт 3200** — 25 глав → **37 edit-единиц** (11 глав > 3200 делятся абзацно на 23 под-единицы; hist out: 26×[2k–3.2k], 10×[0–1k] split-хвосты (< нижней зоны деградации — покрыты omission-бэкстопом), 1×[1k–2k]). ⟶ «25/25 в 2k–8k» описывал 8000-арм, НЕ дефолт; при 3200 единицы МЕНЬШЕ (безопаснее для span-омиссии — цель F1) ценой 10 мелких split-хвостов. **omission-бэкстоп (слой 5) = СЕТЬ, НЕ доказательство безопасности** (Q2a span-омиссия НЕ гонялась; бэкстоп entity-уровня слеп к span-пропуску → размер edit-единицы гейтится, см. (б)/(е)). - COGS-прогноз (no-cache upper bound): $0.403/25-гл; с кэшем — WS6. ### (д) Тест-спека ПОСЛЕ стройки - Пин: `est_out`-бюджет на zh даёт СЧЁТ 56 чанков (не byte-identical к input-est) + fresh↔resume детерминизм. - Пин: мега-предложение → `OversizedSentence=true` флаг (синтетика). - Пин: глава > `EditCeilingOut` → абзацный сплит (never-split-paragraph, tiling lossless). - Пин (ревью-2 F8, испр.): `max_tokens`/`Reserve` edit-единицы сайзятся от ПОЛНОГО ЧЕРНОВИКА единицы (`prev`, `EstimateTokens(prev)·MaxOutputRatio` + флор D24.3, D2.5) — НЕ от источника/`est_out`; синтетика: крупная глава, переданная целиком как `prev`, получает unit-масштабный `max_tokens` без ложного length-ретрая. - Golden re-capture: снос STMDepth/OverlapTokens + фертильность-бюджет = маскированный дифф старого↔нового снапшота (только хеши/версии + новые поля). ### (е) Открытое / маршрутизация - `logical`-стратегия — СЕАМ, наполнение парковано (Q1). `DraftBudgetOut`/`EditCeilingOut` — tunable, ревизия по пере-прогону. src→dst редакторский мэппинг — решён здесь (в план), не хвост. - **Бюджет whole-chapter `Select` для W2-инъекции редактора (ревью-2 пост-верифай, открытый):** свежий Select по ВСЕЙ единице над enriched-банком (§1(б)/F3) не имеет пиннутого token-бюджета; per-чанк `GlossaryTokenBudget` на всю главу (много больше матчей) может вытеснить approved-канон и разбавить enriched-энфорс. Бэкенду: per-глава бюджет ИЛИ unbounded для констрейнт-блока редактора (решить в пак-2). --- ## §3. WS3 — Банк-машинерия W1.5 в Go (слой 4) Go-порт детектора V-C (WHICH) + алиас-ярус-1 + консолидация §C2 + эмиссия сид-дельты. **WHAT (dst) = НЕ ко-оккуренция** (canon-recovery 0.20–0.68 < 70%) → dst через банкноту (WS4) + Палладий (имена) + облако (термы Z1/Z3). ### (а) Что меняем (file:line) - **новый `backend/internal/pipeline/miner*.go`** — детектор V-A→V-B→V-C (авторитет = exp16 `arms.py` FROZEN, НЕ пересказ research/20 §B); алиас-ярус-1 (`alias.py` R1–R4); консолидация (`canon.py` §C2); эмиссия сид-дельты. - **`memseed.go`** — `loadGlossarySeed` (`:64-186`) читает BASE-сид + сухую валидацию дельты (штампует `Source:"seed"` `:131`); **но ПЕРСИСТ майненой дельты после подписи — ОТДЕЛЬНЫМ mined-write-путём** (штамп `Source:mined`, зеркало `rubyToCandidates` `:314-360`), НЕ через `loadGlossarySeed` (F2, §1(в) — иначе `Source:seed` двигает `snapshot_W1`); **новый `tmctl seed-lint`** (сухой прогон фейл-лаудов по дельте: `:87` src, `:94-98` dup, `:104-108` status, `:113-115` draft/approved-dst, `:161-181` спойлер-оверлап, `:201-237` shared-key). - **`store/glossary.go`** — `GlossaryEntry.Source` (`:36`, сейчас `seed|ruby|auto`) += **`mined`**; единая схема кандидата ruby↔mined (research/20 §E-бэкенд-5). - **`store/ruby.go`-паттерн** — прецедент `rubyToCandidates` (`memseed.go:314-360`: Source:ruby, status:auto, no dst, Confidence=occ) зеркалится для mined-канала. - **артефакты детектора** — контраст-словарь (jieba SHA `7197c321…`, вне git, воспроизводим из jieba 0.42.1 `dict.txt`), паттерн-инвентари (`PACK_VERSION="zh-universal-v1"`), Палладий-таблица (~400 слогов, генерируемая). ### (б) Алгоритм (frozen miner-v1 = exp16 §1.2 арм A3; ТОЧНЫЕ пороги) **Детерминизм-контракт:** все ранкинги сорт `key=(-score, -freq, src)`; occurrence-count перекрывающийся (`pos=i+1`), **suppress-free** (D24.2 — subsume чистит ТОЛЬКО ранжирование); norm = `normalizeSourceKey` (memnorm.go, trad2simp→ NFKC→lowercase); Contrast-коллизия → MAX freq; Laplace `(cf+1)/(total_char+|distinct|)` идентичен в char_indep_rel и detect_formants; Палладий-сегментация жадная longest-first, ъ отбрасывается. - **V-A:** кандидаты = Han-n-граммы **1–6** (БЕЗ сегментации); `freq_floor=3`; c-value nested-дисконт с **`g(L)=log₂(L+1)`** (НЕ `log₂(L)` — иначе 蛊/转 зануляются, катастроф-скрин FAIL, 蛊 rank 4753); weirdness = `p_book/max(word_rel, char_indep_rel, 1e-9)`; `score = max(cval,0)·termhood^1.0`, `termhood=log1p(weirdness)`; subsumption `α=0.80` (дроп a если ∃ длиннее b, a⊂b, f(b)≥α·f(a)). - **V-B:** + spread (Dice ко-оккуренция ru-лемм, pymorphy3) `score·(1+λ·spread)`, `λ=0.0` на инъект-черновиках (spread ВРЕДИТ, конфаунд §D1), свип >0 только на cold-start; `spread_freq_min=5`; dst_variants `top=4, min_dice=0.05, co-salience need=max(2, round(0.30·|chunks|))`. - **V-C:** + паттерн-каналы (`patterns.py`): (1) 百家姓-якорь (SURNAMES_SINGLE после `.discard(凝)` + SURNAMES_COMPOUND 16 вкл. 古月; окно 1–2 Han справа → name); (2) титул-суффиксы (TITLE_SUFFIX 26, ≤3 Han слева → title) + (2b) ordinal_title (ORDINAL 15 + suffix → 四代族长); (2c) rank/grade (GRADE_PREFIX 甲…癸 / NUMERAL / RANK_WORD 等转阶级重 品段层 → title, ловит 等/转 форманто-невидимые); (3) топо-суффиксы (TOPO_SUFFIX 26 → place); (4) авто-формант (`detect_formants`: символ c продуктивен если ≥`min_partners=3` различных морфем-партнёров И `over_rep=p_book/p_gen≥15.0` — над-представленность, НЕ редкость; изолирует 蛊/窍/虫, отвергает 师/花/等); (5) Палладий ru-канал (`is_palladius_token` + кап-лемма-гейт `is_name_lemma` → блок «найти»=най+ти; бонус +56.0). Бонусы `{name/place:140, title:120, term:80}`×pattern_source_weight `{surname:1.0, ordinal_title:1.0, rank_grade:1.0, title_suffix:0.9, topo_suffix:0.9, palladius:0.8, formant_suffix:0.6, formant_prefix:0.5, title_bare:0.4, default:0.3}` (pw = МАКС по evidence); sub-floor `score=bonus+40.0·f·pw`. `top_k=90` операционная точка. - **Алиас-ярус-1 (R1–R4, `alias.py`, precision-safe, R4 hard-blocks ПЕРВЫМИ):** R4-ii (разный пол, кроме hidden) → R4-iii/v (разный approved dst → разные сущности ДАЖЕ при containment: блок `族长⊂四代族长` [D38 §3], `古月⊂古月方源`) → R1 containment (|X|≥2 → extension; **компози-гард**: long=short+Y, Y∈{title,place,term}∩gt → ФРАЗА не алиас, блок `古月+族长`, сохрани `古月+方源`) → R2 фамильный якорь → family (не identity; R4-i разные имена) → R3 общий ru-рендеринг → identity (**кап-лемма-гейт**: Палладий-токен И name-lemma min_syl=2). R4-iv `cooccur_same_sentence`. Кластеризация union-find по identity-рёбрам, `sorted`. Метрика — **B³ (entity-level), НЕ MUC**. - **Консолидация §C2 (`canon.py`):** канон по ВСЕМ вхождениям (НЕ first-wins, анти-LTCR) × Палладий-конформность (name/place) × полнота падежной леммы. `canon_recovery cover_frac=0.5`. **Майнер по построению НЕ пишет approved** (канон-предложение = draft). Пол = 他/她-счётчики evidence, вердикт владельца (D19.3, hidden невыводим). since_ch = первая глава вхождения (авто); until_ch — только человек. - **Эмиссия сид-дельты §C2-7:** YAML в схеме `seedTerm` (`memseed.go:30-57`, БЕЗ новых полей): non-seed кандидат с banknote/canon dst → `status:draft` (dst, ⟨проверить⟩); WHICH-only без dst → `status:auto` (инертен). Алиасы — поверхностями терма. Evidence/Source-класс/зоны Z1–Z5 — в сайдкар-карту подписи (прецедент `diag/glossary_v2_signoff.md`), НЕ в схему сида. Пре-валидация против фейл-лаудов loadGlossarySeed = часть карты. **Эмиттер обязан реюзить subsumption+fragment-фильтры** (`emit_owner_sheets.build_precision30`: тип∈{name,place, title}, freq≥5, src∉subsumed, len≥2, не фрагмент по PARTICLE-сету), а НЕ сырой 13618-дамп (WS3 (г) находка: сырой дамп несёт §B2-фрагмент-шум). - **Плагин-сеамы P1–P6 (§B5):** данные/конфиг per язык/пара (идиом промпт-паков `PromptPathFor`), НЕ ветки кода. Наполнение — zh(-ru) инстансы + существующий ruby-канал ja. Политика: **degrade-with-flag** (P2–P6, потеря recall видима в карте подписи), **fail-loud ТОЛЬКО P1** (нет генератора кандидатов → майнинг для языка не заявляется). P5 = лемматизатор цели (см. ru-лемматизация ниже). P4 = таблица транслитерации по ПАРЕ (Палладий zh→ru). - **ru-лемматизация (§B5 P5) — Go-прод-путь W1.5 (ИСПРАВЛЕНО ревью-1 F2, прежняя формулировка «λ=0 → лемматизатор не нужен» ОПРОВЕРГНУТА):** `λ=0` глушит spread-МНОЖИТЕЛЬ, НО `dst_variants` считается БЕЗУСЛОВНО λ (`arms.py:103` `_dst_of` при `freq≥5`), а Палладий-канал V-C потребляет `dst_variants[:2]` + `is_name_lemma` (`arms.py:124-131`) — обе pymorphy3-backed (`spread.py:45-60,117-143`). ⇒ **Палладий-подканал V-C НУЖДАЕТСЯ в лемматизации даже при λ=0.** Три варианта (owner/бэкенд-развилка §10): **(A) faithful pymorphy3-порт** (dst_variants + is_name_lemma) → Go воспроизводит golden ТОЧНО, вкл. Палладий-бонус +56.0 → `古月:21`; цена — нетривиальный порт лемматизатора; **(B) дропнуть Палладий-подканал в Go-дефолте** → `古月` теряет +56.0, ранг **21→22** (катастроф-СКРИН ∈top-50 ВСЁ ЕЩЁ PASS, но exact-ранг не воспроизводится); **(C) surface-аппроксимация** (cap-предоминантность по поверхности + стеммер для dst_variants) — грубее, паритет НЕ exact. **Рекомендация (обновлено ревью-2 к ратифицированному §10-14 дефолту): дефолт (B)** — дроп Палладий-подканала с явным пере-пином фикстур (`古月:21→22`, скрин PASS; dst доставляет банкнота, exact-ранг НЕ продуктовая ценность); (A) faithful pymorphy3-порт — ТОЛЬКО при готовой поддерживаемой Go-морфологии. omission-бэкстоп слоя 5 (WS2/WS5) — отдельно: substring- матч по `decl.forms`+dst + лёгкий Snowball/Porter-ru-стеммер (Go-либа, детерминирован), pymorphy3 НЕ требуется там. ### (в) Конфиг + снапшот - Пороги miner-v1 = **СТАРТОВЫЙ версионируемый конфиг** `mining-v1` (свип ±50% инвариантен — A3@f≥3=1.0 держится по floor/over_rep/subsume/partners). `PACK_VERSION="zh-universal-v1"`. - Майненые auto/draft строки: при `PostcheckGate=false` НЕ двигают `MemoryVersion` (`memory.go:264-266`); подписанные approved → двигают `snapshot_W2` (§1 (в)). Строка манифеста §8. - `Source:"mined"` — новое значение enum (аддитивно, миграция store). ### (г) Верификация ДО стройки ($0, несущая) — `ws3_miner_verify.py` + `ws3_seeddelta.py`, РЕЗУЛЬТАТ - **(г) до-стройки:** Python miner-v1 на запинённых входах exp16 (§1.10 SHA: 8 код-файлов ✅, jieba `7197c321`, сид `175a67ad`, records `3de49eaf`) воспроизводит golden: катастроф-ранги `{方源:0,蛊:1,蛊师:2,古月:21}` EXACT, **13618 кандидатов** EXACT, recall@proposed full **0.9649**≈0.965, test-half **0.9318**≈0.932, top-1 方源 score 6640.9. ⚠ **Паритет-фикстура РАСЩЕПЛЕНА (ревью-1 F1) — нельзя пинить `古月:21` exact И дропать pymorphy3:** (i) **Палладий- ИНВАРИАНТНЫЕ гарантии** (Go-порт обязан воспроизвести при ЛЮБОМ ru-лемм-решении): recall@proposed 0.965/0.932, 13618-кандидат-СЕТ (членство), катастроф-СКРИН ∈top-50; (ii) **Палладий-ЗАВИСИМЫЕ ранги/скоры** (古月 rank/score, прочие +56.0-кандидаты): пинить exact ТОЛЬКО при варианте (A) faithful pymorphy3-порт; при (B)-дропе — пере-пин (古月→22, скрин PASS). `方源/蛊/蛊师` (ранги 0/1/2) Палладий НЕ трогает → инвариантны. - **Юнит-фикстуры алиас-правил** (exp16 §3.2): 族长≠四代族长 блок, кап-лемма-гейт «найти», компози-гард — в `alias.py` (recall известных 2/2). Прогон против Python-реализации — часть фикстур порта. - **Эмиссия сид-дельты §C2-7** (exp16 её НЕ эмитил — только сайдкары): **61 терм** (21 draft+banknote-dst / 40 auto-без-dst); **сухой линт против фейл-лаудов memseed = 0 проблем** (loadable). Артефакт `books/gu-zhenren/design11/ws3_seed_delta.yaml`. Находка: draft-кандидаты флагают ТОЧНЫЙ D38-дефект (`古月族长→старейшина`, `甲/丙`-грейд) как `draft ⟨проверить⟩` для подписи — дисциплина «майнер не пишет approved» РАБОТАЕТ; auto-кандидаты несут §B2-фрагмент-шум → эмиттер обязан реюзить subsumption+fragment-фильтры. - **(д) исполнение самого паритета Go↔Python** закрепляет бэкенд после стройки (метод mem_select.py в обратную сторону: Python = референс; при расхождении чинить Go-порт). ### (д) Тест-спека ПОСЛЕ стройки - **Байт/значение-паритет Go↔Python miner-v1:** Go-порт воспроизводит Палладий-ИНВАРИАНТНЫЕ гарантии (13618-СЕТ, recall 0.965/0.932, катастроф-СКРИН ∈top-50) — фикстуры (г). Палладий-ЗАВИСИМЫЕ exact-ранги (古月:21) — только при варианте (A) pymorphy3-порт; при (B) — пере-пин. Начать с `normalizeSourceKey` (реюз `memnorm.go`, уже байт-верифицирован через `mem_select.py`). - Юнит-тесты алиас-правил (5 R4-блоков + компози-гард + кап-лемма-гейт). - `tmctl seed-lint`: прогон эмитированной дельты через РЕАЛЬНЫЙ `loadGlossarySeed` (не Python-зеркало) → 0 фейл-лаудов. - **Пин `Source:mined` (ревью-2 F2):** reseed W1.5-дельты штампует `Source:mined` (НЕ `seed` — `loadGlossarySeed` хардкодит `Source:"seed"` `memseed.go:131`); проверить `base-bank-version` = хеш `Source∈{seed,ruby}` СТАБИЛЕН после вливания mined-дельты (иначе `snapshot_W1` двигается → «переоплата ОДНА» сломана). - Детерминизм: повтор Go-майнера байт-в-байт (без time/rand). ### (е) Открытое / маршрутизация - `λ`-режим (spread-коэф): дефолт 0 (инъект); свип на реальных не-инъект черновиках пере-прогона (§10). - ru-лемматизатор P5 — полный pymorphy3-паритет парковано; дефолт-майнер обходится (см. (б)). - Мини-голд алиасов владельца (§E-1) — recall кластеризации; без него ярус-1 только по precision (§10). - precision@30 non-seed A3 — адъюдикация владельца (§10). - Z1-адъюдикация → ОБЛАКО (локальная 9B wrong-catch 0.333, принимает 族长→«старейшина»), НЕ локаль. Локальный 9B-споттер НЕ строим (код 0.965 ≥ локаль 0.895). --- ## §4. WS4 — Банкнота: dst-канал (слой 4, wire-инвариант) Спека banknote-v1 = research/20 §B3 / exp16 `banknote.py`: разделитель `⟦TM-BANK-v1⟧`, ≤`MAX_LINES=12` строк `srcdsttype` (type∈{name,place,title,term}, парсер +nickname), «только НОВОЕ» (нет новых → нет разделителя). Строить канал ТОЛЬКО если §D-правило «сноска оправдана» сработало. **РАТИФИЦИРОВАНО D39.10** как dst-канал: parse_fail 0% (<5% ✓); ниша = **ПРЯМАЯ доставка dst** (переводчик эмитит 蛊→гу, non-seed 龙公→Лун Гун — ровно то, что ко-оккуренция извлечь НЕ смогла), НЕ recall (маржин f<3 +1 терм ~10 п.п. — на пороге; код сильнее: f<3 8/10 vs банкнота 5/10); Δкачества mean 0.935 под шум-полом 0.261 → уверенной деградации нет. Цена ~$0.01–0.1/ранобэ. ### (а) Что меняем + 12 интеграционных точек (file:line, HEAD, каждая с тест-спекой в (д)) 1. **Санитайзер (`sanitizer.go` v6):** на draft-стадии НЕ гоняется (isFinal-only, `chunkrun.go:48`). ⚠ Существующий `trailingNoteRE` (`:240`) НЕ ловит `⟦TM-BANK-v1⟧`/`srcdst`-строки (только «Примечание/Сноска»-заголовки), а CJK-leak (`:76-84`) фаерит только на голых Han-ранах — смешанная банкнот-строка «蛊 гу term» может НЕ сработать (ревью-1 F3: НЕ полагаться на trailing-note как бэкстоп). **ПЕРВЫЙ и единственный надёжный рубеж — телеметрия п.10 `banknote_parse_fail` ДО редактуры.** Если нужен финал-бэкстоп — добавить ЯВНОЕ версионируемое `⟦TM-BANK-v1⟧`-правило санитайзера (не переиспользовать trailing-note). 2. **Санитайзер cjk_leak (v6):** src-колонка = Han по построению → срез ДО санитайзера снимает ложное срабатывание; gloss-whitelist v6 НЕ трогаем. 3. **classify/echo-детект** (`disposition.go`, `const cjkEchoThreshold=0.15`): считать по ОЧИЩЕННОМУ тексту (срез → classify(stripped)). Порядок ЖЁСТКИЙ. 4. **Coverage-гейт** (`coverage.go`, translator-only `chunkrun.go:73`): `len_ratio` числитель по очищенному (сноска ≠ перевод). 5. **max_tokens-сайзинг** (`stagerun.go:87-100`): += бюджет блока (≤12 строк × ~12 ток). Перевод ПЕРВЫМ, блок хвостом → трункация бьёт по блоку; парсер fail-tolerant на последней строке + флаг `banknote_truncated`. 6. **request_hash/снапшот** (`render.go` RequestHash, `snapshot.go`): ⚠ **(ревью-1 F2) `PromptSHA256` (`:238`) фолдит ТОЛЬКО файл промпта** — если инструкция сноски = config-флаг, аппендящий её при рендере (как `few_shot`), PromptSHA256 не двигается, wire тихо расходится (`snapshot.go:110-116`-класс). **Решение (а): запечь инструкцию в ФАЙЛ промпта** (тогда PromptSHA256 реально двигается) ИЛИ явный stage-флаг-фолд. **(б) Версия ПАРСЕРА/среза банкноты — verdict-ось** (управляет resolved-выходом черновика + derived-hash, как `ClassifierVersion` `:173` / `sanitizerSnap` `:66-77`): добавить `banknoteSnap{enabled, parser_version}` в `snapshotID()` (единое имя поля, ревью-2 F9-унификация; json-тег в Go — как `sanitizerSnap.version`, решает бэкенд) — иначе bump парсера без правки промпта оставляет снапшот тем же (свежие чанки режут новым парсером, резюмнутые отдают старый derived → тихая внутри-книжная дивергенция). Включение канала = громкий `--resnapshot`. 7. **Чекпоинт/резюм** (`stagerun.go:66-80`): resume передоказывает derived-артефакт из сырого чекпоинта бесплатно (content-addressed hash → тот же id). 8. **«Редактор её не видит» (НЕСУЩАЯ, главная правка кода):** свежий прогон — редактор читает `prev=sr.Text` (`chunkrun.go:162`), хук подменяет на очищенный. НО на резюме OK-путь берёт текст по `final_hash`, который сегодня = СЫРОЙ attempt (`stagerun.go:168-169`: `if disposition==DispOK { finalHash=last.reqHash }`); re-point на derived существует ТОЛЬКО для flagged (`:170-185`). **ТРЕБОВАНИЕ: final_hash OK-стадии draft ПЕРЕНАПРАВИТЬ на `tm-banknote-v1` derived-чекпоинт** (расширение прецедента flagged→ok). Без этого резюм отдаёт редактору сырой блок. ⚠ **Эскалационный путь (ревью-1 F1): draft-стадия ЭСКАЛИРУЕТ** (`pipeline-c1.yaml:53` `escalate_to: deepseek-v4-pro` — живое, не гипотеза; `pipeline.go:374` разрешает escalate_to только translator). На эскалированном OK-черновике `last=esc.fb` (`stagerun.go:159`), `finalHash=last.reqHash` = СЫРОЙ эскалированный чекпоинт, `sr.Text=last.text` кормит редактора СЫРЬЁМ. **Срез+derived-commit+`sr.Text`=stripped+re-point ДОЛЖНЫ стоять ПОСЛЕ резолва эскалации (`stagerun.go:163`) и потреблять `last.stripped`** — тогда эскалированный черновик тоже очищен. (`resume.go:32-52` читает `GetCheckpoint(final_hash)` прозрачно → правка resume.go НЕ нужна.) 9. **Пост-чек глоссария** (`mempostcheck.go`, по финалу редактора): не влияет; но `renderGlossaryBlock` (`memory.go:477`) идёт в промпте ДО инструкции сноски («новые = не из этого списка»). 10. **Телеметрия (лауд):** per-chunk `n_banknote_lines`, `banknote_parse_fail`, `banknote_truncated` (в retrieval_state- стиле). 11. **Провайдер-квирки:** DeepSeek thinking-ON НЕ трогать (`models.go` `echoMineViolation`, `LoadModels` fail-fast). §D-арм меряет дельту качества черновика с/без инструкции (гейт канала; exp16: mean 0.935, под шум-полом 0.261 → уверенной деградации нет, но пере-замерить на пере-прогоне). 12. **Экспорт/полигон:** `tmctl export` отдаёт final без сноски автоматически (срез раньше); кандидаты — из артефакт-файла, не текста. ### (б) Алгоритм - **Срез (`split_banknote`):** `idx=output.find(SEP)`; <0 → `(output.rstrip(), "")`; иначе `clean=output[:idx].rstrip()`, `block=output[idx+len(SEP):].strip("\n")`. Срез ПЕРЕД `classifyOutput` (`chunkrun.go:32`) на сыром draft-output. - **Парс (`parse_banknote`):** строки `block.split("\n")` непустые; толерантный field-split `\t| {2,}|\s*\|\s*`; <2 полей → если последняя строка И `truncated_generation` → `banknote_truncated`, иначе `bad+=1`; `typ=parts[2].lower()` (≥3 полей) иначе `term`, не-`_TYPE_OK`→`term`; **src ДОЛЖЕН содержать Han** `[㐀-鿿]` иначе bad; `parse_fail=(bad>0)`. - **finish=stop-only гейт (обязателен, errata exp16 §6):** генерация банкноты принимается ТОЛЬКО при `finish=="stop"` (не «≠length» — DeepSeek peak-overload даёт `insufficient_system_resource`). При stop-only truncation-толерантность парсера в проде НЕДОСТИЖИМА (гейт отклоняет не-stop) — но держать в парсере для устойчивости. - **derived-checkpoint (ТОЧНАЯ формула прецедента `commitSanitizedExport`, `stagerun.go:230-232`):** `sha256("tm-banknote-v1\x00" + reqHash + "\x00" + stripped)`, id-префикс `"tm-banknote-v1:"`, через `PutDerivedCheckpoint` (`ledger.go:182`, cost=0, escalation=0, idempotent), `UsageJSON:"{}"`, `FinishReason:"banknote_export"`. (НЕ упрощённая формула доки без NULL-байтов.) - Распарсенные кандидаты → отдельный артефакт-файл (evidence для W1.5 §C), НЕ в store до подписи. ### (в) Конфиг + снапшот - Включение канала: инструкция запечена в ФАЙЛ промпта (→ `PromptSHA256` двигается) ИЛИ stage-флаг-фолд; ПЛЮС новый **`banknoteSnap{enabled, parser_version}`** в `snapshotID()` (verdict-ось, зеркалит `sanitizerSnap`) — не полагаться на `PromptSHA256` (ревью-1 F2). Смена парсера/среза = громкий `--resnapshot`. Строка манифеста §8. - derived-hash неймспейсит версию канала → смена версии = новый id (не коллизия). ### (г) Верификация ДО стройки ($0) — `ws4_banknote_verify.py`, РЕЗУЛЬТАТ - **Пере-парс 95 строк** (14 сохранённых сырых выходов exp16): 95 строк / 65 distinct_src / **0 parse_fail / 0 truncated** — EXACT golden (parse_fail 0% воспроизведён). - **Синтетическая трункация:** под `truncated_generation=True` → 2 годные строки + `banknote_truncated` (не bad); под `False` → `banknote_parse_fail` (корректно). - **fresh↔resume эквивалент-тест (спека):** `tm-banknote-v1:` id = content-addressed (формула с NULL-байтами) → fresh и resume дают ИДЕНТИЧНЫЙ id; смена stripped → другой id. **Re-point OK-пути final_hash resume-стабилен конструкцией.** - **Инварианты парсера:** SEP не note-слово; non-Han src=bad; no-SEP→всё clean; толерантный сплит (таб/≥2проб/pipe) — все PASS. ### (д) Тест-спека ПОСЛЕ стройки - Каждая из 12 точек — пиновый тест (срез ДО classify; cjk_leak/coverage/echo по очищенному; max_tokens+блок; derived-hash формула; **re-point OK-пути final_hash → derived** [fresh↔resume эквивалент]; телеметрия 3 метрики; thinking-ON нетронут). - Пин: parse_fail 0% на 95 строках; truncation-толерантность (синтетика); SEP не встречается в чистом тексте. - Пин: включение канала = снапшот-сдвиг (промпт-SHA + константа). ### (е) Открытое / маршрутизация - Дельта качества перевода от форматного констрейнта — пере-замер на пере-прогоне (§11-провайдер §D-арм). - Ниша канала = ДОСТАВКА dst, НЕ recall (код f<3: 8/10 vs банкнота 5/10). Z2-дрейф банкнотного dst (转→«оборот» vs approved «ранг») — ловится подписью владельца (draft, не approved). --- ## §5. WS5 — Дефект-класс чекеры + данные пакета пары (слои 5 + 2-данные) Дефект-класс-леджер D39.8 (все пак/банк/гейт-уровня, НЕ нарезка). Действие при срабатывании: **флаг / констрейнт- инъекция — НЕ тихая правка текста** (диспозиция D2 цела). Гейты — ДЕТЕРМИНИРОВАННЫЕ ($0): судьи слишком шумны (пол 0.126 когезия / 0.261 смысл — судьи только НОМИНИРУЮТ). ### (а) Что меняем (file:line) — по классу: где живёт правило (данные пакета vs код гейта) - **`cheapgates.go`** — расширяется дефект-классами DC1/DC2/DC7 (детерм. флаггеры на финале, `chunkrun.go:199-207` реюз `runCheapGates`, target-gated `isRuTarget`, наблюдаемость не гейт по дефолту). - **новый omission-бэкстоп** (слой 5, компенсатор зоны деградации WS2) — reflow-инвариантный лемма-матч. - **данные пакета пары zh-ru** (слой 2, версионируемый) — таблицы конверсии единиц (DC1), негатив-лист регистра (DC6), verse-локусы (DC5). - **`memory.go`/`renderEditorConstraintBlock`** — gender-инъекция (DC3) из поля сида `Gender` (`glossary.go:26`, `memseed.go:35`, folded в F1 `memory.go:273`). ### (б) Алгоритм + верифицированный статус ланд-абельности - **DC-1 时辰/меры (данные пакета + детерм. чекер):** таблица конверсии `时辰=2ч`, `万/億`-порядок, `里/斤/суй-1`; извлечь числовое время из src (`N个时辰`), сверить рендер (`三个时辰≈6ч`, НЕ «три часа»). Рефайн: флагать ТОЛЬКО явный mismatch (рендер счёта как часов), не «часы не найдены». ⚠ **(ревью-1 A1) сработка на 25-гл — ОДНА (n=1, ch1/chunk4), РЕАЛЬНЫЙ дефект (`3个时辰→3 часов`) на КОРПУСЕ-ОТКРЫТИЯ этого дефекта → «true FP≈0» преждевременно (n=1 ≠ rate). Landing ГЕЙТИТСЯ §5(д)-FP на СВЕЖЕМ пере-прогоне** (не текущий rerun). Конверсия детерминирована → низкий-FP ожидаем. - **DC-2 масштабы `千万/数十万` (b1-класс):** `千万=10⁷`, `数十万=неск.×10⁵`; + word↔word alignment (`四成四=44%`, `三成=30%`, `十二分之一=1/12` — реюз `q4a_traps.rule_b1/rule_b2` + LOCATOR). Это то, что `regressionguard.go` НЕ умеет (арабские цифры только, `L5-regressionguard` слеп к word↔word). ⚠ **(ревью-1 A4) `千万` — стоковая ГИПЕРБОЛА («несметно/тьма»), «тысячи» — in-register литературный рендер, НЕ жёсткая ошибка как DC1/DC6.** НЕ bundle с DC1/DC6 как «true FP≈0»; магнитуд-чекер — самый hyperbole-exposed → замерить FP ОТДЕЛЬНО в §5(д). Landing ГЕЙТИТСЯ §5(д). - **DC-3 gender — РАЗВЕСТИ инъекцию и чекер (ревью-2 F6):** **(1) Инъекция из поля сида `Gender` в констрейнт-блок редактора — ЛЕНДИТСЯ для ВСЕХ ТРЁХ состояний, ВКЛЮЧАЯ hidden-мандат:** male/female ⇒ жёсткие родовые формы; hidden ⇒ мандат БЕЗРОДОВЫХ конструкций / surface-`他` (→ «он») ДО `until_ch` (D19.3, `白凝冰`) — ЭТО и есть фикс класса Бай Нинбин: инъекция НАПРАВЛЯЕТ редактора, кореференция НЕ нужна. **(2) Чекер (наблюдаемость на финале):** male/female-чекер ЛЕНДИТСЯ с FP-гейтом §5(д), паратекст-чанки (foreword/синопсис) исключены. **hidden-ЧЕКЕР НЕ лендится** — ⚠ (ревью-1 A3, УТОЧНЕНО пост-верифай по records.json): FP 3.5% (2 сработки) НЕ обе паратекстные: `ch1/chunk0` = паратекст-книга-референс («её»→«эту книгу»), НО `ch1/chunk5` («Бай Нинбин… её дар просто пугает», сцена совета старейшин, НЕ паратекст) = РЕАЛЬНАЯ narrative-coref-неоднозначность женской формы на HIDDEN-персонаже — это СИЛЬНЕЕ довод к Ф2, не слабее. Паратекст-исключение снимает ТОЛЬКО chunk0; chunk5 остаётся → нужна РЕАЛЬНАЯ кореференция (→ Ф2). until_ch/sentence-scope НЕ фиксит. ⟶ Не лендится ТОЛЬКО hidden-ЧЕКЕР (инъекция всех трёх — лендится); прежняя формулировка «лендится только male/female энфорс» смешивала — бэкенд не построил бы hidden-инъекцию. - **DC-4 род говорящего в диалоге:** Ф2-annotator (дискурс-уровень, НЕ поле сида — говорящий может быть не в глоссарии). Трап «я мечтал»→«мечтала». **Вне детерм.-скоупа слоя 5 → Ф2** (диспозиция §9). - **DC-5 стих/аллюзии — ⚠ НЕ ЛЕНДИТСЯ детерм. (верификация):** loose line-profile детектор ловит трап но **FP 73.7%**; strict (регулярный метр + поэт-лексема) → FP 42.1% НО теряет позитив. ⚠ **(ревью-1 A5)** ДВЕ протестированные профили не лендятся (best = 42% FP + потеря позитива; 排比↔стих-неоднозначность трудна); универсального «детекция неспособна» из n=2 не утверждаем. **Решение (консервативный дефолт): DC-5 = пак-локусы** (владелец курирует известные verse/ allusion src-строки) + **рендер-мандат «не сплющивать» + слот сноски** + опц. LLM-НОМИНАЦИЯ (не гейт). Продукт-решения (рендер-политика, сноски) — **владельцу** (§10). - **DC-6 регистр-лексикон (негатив-лист данных пакета):** `{терем-класс}` — сказочно-русский/канцелярский слой, ломающий жанр. Чекер вхождений на финале + замены. Сработка на 25-гл — ОДНА (ch1/chunk4), РЕАЛЬНЫЙ дефект («терем» в сянься — жёсткая регистр-ошибка). ⚠ (ревью-1 A1) n=1 ≠ FP-rate; **Landing ГЕЙТИТСЯ §5(д)-FP на свежем прогоне** (негатив-лист по построению низкий-FP). Негатив-лист — данные пакета (расширяемо владельцем). - **DC-7 editor-инверсии grade/role (сверка на ВЫХОДЕ редактора):** канон-грейд `甲乙丙丁→«класс А/Б/В/Г»` (не Б/2/A), титул `族长→«глава клана»` (не «старейшина»). Точный канон-матч против editor-output → низкий FP ожидаем. ⚠ **(ревью-1 A2) DC-7 НЕ имеет $0-верификации** (ws5-скрипт не тестирует его) → **landing UNVERIFIED, FP только в §5(д)** — не утверждать «ЛЕНДИТСЯ» до замера. Корень (D38 §3): promote `甲乙丙丁`+`四代族长` **draft→approved** + reseed (owner-only) — иначе `renderEditorConstraintBlock` (CONFIRMED-only) не инъектит, `suppressContained` съедает верный `族长`. - **Omission-бэкстоп (слой 5, WS2-компенсатор):** reflow-инвариантный лемма-матч (`reflow_omission.py`): per-source ENTITY присутствует ли ГДЕ-УГОДНО в target (position-independent → reflow-safe); verdict-каскад skip→present→partial →omitted; `inflection_gap` FLAG (лемма совпала, surface вне pinned decl.forms — НЕ omission, иначе ~7-8% ложных); content-floor `ratio=len(tgt)/(1.3·len(src))<0.5` крайний backstop. Правило: entity-omission≈0 = «INSTRUMENT CEILING» НЕ «size safe» (D32.4). Go-путь БЕЗ pymorphy3: substring-матч по `{dst, decl.forms, aliases}` (детерм.), флаг при отсутствии; inflection-толерантность через лёгкий стеммер (см. WS3 (б) P5-решение). `RegressionGuard`/ coverage тут смещены (editor легально мержит — research/19 §B0-4). ### (в) Конфиг + снапшот - Данные пакета пары (таблицы единиц, негатив-лист, verse-локусы) — версионируемый пакет zh-ru, снапшот-folded (правка = громкий resnapshot, как `chunkerVersion`). Строка манифеста §8. - gender-инъекция (ревью-2 реконсиляция): ДАННЫЕ пола (`Gender` seed-поле) фолдятся через `MemoryVersion` (правка пола персонажа → снапшот); РЕНДЕР-ЛОГИКА (пол→констрейнт-текст в `renderEditorConstraintBlock`) фолдится тем же **выделенным `render-format-version`-компонентом**, что и src→dst-блок (НЕ `memoryMatchVersion` — §2(в)/§8). - Дефект-чекеры (cheapgates) версионируются `StyleCheckVersion` (`snapshot.go:207`, наблюдаемость → resnapshot при правке правила). - DC-7 promote `甲乙丙丁`+`四代族长` draft→approved = reseed → `MemoryVersion` двигается (снапшот). ### (г) Верификация ДО стройки ($0) — `ws5_checkers_verify.py`, РЕЗУЛЬТАТ - **(а) Позитивы: 6/6 пойманы** (синтетика exp15 §7.9/§2: `三个时辰→«три часа»`, `千万→«тысячи»`, `数十万→«десятки тысяч»`, «терем», стих[loose], gender-hidden). Точные zh-трап-строки (exp14b §2 таблица a1..d2 + 时辰/千万) — фикстуры. ⚠ **DC-7 (grade/role) НЕ покрыт $0-скриптом** (ревью-1 A2) → его landing verifies только §5(д). - **(б) FP-fire на 25-гл финале** (корпус records.json final; §5(д)-замер = `tmctl export` после стройки — rerun-store схема v7 < HEAD v8, read-only не мигрирует; exportNormalize косметичен, не меняет слова чекеров): **DC1/DC2/DC6 = по 1 сработке (n=1, все на ch1/chunk4 — реальные дефекты, но n=1 ≠ FP-rate, ревью-1 A1)** · **DC3 3.5% (2 — 1 паратекст book-ref + 1 narrative-coref на hidden-персонаже, A3-уточ; male/female-чекер $0-неверифицирован как DC-7 → §5(д))** · **DC5 loose 73.7% / strict 42.1% → НЕ детерм.-гейт**. ⟶ Разделение: DC5 НЕ лендится (эскалация владельцу); DC1/DC2/DC6/DC7 landing ГЕЙТИТСЯ §5(д)-FP на СВЕЖЕМ прогоне (текущий n=1 — недостаточно); DC3 — только male/female seed-field. ### (д) Тест-спека ПОСЛЕ стройки - **FP-замер через `tmctl export`** (инвариант №8) на СВЕЖЕМ пере-прогонном 25-гл финале (не records.json): каждый чекер (DC1/2/3/6/7) — измеренный FP-rate в отчёте; чекер с заметной долей ложных не лендится. ⚠ **Пред-условие (ревью-1 A6): DC1/DC2 нужен SOURCE-столбец** (для `时辰`/`千万` сверки src↔target); `tmctl export --pairs` на HEAD НЕ существует (research/20 §E-бэкенд-3) → построить `--pairs` (source-join в Export уже есть) ДО §5(д)-замера DC1/DC2. - Позитивы из эмпирики (exp15 §7.9 / exp14b / exp16) обязаны ловиться (регресс-фикстуры). - Sentence-scoping обязателен (q4a_traps F4: скорить ОДНО предложение, эндеры `.!?…\n` — иначе raw-draft и merged-glm- абзац несопоставимы). Три исхода fix/fail/«?»; «?» ИСКЛЮЧАЕТСЯ из rates, никогда тихий pass. ### (е) Открытое / маршрутизация — ВЛАДЕЛЬЦУ (§10) - **DC-5 рендер-политика стиха + слот сноски** (детерм. детекция не работает — эмпирика): продукт-решение владельца. - **DC-4 род говорящего** = Ф2-annotator (диспозиция §9: Ф2-очередь). - Промоут `甲乙丙丁`/`四代族长` draft→approved — owner-only (approved-инвариант). --- ## §6. WS6 — Свап-арм редактора + готовность пере-прогона ### (а) Что меняем (file:line) - **`configs/pipeline-*.yaml` / `models.yaml`** — армы = КОНФИГ, не код: editor-стадия `model: glm-5` (базлайн) / `mistral-large-latest` / `deepseek-v4-pro` + `few_shot` тумблер (`pipeline.go:103` `FewShot *bool`); снапшот per-арм (разный editor-model → разный `stageSnap.Model` → разный `snapshot_W2`). - **`export.go`/`quality.go`** — атрибуция арма в export/report (editor-model уже в chunk_status/checkpoint). - Инвариант: editor pinned (D12, `pipeline.go:373`: escalate_to только на translator) — свап = смена editor-модели конфигом, НЕ эскалация. Пинится `echo_mine_test.go` (сейчас требует glm-5 обоих пайплайнов; тест обновляется под арм-варианты). ### (б) Алгоритм (прогон-протокол — предпосылки для будущего полигон/монитор-промта) - Армы идут ЧЕРЕЗ прод-путь со ВСЕМИ гейтами (урок V2-брака §7.9: eval-риг без прод-гейтов [санитайзер v6 поймал бы 待遇/一步登天] дискредитировал арм). - **Пред-условие mistral-арма (ревью-2 F4):** `mistral-large-latest` НЕ идёт через прод-путь до пер-модельного rate-guard (§1(б)) — ~48% retry-fails @1.3s без него исказят и латентность, и денежную телеметрию арма. glm-5/ deepseek-pro-армы (grok-семейство 0% retry-fails) rate-guard не блокирует, но семафор общий по конфигу. - Инструмент-фиксы слепых пакетов (D39.8 п.3): авто-QA до человека (CJK-в-ru / битые формы / род-консистентность); обрезка ВСЕХ версий по общему финальному предложению; 2-way для тонких контрастов; **тир-структура претензий** (критические/смысловые/редакторские) вместо плоского «≤2». Планка — 2 претензии (интерим, не пилот Ф2.5). - Любое судимое сравнение наследует ратифицированный риг-стандарт (D39.7): per-vote персист, полно-evidence окна (HEAD_CHARS-класс запрещён), оба порядка, floor-гейт маргиналов, DET-primary > судьи где возможен (валидирован D39.9); генерации — только `finish=stop`. - ⚠ **Полы `0.126`/`0.261` — ИСТОРИЧЕСКИЕ калибровки, НЕ переносимые константы (ревью-2 F5, замечание владельца):** измерены в glm/flash-условиях на S2′/exp14-материале. Каждый СУДИМЫЙ арм-контраст пере-прогона меряет СВОЙ пол A0↔A0′-блоком (~$1/арм) на материале ЭТОГО прогона ДО интерпретации маргиналов — per-editor-model И per-материал. Смягчающее: **главные оси floor-иммунны** — DET-чекеры (WS5) + слепое чтение владельца (WS6/(д)); судьи только НОМИНИРУЮТ. Внести в предпосылки будущего полигон-промта. ### (в) Конфиг + снапшот - Каждый арм = отдельный `snapshot_W2` (editor-model в `stageSnap`). Пере-прогон армов = отдельные джобы (не конфликтуют). `few_shot:false` для reasoning-редактора (deepseek-thinking) — фолдится (`snapshot.go:110-116`, только где есть `---FEWSHOT---` блок). ### (г) Верификация ДО стройки ($0) — `ws1_wave_cogs.py` (реюз WS1-расчёта), РЕЗУЛЬТАТ - **COGS per-арм (фертильность × цены providers.json × реальная структура 25-гл, warm-cache/valley):** **glm-5 $0.335 · mistral $0.205 · deepseek-v4-pro $0.117.** Свап-арм editor'ов ДЕШЕВЛЕ базлайна (out $1.5/$0.87 vs glm-5 $3.2) — COGS-win + D38 качество-фикс. Пик-надбавка ×2 (риск-пин) удваивает ТОЛЬКО deepseek-pro-editor до $0.234 → scheduler-aware критичен только для deepseek-арма. Кэш экономит ~17% на editor-input. - Качественная сторона свап-арма — эскейп-хатч (эскалация): **точная спека «верификация полигоном ПОСЛЕ стройки» в прогон-протоколе** (это (д); платная — отдельной полигон-сессией по ратифицированному плану). ### (д) Тест-спека ПОСЛЕ стройки - Полигон-прогон армов {glm-5, mistral, deepseek-pro} через прод-путь + слепое чтение владельца (тир-структура) — ОТДЕЛЬНЫЙ полигон/монитор-промт по этому плану. Наследует риг-стандарт D39.7. - Пин: editor-swap = конфиг (не код); снапшот per-арм; атрибуция в export/report. ### (е) Открытое / маршрутизация - Качественная ратификация свап-арма — платный полигон после стройки (деньги-развилка §10; кандидат — пере-прогон 3–10 глав, планка 2 претензии). --- ## §7. Порядок стройки и нарезка на пакеты Зависимости: WS3/WS4 (банк W1.5) требуют W1.5-стоп-границы из WS1. WS2 (единицы) требуется WS1 (что гонят волны). WS5-omission-бэкстоп компенсирует WS2-зону деградации. Рекомендуемая нарезка: - **Бэкенд-пак-2 (слой 1 оркестровка + чанкер):** WS1 (волновой раннер: W0-precompute, eager-clients, пер-волновой снапшот + wave-sequencing-инвариант, money-под-∥, scheduler-aware) + WS2 (чанкер-бюджет в output-токенах, edit-единица с КОНСЕРВАТИВНЫМ `EditCeilingOut≤3200` до Q2a, src→dst редактор, снос мёртвых кнобов). Разблокирует всё (волны — несущий каркас). ⚠ omission-бэкстоп в пак-4 → пока его нет, edit-ceiling держать консервативным (ревью-1 WS2-F1). - **Бэкенд-пак-3 (слой 4 банк):** WS3 (Go-майнер V-C + алиас + консолидация + сид-дельта + `tmctl seed-lint` + `Source:mined`) + WS4 (банкнота 12 точек). Гейтится W1.5-стоп из пак-2. WS3-паритет Go↔Python — исполнение в (д). - **Бэкенд-пак-4 (слой 5 + данные слоя 2):** WS5 (DC1/2/3/6/7 чекеры + omission-бэкстоп + данные пакета zh-ru + gender-инъекция) + **`tmctl export --pairs`** (ревью-2 F7: пред-условие §5(д)-FP-замера DC1/DC2 — source-столбец). DC-5/DC-4 — эскалация владельцу/Ф2 (не в пак). - **Конфиг-пак (WS6):** армы редактора = yaml/models (не отдельный бэкенд-пак; часть пере-прогонного конфига). **Гейт:** каждый пак — мандат самопроверки исполнением (свой код + запросы + результаты); адверсариальный бэкенд-ревью после кода. Затем единый resnapshot (§8) → пере-прогон 3–10 глав → чтение владельца. --- ## §8. Манифест единого resnapshot (D30.9 — одна переоплата) Все правки этого плана + уже висящие трек-A компоненты обязаны схлопнуться в ОДИН `--resnapshot` (одна переоплата книги). Всё, что двигает `snapshotID` (`snapshot.go:101-279`): **Уже висящие (трек A залендён, но текущий снапшот книги их не несёт — resnapshot был отложен D38.5/D39.2/39.5):** - `memmatch-v4` (trust-gated suppressor, `memory.go:47`) → `MemoryVersion`. - `sanitizer-v6` (fold-first детекция) → `Sanitizer` (когда enabled). - reseed-сид `eb409f2` (промоут 5 термов) → `MemoryVersion`/`BriefHash`. - editor-v3 `0ac5f7a` (дискурс+чэнъюй, few_shot-тумблер) → `PromptSHA256`/`FewShot`. **Новое из этого плана:** - WS2: снос `STMDepth`/`OverlapTokens` (`contextSnap` меняется) + `segmentationSnap` (бюджет-output-токены + фертильность + edit-ceiling) + `chunkerVersion` bump → снапшот. - WS2: src→dst редакторский блок (`renderEditorConstraintBlock` ФОРМАТ меняется) → **выделенный `render-format-version` folded-компонент** (ревью-2 реконсиляция: НЕ `memoryMatchVersion` — тот const скоуп-матчер/алгоритм `memory.go:33-47`, формат рендера НЕ ловит автоматически; механизм пинится в §2(в)) → снапшот. - WS3: майненые approved-строки (подпись W1.5) → `MemoryVersion` (только `snapshot_W2`, §1 (в)); `Source:mined` enum (аддитивно, миграция store — НЕ снапшот, если не меняет folded content). - WS4: включение банкнота-канала = промпт-SHA (инструкция) + константа `banknote-v1` → `PromptSHA256` (draft-стадия); **+ `banknoteSnap{enabled, parser_version}`** (ревью-2 F9, verdict-ось из WS4(в) — управляет resolved-выходом черновика + derived-hash, зеркалит `sanitizerSnap`; §8 прежде нёс ТОЛЬКО промпт-SHA) → `snapshotID()`. - WS5: данные пакета пары (единицы/регистр/verse-локусы) — версионируемый пакет → снапшот; gender-инъекция: ДАННЫЕ (`Gender` seed-поле) → `MemoryVersion`, РЕНДЕР-ЛОГИКА → `render-format-version` (ревью-2 реконсиляция, НЕ `memoryMatchVersion`; §5(в)/§2(в)); DC-чекеры → `StyleCheckVersion`; promote 甲乙丙丁/四代族长 → `MemoryVersion`. - WS6: editor-model per-арм → `stageSnap.Model` (разные армы = разные снапшоты, но это РАЗНЫЕ прогоны, не переоплата одного). - **Структурное (ревью-2 F9):** сама пер-волновая реструктуризация `snapshotID()` (два `MemoryVersion` base/enriched + два снапшота W1/W2 вместо одного book-global, §1(в)) = снапшот-инвалидация — ОЖИДАЕМАЯ строка golden re-capture (структура хеша меняется даже при идентичном контенте). **Доказательство «переоплата ОДНА»:** пере-прогон = ОДИН fresh-translate под финальным конфигом (W1 под `snapshot_W1` + W2 под `snapshot_W2`); ВСЕ вышеперечисленное фолдится в эти два пер-волновых снапшота ДО старта прогона. W1.5- обогащение банка двигает ТОЛЬКО `snapshot_W2` (пер-волновой скоуп, §1 (в)) — W1 не пере-оплачивается. Итог: один draft- проход + один edit-проход = стоимость книги ×1 (не ×N от множественных resnapshot). Golden re-capture: маскированный структурный дифф старого↔нового снапшота = только хеши/версии + новые поля. --- ## §9. Диспозиции хвостов **Свип дорожек L1–L8 аудита 08 (полнота; приоритет L1/L2/L3/L5):** - **L1 (контракт t/e):** editor-4-мандата-full-regen → **WS6 свап-арм** (near-term: слабое звено = редактор, D38/D39.9); translator-structure-arm (H3) → **ЗАКРЫТ D39.9** (flash-черновик уже верен 25/0/27; pro не ратифицирован; «верность у переводчика» закрыта); diff-editing (H4/E.7) → **парковка Q4b** (не тестировано; EM 0.68–0.95 на КОДЕ); fidelity-no- owner → **WS5 DC-2/DC-7 детерм. чекеры** (полярность/число/грейд/ранг) + omission-бэкстоп. - **L2 (сегментация):** ВЕСЬ трек → **WS2** (бюджет-output-токены, edit-единица, снос STMDepth/OverlapTokens, мега- предложение-флаг); фантомный «D35.7» формализован D39 как цель слоя 1 (`05-log:563`), реализуется здесь. - **L3 (память):** код-корень терм-дрейфа **ЗАКРЫТ треком A** (`d3f6b34` trust-gated suppressor `memory.go:684`, громкий trustGateEvent) — **НЕ переоткрываем**; src→dst редактор (D30.1) → **WS2 (решён)**; майнинг ПОВЕРХ → WS3. - **L5 (экспорт):** нормализация **ЗАКРЫТА треком A** (`exportNormalize`, fold-first санитайзер v6, CJK-gloss whitelist owner-решение) — **НЕ переоткрываем**; L5-legit-cjk-gloss-stripped (D38.3 §4б owner-q) → **РАЗРЕШЁН** (whitelist сохраняет глоссы `sanitizer.go:662`, WS4-банкнота = dst-канал); резидуалы L5-broken-word (retract) / L5-diacritics (NFC-pass в export) / L5-regressionguard-40% (калибровка) — мелкие долги ниже. - **L4 (пер-язык промпты; L4-convention-pack-layer NEVER_CLOSED/HIGH):** сеам **ЗАЛЕНДЁН треком A** (`prompts:{zh-ru}` fail-loud); полный versioned convention-pack ЛАЙЕР + язык-промпта → **промпт-тема `10-prompt-architecture.md`**, гейт exp15+research/18 §D-3 (форма пакета). **Субстанция пакета РЕАЛИЗУЕТСЯ здесь:** данные пакета пары zh-ru (единицы DC1/ регистр DC6/verse DC5) = WS5-слой-2-данные; плагины P1–P6 = WS3. Форма versioned-lay­er — за промпт-темой. - **H5 (no-in-loop-quality-signal, NEVER_CLOSED/HIGH):** частично ЗАКРЫТ треком A (`quality.go` QualityReport = claim-1 структурный KPI); span-citing судья claim-2 нет. **Субстанция слоя-5 сигнала = WS5** (DET-чекеры DC1/2/3/6/7 + omission-бэкстоп = в-контуре измеримость); семантический span-судья — Ф2 (ресёрч-зависим). - **L6 (дискурс-reflow):** Ван Цуй 5 техник + чэнъюй-атом верны; резидуалы (L6-overclaim `editor.md:14`, deliteralize↔ chengyu tension, chengyu-atom untested) → **промпт-тема / измерить в пере-прогоне** (мелкие долги ниже). confirm-only. - **L7 (NEVER_CLOSED владельческие идеи):** H6–H19 — каждой явная диспозиция ниже. - **L8 (чистота кода):** role-registry / target-гейты / gatesEnabled-снос **ЗАКРЫТЫ треком A**; остаток (strip-smear 5-файлов, foldModelWire-extract, prompt-orphans) — **закрыты пак-1/1.5** (аудит датирован ДО них); confirm-only. **Ledger-очередь D39.4/39.5 (LOW/NOTE — в пак по надобности, не блокеры):** golden-глосс-ячейка, escalated+stripped тест, Retries-снапшот pre-existing, пунктуационные огрызки, trustGateEvent первый-отказ, pair-fail-loud-до-store, капы `maxGlossCJKRunes=6`/`maxGlossLatinTokenRunes=24` (tunable, ревизия по пере-прогону), F6-rename (CJK-сплит). **§E research/19 (E.5–E.8-бис):** E.5 аддитивный `Chunk` → WS2 (реализовано). E.6 кнобы когезии → СНЕСЕНЫ (carryover не строим). E.7 дифф-apply после Q4b → **парковка** (Q4b не гонялся; diff-XYZ EM 0.68–0.95, aider 91–98% на КОДЕ, ru-проза — гипотеза). E.8 DeepSeek-слаги → WS1 (в)-(в) (grep). E.8-бис ZAI-кэш → WS1 (в)-(б) (разрешён). **Парковки (НЕ тестировано — консервативный дефолт, НЕ строим, НЕ хоронить как «доказано»; ревизия по пере-прогону):** lookahead (Q3b) → WS1 (е). diff-редактор §C2 (Q4b) → E.7 выше. **Q4c reflow-отдельным-пассом · Q2c широкое-окно/ узкая-эмиссия · Q5 W2.5-швейный-пасс — все три НЕ гонялись, парк, не в дефолт** (§0-скоуп: рычаги сцеплены, но эти конкретные армы вне ратифицированной матрицы; ревизия только если пере-прогон/чтение владельца укажут дефицит). **§E research/20 (E-бэкенд-1…6):** E-1 `Source:mined`+`tmctl seed-lint` → WS3. E-2 канал сноски → WS4. E-3 `tmctl export --pairs` (НЕ существует на HEAD, `main.go`) → **ОБЯЗАТЕЛЬСТВО пак-4** (ревью-2 F7: НЕ «слот по надобности» — §5(д) требует source-столбец ДО FP-замера DC1/DC2): source-join в Export уже есть (`export.go` bookChunks); флаг `--pairs` строится как ПРЕД-УСЛОВИЕ §5(д)-замера DC1/DC2. E-4 транспорт gender → WS5-DC3. E-5 ruby↔mined схема → WS3. E-6 src→dst редактор (D30.1, `memory.go:505-511`) → **WS2 (решён в план, не хвост)**. **Дефекты D39.9 (finish=length-гейты в eval-харнесах):** `exp14_common.call` НЕ отклоняет `finish=length` — это EVAL-риг-долг (полигон), НЕ прод (прод classify обрабатывает length, `stagerun.go:455`). Прод-инвариант WS4: генерации только `finish=stop`. Диспозиция: eval-риги будущих полигон-сессий чинят (в промт-стандарт D39.7), прод чист. **NEVER_CLOSED владельческие идеи дорожки L7 аудита 08 (каждой — явная диспозиция, требование `09:165`):** - **H7 input-size-limit** (лимит входных токенов книги, отдельно от money-ceiling): **НЕТ кода/диспозиции.** Диспозиция: **preflight-чек в W0** (est-токены книги vs конфиг-лимит `max_book_tokens`, fail-loud ДО трат) — дешёвый, в пак-2 (W0 уже считает est_out). Владельцу: значение лимита (§10). - **H8 abuse/misuse pre-screen** (дешёвый high-precision скрин prompt-injection/non-translation): **НЕТ кода/дисп.** Диспозиция: **паркуется в Ф3** (trust-boundary + injection-probe → eval-очередь, `05-log:311` «не срочно»); дешёвый non-translation pre-screen — записка владельцу (§10: строить сейчас дёшево или Ф3?). - **H10 user-stop/cancel** (+ non-resumable для abuse-джобов): **НЕТ кода/дисп.** Диспозиция: **Ф2/Ф3** (resume.go + ceiling/outage-пауза есть; user-facing stop в `tmctl` — дешёвый follow-up, но не гейтит пере-прогон). Записка (§10). - **H16 self-improvement harness** (само-улучшение без дорогого human-review): **НЕТ кода/дисп.** Диспозиция: **предпосылка = слой 5 quality-сигнал (WS5) + banknote/bank-auto-build (WS3)**; сам A/B-loop = **Ф3** (frontier-meta- reviewer убит владельцем V4-п2, базовая идея — после того как слой 5 даёт измеримость каждой правки). - **H17 frontier-meta-reviewer** (фронтир-модель ревьюит правки пайплайна): **ЗАКРЫТ владельцем (V4-п2)** — НЕ переоткрываем (ревью-2 F10: без этой строки клейм «H6–H19 каждой диспозиция» формально ложен). Базовая измеримость правки = слой 5 (WS5); сам meta-review-loop = Ф3 (см. H16). - **H6 running-summary** (DelTA-class near-term рычаг): **carryover НЕ строим** (D39.7/8 — метрики под полом 0.126 И читатель). Диспозиция: **ретайр для книг этого класса** (не хоронить как «доказано» — ревизия на структуро-разнообразном корпусе; §0.1). Не в пак. - **H9 parallelism** («главная V4-идея»): **ПОДХВАЧЕНО WS1** (волновой раннер = safe-parallel дизайн; не наивный — клиенты предвычислены, деньги сериализованы, sticky precompute'нут). Диспозиция: **строится (пак-2)**. - **H15 glossary-auto-build** (V4-п4): **ПОДХВАЧЕНО WS3** (банк-майнинг W1.5 = query-time context + детектор). Диспозиция: **строится (пак-3)**; broad web-fetch auto-populate = Ф3. - **H11 backend-render-questions** (§D-7 neighbor-reference): PROD-MINOR (`MessagesWithInjection` принимает произвольную инъекцию); ±1-reference SHELVED (exp14 A-ref null). Диспозиция: **закрыто SHELVED** (соседи-черновики не в дефолт, §1 (е)). - **H12 cross-boundary trap-set** на вебновелл-домене: не построен; «cross-boundary не доминирует» предварительно (PD- classic). Диспозиция: **чистовик на вне-претрейн новелле** (после пере-прогона, §0.1). - **H13 broken-draft-joins** (битые склейки без детектора): Диспозиция: **omission-бэкстоп WS5 частично покрывает** (content-floor); специфичный join-детектор — ledger-хвост LOW. - **H14 inversion-guard** (полярность/ранг/эмоция, D37 §2г): **editor-swap = near-term защита** (WS6) + **DC-7 grade/role чекер + DC-2 полярность-числа** (WS5) частично реализуют. Диспозиция: **WS5-DC частично + WS6 свап**; полный полярность-guard — ревизия по пере-прогону. - **H18 findings-ledger process** (концерн-5, стоящий процесс): Диспозиция: **оркестратор-процесс** (completeness-critic на границе фаз — этот план = экземпляр; не бэкенд-код). - **H19 merge↔completeness collision** (`editor.md:8` сливай vs `:9` полнота, без omission-guard): Диспозиция: **omission-бэкстоп WS5** = измеримость коллизии (лемма-присутствие после мержа). **L5/L6 мелкие долги (аудит 08 §8 п.9):** L5-broken-word-class (fix-or-retract — D-лог рекламирует несуществующую способность; **retract** в контракте, класс отложен Ф2-морфология) · L5-diacritics-class (NFC-pass дёшев — **в export-слой**, zero-FP) · L5-regressionguard-40% (калибровать по реальному draft→final распределению — ledger-хвост) · L6-overclaim (`editor.md:14` «КОНВЕНЦИЯ русского» → техника-vs-норма split — промпт-тема) · L6-deliteralize↔chengyu (эксплицитная граница — промпт-тема) · L6-chengyu-atom (измерить в пере-прогоне, не считать закрытым). --- ## §10. Реестр вопросов владельцу (консолидированный, с дефолтами плана) | # | Вопрос | Дефолт плана | Развилка | |---|---|---|---| | 1 | **W1.5-UX:** стоп посреди книги для подписи vs автопродолжение? | Явная стоп-граница (раннер стоп → подпись → resume W2) | UX-решение | | 2 | **DC-5 стих:** рендер-политика (не сплющивать) + слот сноски? Детерм. детекция НЕ работает (FP 74%/42%) | Пак-локусы (владелец курирует src-строки) + рендер-мандат + сноска; LLM-номинация не гейт. **Рекомендация оркестратора (ревью-2):** дефолт плана поддержан | Продукт-решение | | 3 | **DC-3 gender:** промоут `甲乙丙丁`/`四代族长` draft→approved + подпись пола `人祖=male, 古月=n-a, 白凝冰=hidden` | Промоут + reseed (owner-only, approved-инвариант) | Подпись | | 4 | **H7 input-limit:** значение `max_book_tokens`? | Preflight-чек в W0, лимит tunable | Настройка | | 5 | **H8 misuse-prescreen:** строить дешёвый non-translation скрин сейчас или Ф3? | Ф3 (не срочно) | Скоуп | | 6 | **H10 user-stop:** user-facing stop/cancel в tmctl сейчас или Ф2/Ф3? | Ф2/Ф3 (ceiling/outage-пауза есть) | Скоуп | | 7 | **Мини-голд алиасов** (~20–30 мин): нужен для recall кластеризации ярус-1 | Без него — только precision | Тачпойнт | | 8 | **precision@30 non-seed A3:** адъюдикация T/F/? | Сайдкар готов (`precision_at30.md`) | Тачпойнт | | 9 | **DeepSeek пик ×2:** риск-пин (вторичка, не офиц.); ре-чек 24.07 у катовера слагов. **Провенанс (ревью-2 F11):** клейм D39.7 «официально» имел провенанс «через владельца» (URL/цитата НЕ пинились); свежий чек 19.07 офиц. прайса = ФЛЭТ → label остаётся РИСК-ПИН до ре-чека 24.07 (там запинить URL+цитату); историч. запись D39.7 НЕ переписывать (D23.3) | scheduler-aware вне пик-окон + ×2-бюджет-фолбэк | Вендор-чек | | 10 | **Свап-арм качество:** платный полигон-прогон армов после стройки (планка 2 претензии)? | Пере-прогон 3–10 глав через прод-путь | Деньги-развилка | | 11 | **ja→ru реплика** (P1=ruby-канал) как тест общности §0.1 после zh→ru | Отдельный мини-фриз след-за-стройкой | Скоуп/деньги | | 12 | **PostcheckGate флип** (hard-gate) — висит за пере-замером D24.4/D28.1 | Дефолт-флаггер (false); пер-волновой снапшот держит обе механики | Пере-замер | | 13 | **Edit-ceiling размер** (ревью-1 WS2-F1): глава-единица до 6.5k out span-омиссия НЕ доказана безопасной (только Q2a) | Консервативный `EditCeilingOut≤3200` до Q2a; большая-глава-арм — платный Q2a span-судья. **Рекомендация оркестратора (ревью-2):** дефолт 3200 + платный Q2a-гейт поддержан; Q2a ОБЪЕДИНИТЬ с полигон-прогоном свап-армов (Q10) | Деньги/качество | | 14 | **WS3 ru-лемматизатор** (ревью-1 WS3-F2): Палладий-подканал V-C нужен pymorphy3 (dst_variants+is_name_lemma) | (A) faithful pymorphy3-порт → golden exact; или (B) дроп Палладий → `古月:21→22`, скрин PASS, пере-пин. **Рекомендация оркестратора (ревью-2):** дефолт **(B)** — дроп подканала с пере-пином (dst доставляет банкнота, exact-rank НЕ продуктовая ценность); (A) только при готовой поддерживаемой Go-морфологии | Порт-цена/точность | --- ## §11. Мандат самопроверки (исполнено) + приёмка **Ревью исполнением:** каждый верификационный скрипт (`eval/design11/ws{1,2,3,4,5}*.py`) ПРОГНАН, результат воспроизводим (детерминизм: без time/rand в money-инварианте; фикс-mock-латентности); каждое несущее `file:line` сверено с ЖИВЫМ кодом HEAD `766a60e` (не worktree-память отчётов). Находки верификации, изменившие план: (1) WS2 классиф.-mismatch = 0 на zh (не realign, документировать); (2) WS3 сид-дельта флагает D38-дефект как draft (дисциплина работает) + фрагмент-шум (эмиттер реюзит фильтры); (3) WS5 DC-5 стих НЕ лендится детерм. (74%/42% FP) → эскалация владельцу; (4) WS1 W1.5-re-bill-форк → пер-волновой снапшот. **Адверсариальный селф-ревью несущих алгоритмов — ИСПОЛНЕН (раунд-1, 5-агентный refute-by-default воркфлоу, author≠ reviewer; каждый агент пере-исполнил верификации + сверил file:line на HEAD).** Все скрипты воспроизвелись байт-в-байт. Несущие расхождения ВПИТАНЫ в план (помечены «ревью-1» in-line): - **WS1-F1 (несущее):** «переоплата ОДНА» держит НЕ снапшот, а **волновой инвариант секвенирования** (W2 не пере-рендерит draft; редактор читает черновик из W1-чекпоинта) — `renderGlossaryBlock` инъектит майненые draft-строки, так что наивный пере-рендер draft над обогащённым банком пере-оплатил бы W1. Снапшот избегает resnapshot-СТОПА. → §1(в) переписан. - **WS1-F3/F4/F5:** money-границы ПАРАЛЛЕЛЬНЫ (≤N in-flight, overshoot ≤ Σ(cost−est)); mock ≠ полное зеркало (day-ceiling/ идемпотентность в Go-тест); wall-clock = compute-only, end-to-end + W1.5-стоп. WS1-F6/F8: client() read-only в волнах; `waves.workers` (не fanout). - **WS2-F1 (риск):** edit=глава при 6.5k out НЕ доказана span-безопасной (backstop entity-уровня слеп; только Q2a) → `EditCeilingOut≤3200` консервативно + Q2a-гейт (§10-13). **WS2-F2:** 1797 не byte-identical (4 чанка, 2 абзац-сдвига). **WS2-F3:** классиф-инвариантность корпус-специфична (не ja/ko). **WS2-F4:** src→dst — одно несущее обоснование (омонимы), явный снапшот-фолд формата. - **WS3-F1/F2:** паритет-фикстура расщеплена (Палладий-инвариант vs -зависимый `古月:21`); «λ=0 → лемматизатор не нужен» ОПРОВЕРГНУТ (dst_variants безусловен λ) → развилка A/B/C (§10-14). - **WS4-F1/F2/F3:** point 8 + эскалационный путь (draft эскалирует в prod); парсер-версия = verdict-ось снапшота (не PromptSHA256); trailing-note НЕ бэкстоп банкноты. - **WS5-A1..A6:** DC1/2/6 сработки = n=1 на корпусе-открытия (НЕ FP-rate) → landing гейтится §5(д); DC2=гипербола-exposed; DC3-fires = паратекст-кореф (не hidden; только male/female лендится); DC5 «две профили не лендятся» (не «неспособна»); DC7 без $0-верификации; §5(д) нужен source-столбец (`tmctl export --pairs`). - **Completeness:** Q4c/Q2c/Q5 явные §9-строки; H5/L4-pack/L5-gloss названы. CONFIRMED без правок: eager-build race-фикс, Select-детерминизм, derived-hash формула, strip-ordering, records.json-FP-суррогат легитимен. **Ревью-2 оркестратора (19.07) — ВПИТАН (in-line маркер «ревью-2»; провенанс: соло-проход оркестратора [полное чтение + спот-чеки file:line по HEAD исполнением; мульти-агентный рубеж НЕ запускался — лимиты владельца] + 2 замечания владельца F4/F5).** Вердикт: план РАТИФИЦИРУЕМ после фикс-листа. Ни одна находка НЕ пересматривает алгоритмы или шесть $0-верификаций — все правки ТЕКСТОВЫЕ (скрипты не пере-гонялись; ни одна (г)-цифра не изменилась). Фиксы: - **F1 (MAJOR):** `EditCeilingOut` дефолт приведён к **3200** (было противоречие «8000» в §2(в) vs ≤3200 везде); 8000 = потолок ГЕЙТНУТОГО крупно-главного арма за Q2a. → §2(в). - **F2 (MAJOR):** явный инвариант — mid-run подпись W1.5 трогает ТОЛЬКО `Source:mined`; правка seed/ruby = ОТДЕЛЬНОЕ пред-прогонное resnapshot-событие (§8), НЕ W1.5; промоут 甲乙丙丁/四代族长 — в reseed. Пин в (д). → §1(в)/(д). - **F3 (MAJOR):** инъекция W2 специфицирована — СВЕЖИЙ `Select` по ВСЕЙ единице над enriched-банком, запрет реюза per-чанк W0-`memSel` (base). → §1(б)/§2(б)/(д). - **F4 (MAJOR, владелец):** пер-модельный rate-guard в волновом диспетчере (пер-вызовный 429 УЖЕ есть `httpllm.go:27-29`, гэп = N-∥); `models.yaml`-конфиг, НЕ снапшот-ось; mistral-арм — за rate-guard; заготовка quirks → §12; каветка wall-clock. → §1(а)/(б)/(г), §6(б). - **F5 (MINOR, владелец):** полы 0.126/0.261 — ИСТОРИЧЕСКИЕ, per-editor-model + per-материал; каждый арм меряет свой A0↔A0′ (~$1); главные оси floor-иммунны (DET + слепое чтение). → §6(б). - **F6 (MINOR):** DC-3 развести инъекцию (ЛЕНДИТСЯ все три вкл. hidden-мандат) от hidden-ЧЕКЕРА (не лендится, Ф2-кореф). → §5(б). - **F7 (MINOR):** `tmctl export --pairs` = обязательство пак-4 (пред-условие §5(д)), не «слот». → §7/§9. - **F8 (MINOR):** сайзинг edit-единицы (`max_tokens`+`Reserve`) unit-aware (МЕХАНИЗМ исправлен пост-верифай: «от ЧЕРНОВИКА `prev`, не est_out» — блок ниже). → §2(б)/(д). - **F9 (MINOR):** §8 += `banknoteSnap{enabled,parser_version}` verdict-ось + строка «пер-волновая реструктуризация = снапшот-инвалидация». → §8. - **F10 (MINOR):** H17-frontier-meta-reviewer добавлен в §9-свип (ЗАКРЫТ V4-п2 — иначе «H6–H19 каждой» ложен). → §9. - **F11 (микро):** §10-9 нота провенанса Q9 (D39.7 «официально» = через владельца; риск-пин до 24.07, D39.7 не переписывать). → §10. - **§10 рекомендации оркестратора** вписаны: Q13 (3200+Q2a, объединить с Q10), Q14 (дефолт **B**), Q2 (дефолт поддержан). **Пост-фикс адверсариал-воркфлоу (7-агентный, 19.07 — тот мульти-агентный рубеж, что ревью-2 не гонял; $0 к провайдерам, все 6 $0-скриптов воспроизведены под `eval/.venv` [зависимости `regex`/`jieba`/`pymorphy3` — НЕ system python]).** Вердикт критика: **РАТИФИЦИРУЕМ, все 11 фиксов на месте, F1 MAJOR полностью снят, MAJOR-противоречий нет; 6/6 golden воспроизведены байт-в-байт.** Несущие находки ВПИТАНЫ (маркеры «пост-верифай»/«испр.»): - **F8 ПЕРЕПИСАН (был неверен):** «сайзить от `est_out` единицы» — ошибка; `stagerun.go:87-90` (D2.5) уже сайзит редактора от ЧЕРНОВИКА (`prev`, ~1.9× источника), а `est_out` источнико-производна + чужое токен-пространство → НОВОЙ формулы нет, §8-строки F8 не требует. → §2(б)/(д). - **F1 ПЕРЕ-СИМУЛИРОВАН at 3200:** ратиф. дефолт = 25 глав → **37 edit-единиц** (11 делятся, 10 split-хвостов <1k out); «25/25 в 2k–8k» был 8000-арм. → §2(б)/(г), `ws2_chunk_sim.py` ceiling∈{8000,3200}. - **F2 write-path (грунт-гэп критика):** `loadGlossarySeed` хардкодит `Source:"seed"` (`memseed.go:131`) → reseed mined-дельты ОБЯЗАН нести `Source`-столбец, иначе `snapshot_W1` двигается; 甲乙丙丁/四代族长 промоут = элевация в `Source:seed` (base) → pre-run. → §1(в), WS3 (д). - **Реконсиляция снапшот-loudness:** src→dst-блок + gender-рендер фолдятся ВЫДЕЛЕННЫМ `render-format-version`, НЕ `memoryMatchVersion` (const скоуп-алгоритм `memory.go:33-47`) — §8/§5(в) приведены к §2(в). - **F6 уточнён по records.json:** 2-я DC3-сработка (`ch1/chunk5`, «её дар» на Бай Нинбин, сцена совета) = narrative-coref на HIDDEN-персонаже, НЕ паратекст — СИЛЬНЕЕ довод к Ф2. → §5(б)/(г). - Мелкое: `banknoteSnap` имя поля унифицировано (`parser_version`); §3(б)/§0-сводка приведены к дефолту B; whole-chapter Select-бюджет — открытый пункт (§2(е)). **Подтверждающий пас (3-агентный, 19.07 — верификация 6 корректировок на НОВЫЕ противоречия) — ИСПОЛНЕН.** Поймал ДВА F1-класс-резидуала (моя реконсиляция сиблингов оставила одну строку устаревшей) — ОБА пофикшены: (1) **MAJOR** — §8-WS5 `gender-инъекция → memoryMatchVersion` не был приведён (сиблинги §5(в)/§8-WS2/§2(в) были) → реконсилирован к `render-format-version`; (2) **MINOR** — §1(в) предлагал «расширить `seedTerm`-схему», что противоречит §3(б) «без новых полей» → пиннут ЕДИНЫЙ путь: отдельный mined-write-путь (штамп `Source:mined`), §3(а)/§3(б)/§1(в) согласованы. Корректировки 1/2/5/6 подтверждены ЧИСТЫМИ (F8 grounded `stagerun.go:87-90`; 37-единиц воспроизведён байт-в-байт; F6 по records.json; имена/дефолт-B). После фикса резидуалов — MAJOR-противоречий не осталось. **Приёмка (оркестратор):** пере-исполнение симуляций; спот-чек file:line; refute-воркфлоу по несущим алгоритмам; сверка полноты против скоуп-листа D39.11. Мин-планка ратификации: все WS с исполненной (г) — **все шесть исполнены и PASS** ($0-верификации воспроизводимы, детерминированы); манифест resnapshot полон (§8); реестр владельцу консолидирован (§10). **Гейтнутые армы (ратифицируемы как ГЕЙТНУТЫЕ, стройка сердцевины не ждёт, но арм не стартует до верификации):** (1) **WS2 КРУПНАЯ edit-единица** (>~3200 out) — гейтится платными Q2a span-судьями (сердцевина строит с консервативным `EditCeilingOut≤3200`); (2) **WS6 качество свап-арма** — гейтится платным полигон-прогоном после стройки (COGS-сторона (г) исполнена; эскейп-хатч (а)/(б) D39.11); (3) **WS5 DC-чекеры landing** — гейтится $0-FP-замером §5(д) на СВЕЖЕМ прогоне (текущий n=1 недостаточен — не платный гейт, build-time тест); (4) **WS6 mistral-арм через прод-путь** — гейтится пер-модельным rate-guard (ревью-2 F4: ~48% retry-fails без него искажают латентность+биллинг). Эти четыре — развилки в §10 (13/10/9/…), маршрутизированы явно, не тихо. Эмпирика exp15/16 — предварительна до вне-претрейн новеллы: несущие пороги «tunable, ревизия по пере-прогону». --- ## §12. Заготовка для `00-provider-quirks.md` (ревью-2 F4 — вносит ОРКЕСТРАТОР при лендинге, зона полигона) > ⚠ Этот блок — НЕ часть плана-контракта; это черновик записи для `docs/experiments/00-provider-quirks.md` (зона > полигона). Дизайн-синтез-сессия НЕ пишет в `docs/experiments/`; оркестратор переносит текст ниже в quirks-файл при > лендинге плана и там же пинит URL/цитату при пере-замере на прод-тире. **`mistral-large-latest` — агрессивный rate-limiter (тир-зависим, пере-замерить на прод-тире):** - Судейский eval-риг (exp15 §7.6 REV.2, N-параллельные вызовы): **~48% retry-fails @1.3s интервала**, max latency **64.7s**; `grok-*` в том же риге — **0%**. - Расширение интервала **1.3→2.6s эффекта НЕ дало** (49.5→48.2%) → лимитер похож на **токен-бакет / конкуренц-кап**, НЕ per-request-пейсинг; тир-зависим. - **Действие бэкенда (WS1(б)):** пер-модельный семафор конкуренции + 429/`Retry-After`-бэкофф (пер-вызовный уже есть, `httpllm.go:27-29,138-140`, кап `maxRetryAfterWait=5мин`). Правило двух направлений: цифры из eval-рига, НЕ офиц. доки — при переносе в quirks запинить прод-тир-замер + вендор-страницу rate-limits. - **Пред-условие:** mistral-editor-арм не идёт через прод-путь до этого guard (§6(б)).