From 811f561deed6ac2e89c9e2af145d600d76047742 Mon Sep 17 00:00:00 2001 From: "Claude (backend session)" Date: Sat, 25 Jul 2026 03:28:37 +0300 Subject: [PATCH] Ratify D39.24 pack-16 diff-editor design with the reorder: build zero-cost parts, paid repair machinery behind enabled false pending residual measurement --- docs/PROGRESS.md | 2 +- docs/architecture/05-decisions-log.md | 12 +- .../reports/PACK16_DESIGN_2026-07-25.md | 571 ++++++++++++++++++ 3 files changed, 582 insertions(+), 3 deletions(-) create mode 100644 docs/archive/reports/PACK16_DESIGN_2026-07-25.md diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index cc1f7cdc..1bdc1fe7 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -7,7 +7,7 @@ > - **exp15 ЗАВЕРШЁН И ЗАЛЕНДЕН (D39.7, `fe34a16`):** «граница вредит» отозван (артефакт окна судьи, свинг +0.689 после фикса), все когезия-эффекты под шум-полом 0.126, «sequential нигде не лучше» (LOO-stable, НЕ ратифицированный зелёный свет волне), фертильность 1.20·cjk+0.39·other; $12.79/$15. **Слепое чтение ИСПОЛНЕНО (D39.8):** планка не пройдена никем, дефекты распределены по армам (читатель подтвердил floor-null; V2-брак = риг без гейтов, не carryover-рычаг) → битва качества пере-прогона = дефект-классы (时辰-юниты · числа b1 · gender-enforce · стих/аллюзии · регистр-лексикон), не нарезка. **Q4a ИСПОЛНЕН И ЗАЛЕНДЕН (D39.9, $1.32/$2):** верность НЕ покупается на translate-стадии (flash уже верен 25/0/27; pro ×3.67 не ратифицирован); смысл ломает glm-РЕДАКТОР (свап glm→mistral/deepseek-pro = приоритетный арм пере-прогона); exp14b-правила дефектны → $0-ре-аудит в exp16. **exp16 ИСПОЛНЕН И ЗАЛЕНДЕН (D39.10, $0.02/$10):** WHICH подтверждена (код-детектор V-C recall 0.97 held-out, $0) / WHAT опровергнута для ко-оккуренции → dst-канал = банкнота (идея владельца оправдана) + облако+Палладий; 9B-споттер не нужен (0.895 ≤ код); Z1 → облако; ре-аудит exp14b: ядро D38 держится, errata не нужна. Тачпойнты владельцу: карта подписи (пол) · мини-голд алиасов · precision@30 (books/gu-zhenren/exp16/). **Исследовательская программа ЗАВЕРШЕНА.** **Директива владельца (D39.11): стройка ТОЛЬКО после ДИЗАЙН-СИНТЕЗ сессии.** **План РАТИФИЦИРОВАН (D39.12, `architecture/11-implementation-plan.md`)** — три рубежа верификации, 4 гейтнутых арма. **Пак-11 (единой сессией, блоками):** ✅ Block A (D39.13: output-чанкер+edit-единицы+src→dst-редактор [закрытие D30.1]+eager-clients+rate-guard+base/enriched+банкнота-ядро) · ✅ continuation (D39.14: Go-майнер паритет-EXACT+банкнота-12-точек+DC-чекеры+`export --pairs`+арм-конфиги+волновой снапшот-аддитив; рубеж-2-долг погашен, clean bill, фикс-лист FL-1/2/3) · ✅ **арх-проход (D39.16: язык-данные майнера → `internal/lang`+`configs/langpacks/` байт-точно [парити EXACT, golden байт-идентичен], граница компиль-enforced; `x/text/language.Tag` ОТВЕРГНУТ [CLDR-инстабильность в вердиктах]; DC-чекеры/снапшот-фолд DEFER [майнер offline/нейтральность]; FL-1/2/3 залендены).** **Очередь:** R1 драйвер-свитч (волновой исполнитель W1/W1.5/W2 + структурный golden-рерайт + фолд `pack.Version()`/загрузка langpack при wiring майнера живым; промт `BACKEND_PLAN11_SESSION_PROMPT.md` АКТИВЕН + `backend/PACK11_CONT_REPORT.md §R1`) → приёмка → единый resnapshot (D30.9, §8-манифест) → пере-прогон 3–10 глав (армы glm/mistral/deepseek-pro) → чтение владельца (интерим-планка 2 претензии, D35). ja→ru-реплика = тест общности §B5 (DEFER-чекеры едут туда). 残: POLYGON_PACKAGE4 residual-трекер (пилот/18+/echo — отложен, D36.1); ре-чек прайса DeepSeek 24.07. > - **Стек (не менялся с D38.5):** draft deepseek-v4-flash (thinking ON) → editor glm-5 БИЛИНГВ v3-discourse (P1a+чэнъюй+few_shot-тумблер; свап-кандидаты mistral/deepseek-pro = армы пере-прогона) → судья gemini-3.1-pro-preview; канал B Mistral+grok; 7 ключей; ~$0.85/ранобэ (D30.4). > - **Ждём от владельца:** тачпойнты exp16 (карта подписи/пол · мини-голд алиасов · precision@30, `books/gu-zhenren/exp16/`, ~30–40 мин — для сид-дельты к пере-прогону) · развилки плана §10 (W1.5-UX · DC5-стих-политика · Edit-ceiling · и др.) · реплика ja→ru (тест общности §B5) · **ре-чек прайса DeepSeek у катовера слагов 24.07** (до него платных deepseek-прогонов нет) · чтение пере-прогона (после R1+resnapshot) · старые висящие: планка запуска (лучше-фана/гибрид/издательский) · билингв-якорь пилота (D25.9-Q1) · контаминация пилот-корпуса (D27.4) · publishable/waiver (D25.1) · FN-bound L3 (D25.4) · юр-пакет · провенанс 12-*-доков. -> - **ОТКРЫТЫЕ ПЕТЛИ (норма 25.07, перепись 180 инцидентов: класс NEVER_CLOSED больше не копим — каждая петля обязана получить диспозицию решено/отложено-с-записью/отклонено; ведёт оркестратор).** *Владелец:* мини-каноны сид-дельты — 元-корень (предложение «изначальн-») и 春秋蝉 (подписано «Весенне-осенняя цикада», ждёт подтверждения) · mini-live гейт петли ремонта (пак-16 D6) · ja→ru-реплика §B5 (тайминг) · старые Ф2.5-блокеры (билингв-якорь D25.9-Q1 · корпус/контаминация D27.4 · судья-дублёр D22.6 · планка запуска · publishable/waiver D25.1 · FN-bound L3 D25.4 · юр-пакет · xAI-ключ off перед продом D27). *Оркестратор:* ратификация дизайна пака-16 (по сдаче фазы 1) · фантом-гард D-ссылок в чек-лист лендинга. *Бэкенд (пак-16 ф.2):* строгий декод book.yaml/pair/models · unknown-key-тест пинит имя ключа · zero-value-паники PickedEntry/Bank. *Полигон (residual):* нет новых. +> - **ОТКРЫТЫЕ ПЕТЛИ (норма 25.07, перепись 180 инцидентов: класс NEVER_CLOSED больше не копим — каждая петля обязана получить диспозицию решено/отложено-с-записью/отклонено; ведёт оркестратор).** *Владелец:* мини-каноны сид-дельты — 元-корень (предложение «изначальн-») и 春秋蝉 (подписано «Весенне-осенняя цикада», ждёт подтверждения) · **включение платной петли ремонта — по замеру остатка на пост-профилактическом прогоне (D39.24; заменил mini-live гейт D6)** · ja→ru-реплика §B5 (тайминг) · старые Ф2.5-блокеры (билингв-якорь — D25 п.9 Q1 · контаминация корпуса — D27 п.4 · судья-дублёр D22.6 · планка запуска · publishable/waiver — D25 п.1 · FN-bound L3 — D25 п.4 · юр-пакет · xAI-ключ off перед продом D27). *Оркестратор:* ~~ратификация дизайна пака-16~~ РЕШЕНО D39.24 (25.07) · фантом-гард D-ссылок в чек-листе лендинга (действует). *Бэкенд (пак-16 ф.2, отмашка дана):* машинерия по D39.24 · строгий декод book.yaml/pair/models · unknown-key-тест пинит имя ключа · zero-value-паники PickedEntry/Bank. *Полигон (residual):* нет новых. > - Архивы хроники: `archive/PROGRESS-2026-07-04-10.md` (D31) · `archive/PROGRESS-2026-07-10-13.md` (D39.6-гигиена). Записи ниже — живая эра D39. ## Оркестратор №7 — промт пака-16 «слой-3 diff-редактор» ВЫДАН (двухфазный: дизайн → ратификация → стройка), 25.07 diff --git a/docs/architecture/05-decisions-log.md b/docs/architecture/05-decisions-log.md index 7177268f..e87aa5b2 100644 --- a/docs/architecture/05-decisions-log.md +++ b/docs/architecture/05-decisions-log.md @@ -1,9 +1,9 @@ -# Журнал решений оркестратора — контракт D1–D39.23 (развязки 04.07 · пакеты 09–10.07 · приёмка/качество-первым/пивот/эмпирика 11–12.07 · арх-ресет+стройка пере-прогонного стека 13–19.07) +# Журнал решений оркестратора — контракт D1–D39.24 (развязки 04.07 · пакеты 09–10.07 · приёмка/качество-первым/пивот/эмпирика 11–12.07 · арх-ресет+стройка пере-прогонного стека 13–19.07) > **⟶ КАРТА АКТУАЛЬНОСТИ (ревизия D31, продлена до D38.2 [12.07]; исторические записи ниже НЕ переписываются — дисциплина D23.3).** Читая контракт целиком, держи под рукой, что чем перекрыто: > - **Полностью superseded:** **D1 (моно-редактор) → D17 → D30.1 (редактор БИЛИНГВ)** · D9 (ja-приёмка) → D18 (蛊真人 zh→ru; ja — второй прогон) · D17 → D30.1 · скобка D19.4 «ключ без data-sharing» и D20.3 «чистый ключ = блокер пилота» → **D27** (единый ключ С data-sharing, отключение перед продом) · exp04-дефолт «editor grok-4.3» (D3) → D30.1 (grok reasoning-off из редакторских ролей СНЯТ — no-op; кандидат glm-5-билингв; gemini — премиум-эскалация только за санитайзером D30.3). > - **Частично амендировано:** D3 (канал B → D14.1/D19.1 + оговорки D22.5/D22.6; апекс-слаг `-preview` — D22.4; draft под вопросом exp13 — D30.6) · D6 («off/minimal для draft/edit» эродирован: draft thinking-ON принудительно, editor-off снят D30.1; живы per-роль принцип и D6.2-буфер) · D10 (+D24.2 alias-слепое пятно, истинная консистентность ≈99.5%) · D11-числа → exp08 → **D30.4** (флип D1 = +15–25%, ~$0.85/ранобэ; «$1.5–2» = неподписанная опция Б) · D12 (+D24.3 флоры max_tokens; +D29.1в rollup — амендмент вердикт-правила) · D13.1-арм из решающего → ПОДТВЕРЖДАЮЩИЙ (D30.1); +D25.3 prefix-анализ судьи · D14.2 закрыт D19.1/D22.4; D14.4 закрыт D22.1 · D15.3 исполнен; спека D15.2: v2→v3→v3.1 (D22.2), реализация = текущий пакет (D30.9) · D24.4 +D28.1 (precision/recall-трейдофф; hard-gate требует пере-замера) · порядок D24.5 отложен D26.1 (этап B — после читаемых 25 глав). -> - **Живое ядро без изменений:** D2 (+новый гейт-класс D30.3), D4, D5, D7, D8, D10-механизм, D12-таксономия отказов, D16, D18, D19.1–19.2, D21 (Ф2-механизмы voice/address/reveal), D22.5–22.7, D23 (golden = инвариант №8), D24–D39.23 — действующая голова контракта (**D39.23 = пак-15 «структура+форма» ратифицирован [хойст text/value-базы → сплит miner/checks/membank/chunk по ИЗМЕРЕННОЙ связности (прил. A пака-14 с поправкой ревью-шапки) · форм-фиксы Export/RequestHash/resolveChunkState · конфиг-слой пары (пар-калибровки из ран-конфигов, промпт-резолв конвенцией) · overlay-хвосты, unionStringMap-коллизия = громкая ошибка; §1 12-go-style амендирован; байт-точность+парити EXACT; crush-масштаб НЕ эмулируем]**; **D39.22 = эксп пере-прогона ЗАКРЫТ решением владельца; итерация №2 ОТЛОЖЕНА [её вопросы — довесок следующего платного прогона]; редактор = deepseek-v4-pro ИНТЕРИМ [судья №1 · проза/глоссарий · дефекты чинибельны · ×2 дешевле], glm-резерв, дефолт-конфиг при лендинге пака-13; курс = разработка бэкенда [масштаб книги · канал B · Ф2-механизмы · ja→ru §B5]**; **D39.21 = стайл-решения владельца: меры → единицы языка читателя [时辰=2ч] · generic-«гу» СРЕДНИЙ род, именные — по-термово из банка · стихи СМЫСЛОМ · 资质=«талант»; + директива аудита полноты банк-инъекции**; **D39.20 = ПЕРЕ-ПРОГОН исполнен+прочитан [операционка вся живьём: $0-резюм драфта ×3 арма · echo 0 · $6.63<$15; судья dspro 0.679>glm 0.589>>mistral 0.232 при floor-шуме 0; слепые чтения ×2: сводные выборы разошлись, чисто-художественные ИДЕНТИЧНЫ = mistral 4/5 → анти-корреляция гладкость↔верность доказана трижды]; планка ≤2 НЕ пройдена → пилот не открыт; mistral ИСКЛЮЧЁН как несущий редактор [критические смысловые вне гейтов; голос = north-star промпта]; dspro-vs-glm НЕ ратифицирован [зазор ~шум] → итерация №2 после механизации [драфт $0]; дефект-классы→механика: 时辰-юнит-чекер · число-масштабы · broken-word/latin · заголовок-шаблон · банк-дельта 元/资质/赤城 · mined_delta-крэш-фикс [пак-13]; решения владельца: род «гу»/меры/стихи/春秋蝉; полигон: авто-QA ключевать слепыми метками**; **D39.19 = пере-прогон-преп ЗАЛЕНДЕН [6 фиксов: R1-FL-A CLI-exit-3 · R1-FL-B reject-set-livelock-закрыт · R1-FL-C x/text-в-Ш-2 · echo-сплит · read-model-naming · ниты]; golden version-only [masked=0]; парити EXACT; §8-манифест полон; gender-signoff закрыт; бэкенд готов к пере-прогону**; **D39.18 = blast-radius edit-единицы: c-lite (редактура чистого остатка вместо бланка всей единицы; вариант владельца (c)+fallback (b)) залендён `488c8d4`, no-flag путь байт-идентичен; + 4 read-модель-фикса пост-лендинг-ревью [quality-echo/status-first-flagged/export-pairs/detail], открытое решение владельцу = echo-метрика семантика; R1-FL-A/B/C deferred в пере-прогон-преп**; **D39.17-errata = runWave parent-cancellation баг [мой рубеж-2 пропустил, 2-я сессия поймала], фикс `5fc4860`**; **D39.17 = R1 драйвер-свитч ЗАЛЕНДЕН [волновой исполнитель черновик∥→банк-майнинг-стоп→редактура∥; редактор не пере-рендерит draft; Ш-1/Ш-2; golden-рерайт; read-модели per-unit]; полный рубеж-2 [5 линз вкл. синк-с-ресёрчем] = clean bill по несущим осям, 3 MINOR/NOTE-фикса (R1-FL-A CLI-exit-код · R1-FL-B mining-стоп-reject · R1-FL-C x/text-версия) в стенд-only mining-пути → пере-прогон-преп; ПАК-11 ЗАВЕРШЁН**; **D39.16 = арх-проход ЗАЛЕНДЕН: майнер-данные (百家姓/Палладий/частицы/суффиксы) вынесены в `internal/lang`+`configs/langpacks/` [граница компиль-enforced, байт-точно — парити EXACT, golden байт-идентичен]; FL-1/2/3+тест-путь; РАТИФИЦИРОВАНЫ 3 дивергенции: language.Tag ОТВЕРГНУТ [CLDR-инстабильность в вердиктах], DC-чекеры+снапшот-фолд DEFER [майнер offline/нейтральность]; общность структурно доказана; промт отработан→архив**; **D39.15 = директива владельца: изоляция языковых данных из `pipeline/` [движок общий · `internal/lang/`+`configs/langpacks/` данными-файлами · `x/text/language.Tag` вместо `isRuTarget` · снапшот-folded как промпты · алгоритмы не трогать]; архитектурный проход ПЕРЕД R1 [ре-секвенс]; поведенчески нейтрально zh→ru [golden байт-стабилен]; не минор-хант/не rewrite-ради-rewrite; промт `BACKEND_ARCH_CLEANUP_SESSION_PROMPT.md` 5-фазный [ресёрч→синтез→фикс-лист→исполнение→ревью]; ja→ru = тест общности §B5 позже**; **D39.14 = пак-11 continuation ПРИНЯТ и залендён [R2 майнер паритет-EXACT · R3 банкнота 12-точек · R4 DC-чекеры+export--pairs · R5 арм-конфиги · R1-аддитив; рубеж-2 D39.13-долга погашен: 3 MINOR/PARTIAL-находки [FL-1 bankTokenBudget-фолд · FL-2 banknote-телеметрия-резюм · FL-3 emission-top-N-кап], все латентны → R1-продолжение; R1 драйвер-свитч резидуал; clean bill по деньгам/wire/порту]**; **D39.13 = пак-11 Block A ПРИНЯТ и залендён [WS2 целиком (вкл. src→dst-блок редактора = закрытие D30.1) + WS1a/b/c-инфра + WS4-ядро; 2 девиации ратифицированы; резидуал R1–R5 в PACK11_REPORT; промт остаётся активным; рубеж-2 приёмки — долг]**; **D39.12 = план `architecture/11-implementation-plan.md` РАТИФИЦИРОВАН [три рубежа: ревью-2 F1–F11 → 7-агентный адверсариал → подтверждающий пас; пер-волновой снапшот + инвариант секвенирования = «переоплата ОДНА»; EditCeilingOut=3200; лемматизатор дефолт B; 4 гейтнутых арма]; бэкенд-пак ЕДИНОЙ сессией — `BACKEND_PLAN11_SESSION_PROMPT.md`**; **D39.11 = директива владельца: стройка ТОЛЬКО после дизайн-синтез сессии с верифицированными алгоритмами; хендофф №5**; **D39.10 = exp16: WHICH подтверждена [V-C recall 0.97, $0] / WHAT→банкнота+Палладий [canon-recovery <70%], 9B-споттер не нужен, Z1→облако, ре-аудит exp14b чист [errata не требуется]; исследовательская программа ЗАВЕРШЕНА → бэкенд-пак слоёв 1+4**; **D39.9 = Q4a: верность НЕ на translate-стадии [flash уже верен, pro ×3.67 не ратифицирован], смысл ломает glm-РЕДАКТОР [свап-арм приоритетен], DET-пол 0.261; exp14b-правила дефектны → $0-ре-аудит в exp16**; **D39.8 = слепое чтение: планка не пройдена никем, дефекты распределены [читатель подтвердил floor-null], битва качества → дефект-классы [时辰-юниты, числа, gender-enforce, стих, регистр]**; **D39.7 = exp15 залендён REV.2: «граница вредит» отозван [артефакт окна судьи], все когезия-эффекты под floor 0.126, «sequential нигде не лучше» LOO-stable [не ратифицированный зелёный свет], фертильность 1.20/0.39, $12.79/$15; гейт = слепое чтение владельца**; **D39.6 = research/20 банк-майнинг залендён [детектор-лестница V-A/B/C, алиас-ярусы, banknote-v1 с 12 гейт-точками, §B5 плагины P1–P6, §D-пре-рег]; полигон-стадия после exp15**; **D39.5 = пак-1.5+F1–F9 залендены [tmctl export с manifest/drift-гардами · fold-first санитайзер · bounded глосс · пример zh-ru]; экстракция полигона впредь через tmctl export**; **D39.4 = адверсариал-долг ПОГАШЕН [8 осей: 0 CRIT/1 HIGH/6 MED], пак-1.5 придержан под фикс-лист F1–F9 в §T4**; **D39.3 = пакет банк-майнинга W1.5 [диспозиция H15/V4-п4] ратифицирован, research/20-промт с HOLD до фриза exp15**; **D39.2 = трек-A пак-1 залендён `d3f6b34`: trust-gated suppressor [терм-дрейф закрыт в КОДЕ] + export-contract нормализация + pair-сеам + реестры + quality-report; адверсариал-долг за 529**; **D39.1 = research/19 залендён: конфликт exp14↔research/18 разрешён [эмиссия≠окно≠связность], Go-спека чанкера+когезии, волновая архитектура W0–W3 [вводная владельца: параллелизм], пре-рег эмпирики Q0–Q5; оркестратор-ревью снято владельцем**; **D39 = АРХ-РЕСЕТ: синк-аудит сломанного телефона [65 находок, 0 refuted, `08-sync-audit-ledger.md`] + целевая 7-слойная архитектура [`09-target-architecture.md`] + фазовый план [трек A build-now ∥ трек B ресёрч, пере-прогон после]; формализует фантомный «D35.7» как цель слоя 1; терм-дрейф код-корень жив [сид-воркэраунд]; редактор 4-мандата-full-regen = причина инверсий**; D35 = пивот качество-первым; D36 = приёмка research/18; D37 = приёмка exp14: претензия-1=промпт-рычаг; D38 = приёмка exp14b: «только gpt-5.4» ОПРОВЕРГНУТО [mistral/deepseek-pro чинят], фронтир в дефолт НЕ нужен, корень терм-дрейфа = статус-draft сида; **D38.1 = слепой h2h P1a↔F-disc залендён [ChatGPT «фронтир сильнее» / Claude «near-parity» расходятся, СХОДЯТСЯ: дефекты = инфра] → РАЗВОРОТ «мерить→строить», выдан BACKEND_INFRA_PACK; D38.2 = консолидация/док-гигиена: спент exp-скрипты в `eval/exp12|13|14|14b/`, статус-доки+.puml актуализированы; D38.3 = инфра-пак читаемости залендён (санитайзер-классы strip+export + число/omission-гард), верифицирован исполнением [build/vet/test зелёные, 3 span-дефекта запинены]; D38.4 = аудит полноты exp14/14b: чэнъюй-рычаг забыт near-term→дописан в дискурс-промпт; inversion-гард D37 §2г не строился (число/omission ≠ полярность)→editor-swap = защита от инверсий (приоритет↑); нарезка = re-run-арм; гард-тумблер вкл. при resnapshot; D38.5 = компоненты resnapshot залендены+верифицированы [reseed-сид eb409f2 · дискурс+чэнъюй editor v3 0ac5f7a с few_shot-тумблером] → следующий шаг единый resnapshot + пере-прогон**). +> - **Живое ядро без изменений:** D2 (+новый гейт-класс D30.3), D4, D5, D7, D8, D10-механизм, D12-таксономия отказов, D16, D18, D19.1–19.2, D21 (Ф2-механизмы voice/address/reveal), D22.5–22.7, D23 (golden = инвариант №8), D24–D39.24 — действующая голова контракта (**D39.24 = дизайн пака-16 diff-редактора ратифицирован с перестановкой: $0-части (позиционная детекция · read-only скан · фиксы ru_hours_re/半个时辰) строим, платная машинерия §D3–D5+§15.2 под enabled:false, включение = решение владельца по замеру остатка пост-профилактики; поправки §15.2 (позитивный ре-гейт+анти-удаление · счётчики из role='repair' без миграции · отбрасывание пересечений · reserve-деградация · classify-гард); ru-target-долг = ограничение**; **D39.23 = пак-15 «структура+форма» ратифицирован [хойст text/value-базы → сплит miner/checks/membank/chunk по ИЗМЕРЕННОЙ связности (прил. A пака-14 с поправкой ревью-шапки) · форм-фиксы Export/RequestHash/resolveChunkState · конфиг-слой пары (пар-калибровки из ран-конфигов, промпт-резолв конвенцией) · overlay-хвосты, unionStringMap-коллизия = громкая ошибка; §1 12-go-style амендирован; байт-точность+парити EXACT; crush-масштаб НЕ эмулируем]**; **D39.22 = эксп пере-прогона ЗАКРЫТ решением владельца; итерация №2 ОТЛОЖЕНА [её вопросы — довесок следующего платного прогона]; редактор = deepseek-v4-pro ИНТЕРИМ [судья №1 · проза/глоссарий · дефекты чинибельны · ×2 дешевле], glm-резерв, дефолт-конфиг при лендинге пака-13; курс = разработка бэкенда [масштаб книги · канал B · Ф2-механизмы · ja→ru §B5]**; **D39.21 = стайл-решения владельца: меры → единицы языка читателя [时辰=2ч] · generic-«гу» СРЕДНИЙ род, именные — по-термово из банка · стихи СМЫСЛОМ · 资质=«талант»; + директива аудита полноты банк-инъекции**; **D39.20 = ПЕРЕ-ПРОГОН исполнен+прочитан [операционка вся живьём: $0-резюм драфта ×3 арма · echo 0 · $6.63<$15; судья dspro 0.679>glm 0.589>>mistral 0.232 при floor-шуме 0; слепые чтения ×2: сводные выборы разошлись, чисто-художественные ИДЕНТИЧНЫ = mistral 4/5 → анти-корреляция гладкость↔верность доказана трижды]; планка ≤2 НЕ пройдена → пилот не открыт; mistral ИСКЛЮЧЁН как несущий редактор [критические смысловые вне гейтов; голос = north-star промпта]; dspro-vs-glm НЕ ратифицирован [зазор ~шум] → итерация №2 после механизации [драфт $0]; дефект-классы→механика: 时辰-юнит-чекер · число-масштабы · broken-word/latin · заголовок-шаблон · банк-дельта 元/资质/赤城 · mined_delta-крэш-фикс [пак-13]; решения владельца: род «гу»/меры/стихи/春秋蝉; полигон: авто-QA ключевать слепыми метками**; **D39.19 = пере-прогон-преп ЗАЛЕНДЕН [6 фиксов: R1-FL-A CLI-exit-3 · R1-FL-B reject-set-livelock-закрыт · R1-FL-C x/text-в-Ш-2 · echo-сплит · read-model-naming · ниты]; golden version-only [masked=0]; парити EXACT; §8-манифест полон; gender-signoff закрыт; бэкенд готов к пере-прогону**; **D39.18 = blast-radius edit-единицы: c-lite (редактура чистого остатка вместо бланка всей единицы; вариант владельца (c)+fallback (b)) залендён `488c8d4`, no-flag путь байт-идентичен; + 4 read-модель-фикса пост-лендинг-ревью [quality-echo/status-first-flagged/export-pairs/detail], открытое решение владельцу = echo-метрика семантика; R1-FL-A/B/C deferred в пере-прогон-преп**; **D39.17-errata = runWave parent-cancellation баг [мой рубеж-2 пропустил, 2-я сессия поймала], фикс `5fc4860`**; **D39.17 = R1 драйвер-свитч ЗАЛЕНДЕН [волновой исполнитель черновик∥→банк-майнинг-стоп→редактура∥; редактор не пере-рендерит draft; Ш-1/Ш-2; golden-рерайт; read-модели per-unit]; полный рубеж-2 [5 линз вкл. синк-с-ресёрчем] = clean bill по несущим осям, 3 MINOR/NOTE-фикса (R1-FL-A CLI-exit-код · R1-FL-B mining-стоп-reject · R1-FL-C x/text-версия) в стенд-only mining-пути → пере-прогон-преп; ПАК-11 ЗАВЕРШЁН**; **D39.16 = арх-проход ЗАЛЕНДЕН: майнер-данные (百家姓/Палладий/частицы/суффиксы) вынесены в `internal/lang`+`configs/langpacks/` [граница компиль-enforced, байт-точно — парити EXACT, golden байт-идентичен]; FL-1/2/3+тест-путь; РАТИФИЦИРОВАНЫ 3 дивергенции: language.Tag ОТВЕРГНУТ [CLDR-инстабильность в вердиктах], DC-чекеры+снапшот-фолд DEFER [майнер offline/нейтральность]; общность структурно доказана; промт отработан→архив**; **D39.15 = директива владельца: изоляция языковых данных из `pipeline/` [движок общий · `internal/lang/`+`configs/langpacks/` данными-файлами · `x/text/language.Tag` вместо `isRuTarget` · снапшот-folded как промпты · алгоритмы не трогать]; архитектурный проход ПЕРЕД R1 [ре-секвенс]; поведенчески нейтрально zh→ru [golden байт-стабилен]; не минор-хант/не rewrite-ради-rewrite; промт `BACKEND_ARCH_CLEANUP_SESSION_PROMPT.md` 5-фазный [ресёрч→синтез→фикс-лист→исполнение→ревью]; ja→ru = тест общности §B5 позже**; **D39.14 = пак-11 continuation ПРИНЯТ и залендён [R2 майнер паритет-EXACT · R3 банкнота 12-точек · R4 DC-чекеры+export--pairs · R5 арм-конфиги · R1-аддитив; рубеж-2 D39.13-долга погашен: 3 MINOR/PARTIAL-находки [FL-1 bankTokenBudget-фолд · FL-2 banknote-телеметрия-резюм · FL-3 emission-top-N-кап], все латентны → R1-продолжение; R1 драйвер-свитч резидуал; clean bill по деньгам/wire/порту]**; **D39.13 = пак-11 Block A ПРИНЯТ и залендён [WS2 целиком (вкл. src→dst-блок редактора = закрытие D30.1) + WS1a/b/c-инфра + WS4-ядро; 2 девиации ратифицированы; резидуал R1–R5 в PACK11_REPORT; промт остаётся активным; рубеж-2 приёмки — долг]**; **D39.12 = план `architecture/11-implementation-plan.md` РАТИФИЦИРОВАН [три рубежа: ревью-2 F1–F11 → 7-агентный адверсариал → подтверждающий пас; пер-волновой снапшот + инвариант секвенирования = «переоплата ОДНА»; EditCeilingOut=3200; лемматизатор дефолт B; 4 гейтнутых арма]; бэкенд-пак ЕДИНОЙ сессией — `BACKEND_PLAN11_SESSION_PROMPT.md`**; **D39.11 = директива владельца: стройка ТОЛЬКО после дизайн-синтез сессии с верифицированными алгоритмами; хендофф №5**; **D39.10 = exp16: WHICH подтверждена [V-C recall 0.97, $0] / WHAT→банкнота+Палладий [canon-recovery <70%], 9B-споттер не нужен, Z1→облако, ре-аудит exp14b чист [errata не требуется]; исследовательская программа ЗАВЕРШЕНА → бэкенд-пак слоёв 1+4**; **D39.9 = Q4a: верность НЕ на translate-стадии [flash уже верен, pro ×3.67 не ратифицирован], смысл ломает glm-РЕДАКТОР [свап-арм приоритетен], DET-пол 0.261; exp14b-правила дефектны → $0-ре-аудит в exp16**; **D39.8 = слепое чтение: планка не пройдена никем, дефекты распределены [читатель подтвердил floor-null], битва качества → дефект-классы [时辰-юниты, числа, gender-enforce, стих, регистр]**; **D39.7 = exp15 залендён REV.2: «граница вредит» отозван [артефакт окна судьи], все когезия-эффекты под floor 0.126, «sequential нигде не лучше» LOO-stable [не ратифицированный зелёный свет], фертильность 1.20/0.39, $12.79/$15; гейт = слепое чтение владельца**; **D39.6 = research/20 банк-майнинг залендён [детектор-лестница V-A/B/C, алиас-ярусы, banknote-v1 с 12 гейт-точками, §B5 плагины P1–P6, §D-пре-рег]; полигон-стадия после exp15**; **D39.5 = пак-1.5+F1–F9 залендены [tmctl export с manifest/drift-гардами · fold-first санитайзер · bounded глосс · пример zh-ru]; экстракция полигона впредь через tmctl export**; **D39.4 = адверсариал-долг ПОГАШЕН [8 осей: 0 CRIT/1 HIGH/6 MED], пак-1.5 придержан под фикс-лист F1–F9 в §T4**; **D39.3 = пакет банк-майнинга W1.5 [диспозиция H15/V4-п4] ратифицирован, research/20-промт с HOLD до фриза exp15**; **D39.2 = трек-A пак-1 залендён `d3f6b34`: trust-gated suppressor [терм-дрейф закрыт в КОДЕ] + export-contract нормализация + pair-сеам + реестры + quality-report; адверсариал-долг за 529**; **D39.1 = research/19 залендён: конфликт exp14↔research/18 разрешён [эмиссия≠окно≠связность], Go-спека чанкера+когезии, волновая архитектура W0–W3 [вводная владельца: параллелизм], пре-рег эмпирики Q0–Q5; оркестратор-ревью снято владельцем**; **D39 = АРХ-РЕСЕТ: синк-аудит сломанного телефона [65 находок, 0 refuted, `08-sync-audit-ledger.md`] + целевая 7-слойная архитектура [`09-target-architecture.md`] + фазовый план [трек A build-now ∥ трек B ресёрч, пере-прогон после]; формализует фантомный «D35.7» как цель слоя 1; терм-дрейф код-корень жив [сид-воркэраунд]; редактор 4-мандата-full-regen = причина инверсий**; D35 = пивот качество-первым; D36 = приёмка research/18; D37 = приёмка exp14: претензия-1=промпт-рычаг; D38 = приёмка exp14b: «только gpt-5.4» ОПРОВЕРГНУТО [mistral/deepseek-pro чинят], фронтир в дефолт НЕ нужен, корень терм-дрейфа = статус-draft сида; **D38.1 = слепой h2h P1a↔F-disc залендён [ChatGPT «фронтир сильнее» / Claude «near-parity» расходятся, СХОДЯТСЯ: дефекты = инфра] → РАЗВОРОТ «мерить→строить», выдан BACKEND_INFRA_PACK; D38.2 = консолидация/док-гигиена: спент exp-скрипты в `eval/exp12|13|14|14b/`, статус-доки+.puml актуализированы; D38.3 = инфра-пак читаемости залендён (санитайзер-классы strip+export + число/omission-гард), верифицирован исполнением [build/vet/test зелёные, 3 span-дефекта запинены]; D38.4 = аудит полноты exp14/14b: чэнъюй-рычаг забыт near-term→дописан в дискурс-промпт; inversion-гард D37 §2г не строился (число/omission ≠ полярность)→editor-swap = защита от инверсий (приоритет↑); нарезка = re-run-арм; гард-тумблер вкл. при resnapshot; D38.5 = компоненты resnapshot залендены+верифицированы [reseed-сид eb409f2 · дискурс+чэнъюй editor v3 0ac5f7a с few_shot-тумблером] → следующий шаг единый resnapshot + пере-прогон**). Ответ на вопросы бэкенд-сессии (PROGRESS §«Вопросы от бэкенда», §«[НУЖНО РЕШЕНИЕ] перед Фазой 1» — с ревизии D31 в `archive/PROGRESS-2026-07-04-10.md`) и полигона (think-режим). Каждое решение прошло адверсариальную панель из 3 критиков (research / экономика / исполнимость в коде Фазы 0); ни одно не отклонено, все уточнены. Это **контракт Фазы 1** — бэкенд исполняет отсюда; при конфликте с буквой 01-decisions/02-mvp-plan — источник истины здесь (потом сольём в основные доки). @@ -797,3 +797,11 @@ API-529-долг закрыт: 8-осевой refute-by-default воркфлоу **Триггер:** харнес-сверка пака-14 (crush ~71 фокус-пакет / провайдеры-данные; GalTransl/AiNiee — data-мандат, но текут языком в тела чекеров = наша вылеченная утечка) + **ИЗМЕРЕННАЯ карта связности** `internal/pipeline` (отчёт пака-14, прил. A, с поправкой ревью-шапки: ведро «distinct/matches 8» — греп-артефакт; miner→membank = 32 вхождения / 14 кодовых = шаред-субстрат, не API). §1-эскейп-тест («отдельная подсистема с узким API») прогнан против фактуры — его проходят miner/checks/membank/chunk. Не гонка за индустрией: crush-масштаб НЕ эмулируется, сплит только по измеренному узкому API (4 подсистемы + база). **Решения:** (а) **сплит**: порядок — хойст общего субстрата (`internal/text`: NormalizeSourceKey/RunesEqual/TokenizeCyrillic + пакет value-типов Chunk/MinerChunk/SegBudget/Document) → `internal/miner` → `internal/checks` → `internal/membank` → `internal/chunk`; драйвер+инфра остаются композит-корнем `internal/pipeline`; каждый этап лендится верифицированным (build/test/golden/парити) до следующего; экспорт-поверхности — прил. B отчёта пака-14 как отправная точка. (б) **§1 `12-go-style-notes.md` амендирован**: file-count не смелл — остаётся; предписан ратифицированный сплит. (в) **форм-фиксы тем же паком**: Export()-декомпозиция с ОБЩЕЙ derivation member-drop (сейчас байт-согласие по комменту) · sticky-сентинел → поле · RequestHash 13 позиционных → struct · resolveChunkState 5-кортеж → struct. (г) **конфиг-слой пары**: пар-калибровки (fertility/len_ratio_bounds) из ран-конфигов → `configs/pairs/<пара>`; промпт-резолв конвенцией `prompts/<пара>/<роль>.md` вместо перечисления `prompts:`-мап (fail-loud сохраняется; `prompt_override` для армов; wire-нейтрально — байты/SHA промптов не двигаются). (д) **остаток пака-14**: тесты Палладия (unknown-category/required-fail) · fail-loud оверлея уровнем выше (пропавший overlayRoot = ошибка — явный opt-in; чужие root-файлы; категория-опечатка в overlay) · **unionStringMap: коллизия ключа base↔overlay = громкая ошибка** (override не ратифицирован; пересмотр — когда книге реально понадобится) · empty-probe гард lintMagnitudeScale · терминаторы chunker/coverage → одна точка истины · `第` → cjk-section. **НЕ в паке:** diff-редактор (слой 3) · isRuTarget/таргет-тред (канал B) · translit-interj (DEFER D39.16). Инвариант — байт-точность (golden байт-идентичен · парити EXACT · wire нетронут). Промт: `BACKEND_PACK15_STRUCTURE_SESSION_PROMPT.md`. + +## D39.24 — Дизайн пака-16 «слой-3 diff-редактор» РАТИФИЦИРОВАН с перестановкой §15.1: всё $0-е строим, платный вызов за `enabled: false` до замера остатка (25.07, оркестратор №7). ✅ + +**Отчёт:** `docs/archive/reports/PACK16_DESIGN_2026-07-25.md` (§0–14 дизайн + §15 пост-панельная ревизия отдельным слоем). Принят ЦЕЛИКОМ с поправками §15.2: (A) ре-гейт = ПОЗИТИВНЫЕ пост-условия по классам + анти-удаление (голое «отсутствие флага» удовлетворялось удалением спана); (B) счётчики починок — из durable-чекпойнтов `role='repair'` + namespace `final_hash` (миграция v11 ОТМЕНЕНА; self-healing на резюме); (C) пересекающиеся спаны отбрасываются + ассерт непересечения перед сплайсом; (D) `errReserveCeiling` в ремонте деградирует как опциональный хоп (иначе терялась строка оплаченной редактуры); (E) `classify` — шестой гард ответа ремонта (ловит soft_refusal, проходящий пять гардов). + +**Замер §15.1 (реплицирован панелью и сессией независимо; таймлайн сверен оркестратором):** профилактика 时辰/成 (`2f91b04`, 24.07 03:19) легла ~3 ч ПОСЛЕ снятия цифр пере-прогона (24.07 00:09) → на прод-арме dspro всего 2 кандидата/7 юнитов, из них 1 — FP-гипербола (杀了千万人); поверхность `shichen_re` = 120/266 (半个时辰 — 32% — слеп); живой FP `ru_hours_re` («три части» → актуатор переписал бы в «шесть часов» И правка прошла бы ре-гейт). → **Порядок работ:** (1) `checks.RepairCandidates` — позиционная детекция, вердикт-нейтральная (`CheapGateVersion` НЕ бампится — доказательство исполнением: golden байт-идентичен + счётчики фикстур не сдвинулись); (2) read-only скан остатка по классам ($0 на любой прогнанной книге); (3) два пар-данных фикса — правая граница `ru_hours_re` + 半个时辰 в `shichen_re` (обязательны ДО актуатора; сдвиг версии пака = version-only, классифицировать при сдаче); (4) машинерия ремонта §D3–D5 с §15.2 — под `enabled: false`; **ВКЛЮЧЕНИЕ платного пути = отдельное решение владельца по замеру остатка на пост-профилактическом прогоне** (открытая петля; заменяет mini-live гейт D6). + +**Ратифицированные отклонения (§15.3):** промпты ремонта `prompts/<пара>/repair/<класс>.md` (ключ = класс дефекта, не роль стадии; fail-loud только для применимых классов пары) · тип ошибки доезжает ВЫБОРОМ файла промпта, не интерполяцией англ. диагностики (языковая утечка) · запрет additive-провайдера в repair-роли (least mechanism вместо нового кноба). **Унаследованный ru-target-долг** (isRuTarget-колл-сайты · TokenizeCyrillic · множитель ×2 в Go) зафиксирован ОГРАНИЧЕНИЕМ: класс работает на новой ПАРЕ данными, на новом ТАРГЕТЕ — нет; НЕ чинить этим паком (едет с канал-B/таргет-работой). Метод фазы 1 (эхо → 7 инвентаризаций → панель 6 линз → 13 скептиков «опровергни по коду» → §15-ревизия отдельным слоем, тело не переписано) — образец для будущих дизайн-фаз. diff --git a/docs/archive/reports/PACK16_DESIGN_2026-07-25.md b/docs/archive/reports/PACK16_DESIGN_2026-07-25.md new file mode 100644 index 00000000..7e8e9428 --- /dev/null +++ b/docs/archive/reports/PACK16_DESIGN_2026-07-25.md @@ -0,0 +1,571 @@ +# ПАК-16 «слой-3 diff-редактор» — ДИЗАЙН-ПРЕДЛОЖЕНИЕ (ФАЗА 1, 25.07.2026) + +> Бэкенд-сессия по промту `docs/BACKEND_PACK16_DIFF_EDITOR_SESSION_PROMPT.md` (ревизия 2). +> **Кода НЕ трогал** — ни одной правки в `backend/`; всё ниже — предложение. **СТОП на ратификацию.** +> +> **⚠ ЧИТАТЬ §15 «РЕВИЗИЯ 2» ПЕРЕД РАТИФИКАЦИЕЙ.** Тело §0–§14 — дизайн в том виде, в каком он ушёл +> на адверсариальную панель. Панель (6 линз + скептик-верификация каждого возражения) вернула +> **4 выживших MAJOR**, а замер, который я после этого воспроизвёл САМ, меняет **рекомендуемый скоуп пака**. +> Все поправки собраны в §15; расхождения тела с §15 разрешаются в пользу §15. + +## ЭХО-БЛОК (как я понял задачу — расхождение чините сразу) + +1. **Скоуп:** спроектировать (не строить) петлю «детерминированный флаг → адресный ремонт минимального спана дешёвым вызовом → детерминированный re-gate → при неудаче прежнее поведение». +2. **Мандат:** решения мои и аргументированные; приоры оркестратора — приоры, а не предписания; north-star — ИЗМЕРЕННЫЙ вклад петли по классам, не вера. +3. **Инварианты «ровно так»:** D2 не хард-гейт; лестница `regenerate_before_escalate`→`escalate_to` не меняет семантику молча; $0-резюм драфта цел; repair-вызовы = новый класс (чекпойнт/RequestHash/леджер/маркер); **no-repair путь байт-идентичен, golden зелён БЕЗ пере-капчера**; пара/книга — данными, не Go; DeepSeek thinking никогда не off; книга и производные вне git. +4. **Не делать:** канал B, копирование GPL/AGPL, пар/книго-литералы в Go, live-вызовы без санкции, расширение скоупа без пинга. +5. **Приёмка фазы 1:** этот отчёт + адверсариальный селф-ревью 4 линзами, затем СТОП. +6. **Хвосты фазы 2** (строгий декод `book.yaml` и пар/models-слоёв, unknown-key-тест, zero-value-паники) — в паке, но после ратификации; пред-проверка стендовых `book.yaml` сделана уже сейчас (прил. B) — она read-only и ничего не стоила. + +--- + +## 0. Метод: чем грунтован дизайн + +Прочитан код (не заголовки): `internal/checks/*` целиком, `internal/pipeline/{chunkrun,stagerun,waverun,escalation,disposition,snapshot,render,resume,runner,banknote,quality,export,status}.go`, `internal/config/{pipeline,pair,book,models}.go`, `internal/store/{ledger,requestlog,migrate}.go`, `internal/lang`, промты `prompts/zh-ru/*`, пар-данные `configs/langpacks/zh-ru/dc-checkers.txt`, шиппинг-конфиги. Сверх этого прогнан воркфлоу из **7 независимых read-only инвентаризаций** (позиции чекеров · класс «чинится чистым кодом» · money/resume/golden-поверхность нового класса вызовов · role/stage/wave-поверхность · стенд-аудит конфигов · объём дефектов на живых выходах rerun2 · прайор-арт) — все утверждения ниже с `file:line`; несогласия агентов с моими выводами разобраны явно (§10, §11). + +Ключевой факт, определивший весь дизайн: **сегодня типизированные факты чекеров вообще не возвращаются в контур.** `checks.RunCheapGates` запускается ПОСЛЕ последовательности стадий, на уровне юнита (`waverun.go:477`), пишется в `retrieval_state` и больше ни на что не влияет. Санитайзер — единственная вердикт-ось (`chunkrun.go:51`), и он же — единственный, у кого уже есть полный цикл «нашёл → детерминированно починил → перепроверил» (`chunkrun.go:60`: `SanitizeOutput(StripCosmetic(x)).Total()==0`). Петля ремонта — это обобщение ровно этого цикла на классы, где детерминированной починки не существует. + +--- + +## D1. Единица ремонта + маппинг флаг→спан + +### Инвентаризация: кто даёт позиции, кто только факт + +Полная таблица — прил. A. Сводка по 21 детерминированной проверке: + +| Класс | Вход | Позиция сейчас | Позиция достижима аддитивно | +|---|---|---|---| +| DC1 时辰 | src↔final | нет (`FindStringSubmatch` ×2, `checkers.go:132,140`) | **да, обе стороны** — `FindStringSubmatchIndex`, та же семантика матча | +| DC2 千万/数十万 | src↔final | нет (`Contains`/`MatchString`, `checkers.go:199-205`) | да — `strings.Index`/`FindStringIndex` | +| percent 成 | src↔final | src-матч есть, tgt нет (`checkers.go:258-266`) | да | +| latin residue | final | **рун-оффсеты уже считаются** и выбрасываются (`checkers.go:290-303`) | да, ноль работы | +| broken word | final | нет — `text.TokenizeCyrillic` лоуэркейсит и теряет оффсеты (`runes.go:32-50`) | да, но нужен оффсет-сохраняющий проход | +| DC6 register | final | рун-индекс есть (`checkers.go:225-232`) | да | +| санитайзер 1/2/6 | final | **байт-оффсеты уже есть** (`sanitizer.go:247,276,617-634`) | да | +| санитайзер 4 (latin) | final | нет вообще | слабо | +| coverage / length-collapse / number-drift / classify | целый текст | нет по природе (скаляр/порог) | **нет** | +| glossary post-check | final | нет — дефект есть ОТСУТСТВИЕ dst-формы (`mempostcheck.go:61-77`) | **нет** (нет чего локализовать) | + +В пакете `checks` **уже есть словарь спанов**: `glossSpans` возвращает `[][2]int` байт-диапазонов (`sanitizer.go:554-570`). Заводить новый тип «на вырост» не надо — это least-mechanism (§1 норматива). + +### Решение D1 + +1. **Единица ремонта = ПРЕДЛОЖЕНИЕ (одно, при необходимости с соседним) вокруг матча, а не строка и не абзац.** Строку брать нельзя, потому что наш редактор легитимно реверстывает — это его прямой мандат (`editor.md:15-19`). Основание из research/22 цитирую ТОЧНО, без расширения: «punctuation add/drop + line-parity **воюют с нашим editor-reflow** (их VN = line-by-line без reflow; наш редактор легитимно сливает пунктуацию/абзацы) — брать нельзя вслепую» (`22-domain-harness-survey.md:118`, продублировано таблицей `:181` — «Осознанно НЕТ»). То есть в наших доках зафиксирован конфликт СТРОЧНЫХ проверок с reflow; вывод «не якорить ремонт на индексах строк» — мой, выведенный из этого конфликта, а не цитата. Границы предложения берутся из общей данной `lang.DefaultTerminators()` (та же таблица, что у чанкера и coverage — `internal/lang/data/terminator.txt`), значит расширение спана пар-агностично. +2. **Новая ЧИСТАЯ функция в `checks`** (детекция — работа этого пакета), а не поля в существующих Result-структурах: + +```go +// checks/repair.go +type RepairClass string // engine-идентификаторы: "dc1_time_units", "dc2_magnitude", … +type RepairCandidate struct { + Class RepairClass + DstSpan [2]int // байт-диапазон в FINAL — что заменяем + SrcSpan [2]int // байт-диапазон в SOURCE (нулевой для target-only классов) + Detail string // операторская диагностика (англ.-первичная, как у остальных) +} +func RepairCandidates(source, final string, cfg CheapGateConfig) []RepairCandidate +``` + +Почему НЕ дополнять `CheapGateResult`/`SanitizerResult` полями позиций: `CheapGateResult` сериализуется в `retrieval_state.style_detail` (`chunkrun.go:113-117`), `PostcheckMiss` — в `postcheck_detail`, и его golden-байты НЕ пусты (`capture.golden:92`, три тега без `omitempty`, `mempostcheck.go:37-41`). Отдельный тип держит новый механизм вне сериализуемых поверхностей полностью — риск байт-сдвига равен нулю по построению, а не по аккуратности. (Замечу для полноты: у `CheapGateResult` все теги `omitempty` и все golden-строки несут `style_detail=` ПУСТО, так что аддитив там был бы безопасен — но он не нужен.) + +3. **ГАРД ОДНОЗНАЧНОСТИ (несущий):** класс порождает кандидата, **только если в юните ровно один матч с каждой релевантной стороны**. Два 时辰 в источнике и одно «час…» в переводе — кандидата НЕТ, остаётся сегодняшний флаг. Это прямо закрывает главную дыру, найденную инвентаризацией: сегодняшние src↔dst-чекеры сопоставляют ПЕРВЫЙ матч с ПЕРВЫМ (`checkers.go:132,140`), то есть выравнивания у нас нет и его неоткуда взять. Прайор-арт формулирует то же правило: «неоднозначный якорь (≥2 матчей) обязан отвергать, а не молча патчить первый». +4. **Ремонт применяется к СЫРОМУ тексту завершившейся стадии** (тому же, что видят чекеры), а не к экспортному: `ExportNormalize` и `ApplyHeading` накладываются позже (`waverun.go:495-507`), поэтому оффсеты, посчитанные на сыром тексте, остаются валидными ровно до места применения. Экспортный контракт применяется поверх отремонтированного текста как обычно. + +### Расхождение с приором D1 + +Приор: «дотипизация = позиции в Result-структуры аддитивными полями». **Не согласен:** позиции нужны только петле, а Result-структуры — персистентная поверхность. Отдельная чистая функция даёт то же самое, не касаясь сериализации (и не заставляя каждый чекер платить за поле, которым он не пользуется). + +--- + +## D2. Классы петли v1 vs flag-only vs «чинится чистым кодом» + +**Критерий отбора (мой, строже приорного):** класс входит в LLM-петлю, только если выполнены ВСЕ четыре условия — (1) есть детерминированный кандидат-спан под гардом однозначности; (2) есть детерминированный ре-чек, который **нельзя удовлетворить тривиально** (не «слово исчезло», а «инвариант класса восстановлен»); (3) правильный вывод НЕ вычислим из байтов (иначе это класс (в)); (4) риск порчи хорошего текста снимается протоколом (см. NO-CHANGE ниже), а не верой. + +| Класс | Диспозиция | Аргумент | +|---|---|---| +| **dc1_time_units** (时辰) | **петля v1** | Спан с обеих сторон получается даром; ре-чек строгий (`ruNum == n && ruNum != 2n` перестаёт выполняться только при реально изменённом числе); правильный вывод требует русской морфологии числительных + согласования («три часа»→«шесть часов», «около трёх часов»→«около шести часов») — это ровно отвергнутый класс «код≥локаль» (pymorphy отвергнут паком-13), значит код не годится, а LLM годится. Объём: 时辰 встречается **266 раз на книгу** (прил. D). | +| **dc2_magnitude** (千万/数十万) | **петля v1, только с NO-CHANGE** | Ре-чек строгий (ok-суппрессор — регексп на верную величину). НО собственный заголовок чекера предупреждает: «千万 — это ещё и штатная ГИПЕРБОЛА, чья „тысячи“-передача в регистре» (`checkers.go:186-188`). Поэтому класс допускается в петлю ИСКЛЮЧИТЕЛЬНО с первоклассным ответом «без изменений»: решение «это гипербола» принимает модель, видя оба спана, а не наш регексп. | +| **percent_scale** (成) | **петля v1** | Истинное значение чекер уже вычисляет (66%), ре-чек строгий (появилась процентная форма). Гард однозначности обязателен: сегодня fraction-cue ищется по всему юниту. | +| **latin_residue** | **петля v1** | Оффсеты уже есть; ре-чек строгий (токен исчез И не появилось новых); вывод («again»→«снова») — перевод, кодом невычислим. | +| **broken_word** («войть», «-йть») | **петля v1** | Детекция zero-FP по построению (структурная сигнатура); восстановление задуманного слова требует словаря/морфологии — отвергнуто политикой; спан = слово+предложение. | +| dc6_register («терем») | **flag-only** | Ре-чек **тривиально удовлетворим**: любая замена лексемы обнуляет счётчик, включая замену на плохую. Ре-чек, который ремонт может «сыграть», — не гейт. Регистр — стилевое суждение, его место в промте (north-star), не в петле. | +| glossary_miss | **flag-only v1** | Ре-чек-то как раз хороший (появилась ли принятая dst-форма), но **локализовать нечего**: дефект — отсутствие; целевого спана не существует (`mempostcheck.go:61-77`). Кандидат v2, когда появится локатор (см. §12). | +| coverage/excision, length_collapse, number_drift, classify-классы | **flag-only** | Скалярные/пороговые по целому тексту; спана нет по природе; `length`/`empty` вообще ключуются на `finish_reason`, а не на тексте. | +| dialogue_dash, ё-консистентность, translit-interj | **flag-only** | Дефект чанк-глобальный (смешение стилей, пара «ё/е»), одиночный спан его не закрывает. Отдельно: дефис→тире соблазнительно чинить кодом, но это правка ЭКСПОРТНЫХ байт на no-repair пути → ломает инвариант байт-идентичности (§D4). Если владелец захочет — отдельным паком экспорт-контракта с санкцией на пере-капчер. | +| **(в) чистым кодом — УЖЕ В ПРОДЕ, в петлю НЕ тащить** | — | заголовок главы: чанкер сам срезает исходный и рендерит «Глава N» (`chunk.ApplyHeading`) — это ПРОФИЛАКТИКА, модель номера не касается; markdown-### и контентless CJK-утечка: `checks.StripCosmetic` + ре-чек; глиф-уровень: `recoverableFold`, `stripCombiningOnCyrillic` в `ExportNormalize`. | + +**Дискриминатор, который я принимаю (сформулирован адверсариальным агентом, и он точен):** дефект чинится чистым кодом тогда и только тогда, когда правильный вывод есть функция от самих дефектных БАЙТОВ (глиф/разметка), а не от смысла источника. Все три прод-прецедента лежат по эту сторону; все остальные классы пере-прогона — по ту. + +### Протокол NO-CHANGE (несущий, из прайор-арта) + +Наш же research зафиксировал APE-прецедент: на WMT19 en→ru **ни одна APE-система не побила «ничего не делать»** на сильном черновике, а «over-correction» — названная главная болезнь APE; LLM-редакторы правят и корректный вход. Поэтому: + +- Ответ модели «ничего менять не надо» — **первоклассный выход**, а не ошибка: движковый сентинел `⟦TM-NOCHANGE⟧` (идиома уже в проде — `⟦TM-BANK-v1⟧`, `banknote.go:28`; это ENGINE-токен, не пар-данные, поэтому в Go он законен). +- Всякий класс с известным риском ложного срабатывания (dc2 в первую очередь) допускается в петлю **только** под этим протоколом. +- Отказ модели чинить = сегодняшнее поведение (флаг в наблюдаемости), плюс счётчик `declined` — это и есть измеримая цифра «сколько наши флаги были ложными», побочный и очень полезный продукт петли. + +--- + +## D3. Механика вызова + +### D3.1. Где живёт: СУБ-ШАГ финальной стадии, НЕ новая стадия + +Инвентаризация role/stage/wave-поверхности (прил. A.2) даёт однозначный ответ. Новая стадия с ролью `repair`: + +- автоматически уезжает в edit-волну (разбиение бинарное: `role=="translator"` иначе EDIT — `waverun.go:41-50`, `snapshot.go:195-204`); +- **становится ФИНАЛЬНОЙ** → `isFinal` уезжает с `edit` (`stagerun.go:117`), санитайзер начинает судить выход ремонта, а не редактора; экспортная строка тоже переезжает (`export.go:173-177`); +- **двигает edit-волновой снапшот** (массив стадий входит в payload) → `--resnapshot` → пере-оплата edit-волны на КАЖДОЙ существующей книге, даже если ремонт выключен; +- ломает read-back существующих книг: `export` → все юниты `pending`, `quality` → `ProcessedUnits 0`, `status` → всё `in_progress`; +- и главное — **вешает книгу навсегда**: `status.go:307` считает `expected = members·|draftStages| + |editStages|` и требует `ok == expected` ТОЧНО; лишняя строка `chunk_status` = вечный `in_progress`. + +Суб-шаг внутри `runStage` финальной стадии не трогает ничего из этого: волна та же, `isFinal` там же, лишних строк `chunk_status`/`jobs` нет, снапшот при выключенном гейте байт-идентичен. Цена суб-шага (его надо решать явно, и я решаю ниже): резюм не бесплатен «сам собой», промпт не фолдится «сам собой», телеметрия конфлатится с редактором, модель не попадает в `reachableModels`. + +**Точка встраивания — `stagerun.go` между разрешением эскалации и записью `chunk_status`** (сейчас это строки 173-206). Там уже есть всё, что нужно, и ровно в нужной форме: +`ch.Text` — источник юнита (склейка чистых членов, `waverun.go:438`), `prev` — черновик юнита, `last.text` — финальный текст, `isFinal` — признак шиппинг-стадии, `job` — для чекпойнта. Условие входа: `isFinal && disposition == DispOK && gates.repair.enabled`. + +### D3.2. Промпт: конвенция `prompts/<пара>/repair/<класс>.md` + +Один файл на класс, в стандартном формате `System ---USER---` (`render.go:92-110`), резолвится тем же `promptConventionPath`-идиомом, что и роли (`pipeline.go:263,466`), fail-loud с названным путём при включённом гейте. Почему так, а не один файл с секциями классов: нулевой новый механизм (загрузчик, сплиттер, SHA-фолд — всё уже есть), каждый класс версионируется отдельным `PromptSHA256`, редактура — работа редакторской команды, как и задумано для `prompts/`. Плейсхолдеры — только существующие: `{{text}}` = исходный спан (пусто для target-only классов, и тогда шаблон класса его просто не упоминает), `{{draft}}` = целевой спан. **Новых полей `RenderVars` не требуется** → байт-нейтрально для всех существующих шаблонов. + +Пар-данность соблюдена: инструкция «时辰 = 2 часа» живёт в `prompts/zh-ru/repair/dc1_time_units.md` — это данные пары, как и уже вшитая строка `editor.md:10`. В Go — только идентификаторы классов и сентинел. + +### D3.3. Модель, потолки, бюджет + +Конфиг (строгий декод pipeline уже включён — `pipeline.go:331`, так что опечатка = громкая ошибка): + +```yaml +gates: + repair: + enabled: false # дефолт — как у coverage/sanitizer/banknote + model: deepseek-v4-flash + max_calls_per_unit: 2 + budget_usd: 0.50 # книго-широкий потолок класса + classes: [] # пусто = ратифицированный дефолт-набор +``` + +- **Модель — дешёвая (flash-класс).** Ценник (`configs/models.yaml:119`): вход $0.14/M, выход $0.28/M. Один ремонт ≈ 350 ток. промпта + ~100 ток. выхода ≈ **$0.0001**. При наблюдаемом объёме (прил. D) 500–1000 вызовов на книгу ≈ **$0.05–0.10** против COGS-цели $0.85 — приемлемо. Для контраста: LLM-судья как триггер стоил бы **$204–255 на книгу** (замер стенда) — поэтому **триггер обязан быть детерминированным**, и это не стилевое предпочтение, а арифметика. +- Валидация конфига при `enabled: true` (все — громкие ошибки на загрузке): модель существует в `models.yaml`; `budget_usd > 0` (иначе петля не может выстрелить — это не «тихий ноль» как у эскалации, а осознанная девиация: гейт, который не может ничего сделать, — запрещённый класс, ср. `pipeline.go:553-561`); `max_calls_per_unit > 0`; для каждого включённого класса существует файл промпта; **D13.6-гейт additive-reasoning применяется к repair-модели ровно как к `escalate_to`** (`pipeline.go:507-513`) — иначе grok-модель в этой роли ослепила бы потолок трат. +- **`reachableModels` расширяется repair-моделью** (`runner.go:293-307`) → её клиент строится в пре-компьют-пассе, `CheckKeys` требует её ключ, `rateGuard` работает. Без этого — громкая ошибка `no pre-built client` (`runner.go:335`). Это ровно та «третья модель-ось», о которой тривайр предупреждает в комментарии. +- **Эхо-мина:** repair-вызов НЕ передаёт `reasoning:"off"` (поле не заводится вовсе) → провайдерский дефолт → на DeepSeek thinking остаётся ON. Гейт `echoMineViolation` работает на уровне модели (`models.go:377`), поэтому конфигурация мины невозможна и здесь. Ответ прогоняется через `checks.StripThink` перед использованием (thinking-модель может протечь блоком). + +### D3.4. Протокол одного ремонта + +1. `cands := checks.RepairCandidates(ch.Text, last.text, cfg)`; фильтр по включённым классам; детерминированная сортировка (класс, затем начало спана); срез по `max_calls_per_unit`. +2. Спан расширяется до предложения (`lang.DefaultTerminators()`); рендерится промпт класса; `RequestHash{Role: "repair", Stage: st.Name, Model: repairModel, Attempt: <порядковый номер кандидата>, SnapshotID: <снапшот волны>, Messages: msgs}` — коллизия с основной осью невозможна (роль различает), одинаковые сообщения дают один хеш, то есть повтор бесплатен и это корректно. +3. `reserve → call → settle+checkpoint` тем же путём, что и все вызовы (переиспользование `runAttempt` с синтетической `config.Stage`, а не копия денежного пути — копия здесь была бы ровно тем классом дрейфа, против которого стоит `foldModelWire`). `max_tokens` считается от спана и проходит `applyModelFloor` для repair-модели. +4. **Гарды применения** (до всякого re-gate; нарушение = ремонт отброшен, деньги записаны, текст исходный): + - ответ ≠ пуст после `StripThink`+trim; `finish == stop`; + - ответ == `⟦TM-NOCHANGE⟧` → «declined», текст не трогаем (это НЕ провал); + - коридор длины: `len(new) ∈ [0.5×, 2×] len(old)` в рунах; + - перевод строк в замене не появляется, если их не было в спане (иначе ремонт переверстает абзац); + - множество CJK-рун замены ⊆ множества CJK-рун исходного спана (микро-эхо не проходит под 15%-порогом `classify`, поэтому нужен локальный гард); + - применение — сплайс байтового диапазона; кандидаты применяются в порядке УБЫВАНИЯ смещения, чтобы оффсеты остальных не сдвигались. +5. **RE-GATE поверх собранного текста** (все проверки — уже существующие чистые функции): + - класс, породивший кандидата, больше не порождает кандидата; + - `RunCheapGates(...).Total()` **не вырос** относительно до-ремонтного; + - `SanitizeOutput` (если гейт включён) не стал грязным, если был чистым; + - `classify(...)` остаётся `ok`; + - **`membank.Postcheck` не даёт НОВЫХ confirmed-промахов** (см. D3.6 — почему это несущая проверка, а не украшение). + Провал любой — **отбрасываем ВСЕ ремонты юнита** (атомарность: содержимое derived-чекпойнта детерминировано и не зависит от того, какие из ремонтов «частично прошли»); цена отката ≤ $0.0002. +6. Успех → derived-чекпойнт (§D4) → `final_hash` → `sr.Text` = отремонтированный текст, чтобы юнит-уровневые cheap-гейты (`waverun.go:477`) измеряли ПОСТ-ремонтное состояние. Диспозиция не меняется никогда. + +### D3.5. Loop-guard + +Раунд ровно ОДИН (петли как таковой нет — один проход): отремонтированный текст повторно на ремонт не подаётся, даже если появился новый кандидат (он попадёт в наблюдаемость и в следующий прогон). Плюс жёсткий `max_calls_per_unit`. Плюс общий книжный потолок $. Возможности «ремонт породил дефект → ремонт ремонта» не существует по построению. + +### D3.6. Три интеграционные развилки, которые нельзя оставить «на потом» + +**(а) Глоссарный ре-чек и минимальное расширение сигнатуры.** Ремонт подменяет спан прозы — а в спане может стоять каноническая форма термина. Пост-чек глоссария сегодня выполняется ПОСЛЕ возврата из стадии (`waverun.go:465-473`), то есть ремонт, сломавший канон, будет замечен, но уже как промах — а при включённом `gates.glossary.postcheck_gate` он бы ПЕРЕВЁЛ юнит в `flagged` с пустым экспортом, то есть ремонт своими руками уничтожил бы главу. (В шиппинг-конфигах гейт сейчас выключен — блок `glossary:` отсутствует в `pipeline-c1.yaml`/арм-ямлах, — но строить дизайн на «гейт всё равно выключен» нельзя.) +Поэтому `Postcheck` входит в re-gate, а для этого `runStage` получает **один дополнительный параметр** — `injected []membank.PickedEntry` юнита. Он симметричен уже существующей прокидке `injectionByRole` через `runStageSequence` и меняет ровно два колл-сайта (`runDraftChunk` передаёт `memSel.Injected`, `runEditUnit` — `editSel.Injected`). Альтернатива «перенести ремонт в `runEditUnit`, где выборка уже под рукой» отвергнута: там пришлось бы (1) вторично писать `chunk_status`, отобрав право на `final_hash` у того, кто его пишет, и (2) держать ВТОРУЮ копию логики для draft-only пути (`runDraftChunk`) — ровно тот класс «две независимые реализации одного правила», который пак-15 только что вылечил `memberDrops()`. + +**(б) Банкнота и владение `final_hash`.** На OK-пути `final_hash` уже может перенаправляться банкнотой (`stagerun.go:184-190`). Пересечение возможно только в draft-only пайплайне (банкнота — translator-only, `banknote.go:167`; ремонт — на финальной стадии), но оно возможно. Порядок ратифицирую явно: **банкнота срезается первой (её очистка уже отражена в `last.text`), ремонт работает поверх очищенного текста, и `final_hash` в итоге указывает на derived-чекпойнт РЕМОНТА.** Банкнотный derived-чекпойнт при этом остаётся записанным (он идемпотентен и контент-адресуем) — просто перестаёт быть финальным; это промежуточный артефакт, а не мусор. Никакого «кто последний, тот и прав» — порядок фиксирован кодом и покрывается тестом. + +**(в) Чекпойнт КАК ПАМЯТЬ о попытке ремонта.** Отдельного «журнала попыток» не нужно: отклонённый (`NO-CHANGE`) и отвергнутый гардами ремонт на следующем прогоне порождает БАЙТ-ИДЕНТИЧНЫЙ запрос (тот же спан, тот же промпт, тот же снапшот, тот же порядковый номер) → попадает в свой чекпойнт → **$0 и тот же исход**. Резюм не переплачивает за неудачные ремонты по тому же механизму, по которому не переплачивает за неудачные попытки стадии. + +### Расхождения с приором D3 + +- Приор: «1 раунд на чанк» — принято, но **уточняю единицу: юнит** (ремонтируется шиппинг-текст, а он в волновой модели per-unit), и добавляю потолок вызовов ВНУТРИ юнита. +- Приор: «re-gate тем же чекером + полной cheap-сюитой» — принято и **усилено**: не только «не хуже по cheap», но и санитайзер + `classify` + локальные гарды применения, потому что cheap-сюита по построению не видит вреда, который может внести подмена спана (эхо в спане, переверстка, обрезка). + +--- + +## D3a. Позиция в лестнице эскалации + +**Факт, меняющий постановку вопроса: множества не пересекаются.** + +- `regenerate_before_escalate` пере-атакует ТОЛЬКО ретраибельный поднабор `{length, empty}` (`disposition.go:130-132`, применяется `stagerun.go:140`). +- `escalate_to` стреляет только на эскалируемых флагах `{cjk_artifact, excision_suspect, loop_degenerate, hard/soft_refusal, content_filter}` (`disposition.go:142-149`) и на роли `editor` **запрещён конфигом** (`pipeline.go:528-531`). +- Ремонт срабатывает на состоянии, которого лестница не знает вообще: **стадия завершилась `ok`, но шиппинг-текст несёт типизированный дефект.** + +Следствия: +1. Формулировка приора «ремонт ПЕРВЫМ, дешевле полного re-gen» **вакуумна**: классы ремонта никогда не вызывали ни regenerate, ни эскалацию. Расхождение с приором фиксирую честно — и получаю инвариант СИЛЬНЕЕ запрошенного: **двойная оплата одного дефекта невозможна по построению**, а не по дисциплине. +2. Провал ремонта НЕ триггерит regenerate — согласен с приором, и опять же по построению: цикл попыток к этому моменту завершён, диспозиция `ok`, пере-атаковать нечего. +3. Семантика лестницы не меняется ни в одном байте: ни один существующий предикат (`retryable`/`escalatable`/`disposition`) не трогается. Ремонт — НЕ новая ступень лестницы, а новая ось: лестница отвечает «выход непригоден», ремонт — «выход пригоден, но в нём точечная ошибка». + +--- + +## D4. Wire / снапшот / resume / golden + +### Снапшот + +```go +type repairSnap struct { + Enabled bool `json:"enabled"` + Version string `json:"version"` // алгоритм: извлечение спана + гарды + re-gate + Model string `json:"model"` + ModelWire json.RawMessage `json:"model_wire,omitempty"` // foldModelWire — та же функция, что для primary/escalate + MaxCalls int `json:"max_calls_per_unit"` + Classes []string `json:"classes"` // отсортировано + PromptSHA256 map[string]string `json:"prompt_sha256"` // класс → SHA файла промпта (json.Marshal сортирует ключи) +} +func (r *Runner) repairSnapshot() *repairSnap // nil, когда гейт выключен +``` + +Три несущих свойства: +1. **Указатель + `omitempty`** (дисциплина `banknoteSnap`, `snapshot.go:104-130`), а НЕ форма `{"enabled":false}` как у coverage/sanitizer. Иначе добавление фичи пере-биллит каждую существующую книгу. Это прямое требование инварианта «no-repair путь байт-идентичен». +2. **Фолдится ТОЛЬКО в снапшот волны финальной стадии** (`finalStageWave()`, `snapshot.go:184-190`): в 2-стадийном пайплайне — только edit-волна. Иначе включение ремонта сдвинуло бы и драфт-волновой снапшот и **сломало $0-резюм драфта** — прямое нарушение инварианта «$0-резюм драфта НЕ ломать». (В draft-only пайплайне финальная волна — драфт, туда и фолдится: правило одно, ветвления по книге нет.) +3. **SHA промптов ремонта фолдится явно.** Суб-шаг не стадия, поэтому автоматики `PromptSHA256` (`snapshot.go:377`) на него не распространяется, а `msgsContentHash` (`render.go:284-309`) покрывает только сообщения хост-стадии. Без явного фолда правка `repair/dc1.md` тихо переиспользовала бы чекпойнты — класс D5.2. + +**Стоимость включения (честно):** enabling = `--resnapshot` = пере-оплата ТОЛЬКО edit-волны; драфт резюмится за $0. Это и есть «resnapshot ОДИН». + +### Resume + +- Repair-вызов — обычный оплаченный чекпойнт по своему `request_hash` → kill-9 между вызовом и записью теряет ≤1 вызов, повтор бесплатен (`ledger.go:127-171` идемпотентен). +- Отремонтированный ТЕКСТ — **derived-чекпойнт** по прецеденту `commitSanitizedExport` (`stagerun.go:252-263`) / `commitBanknoteExport` (`banknote.go:182-208`): id = `"tm-repair-v1:" + sha256("tm-repair-v1\x00" + + "\x00" + <отремонтированный текст>)`, контент-адресуемый, коллизия с hex-хешем реальной попытки невозможна, пишется ДО того, как на него сошлётся `chunk_status`. +- `chunk_status`: диспозиция `ok` (не меняется), `final_hash` → derived. **Ни одной новой строки** (`status.go:307` — `expected := len(u.Members)*len(draftStages) + len(editStages)`, проверено чтением, — не ломается). +- Резюм: быстрый путь `stagerun.go:78-82` отдаёт `resumeFromChunkStatus`, тот читает `final_hash` и возвращает отремонтированный текст. **Ремонт на резюме не повторяется и не оплачивается** — $0 по построению. Для НЕудавшихся ремонтов (declined/rejected — текст не менялся, кандидат остался) работает второй механизм, D3.6(в): повторный запрос байт-идентичен → попадает в собственный чекпойнт → $0 и тот же исход. +- Стоимость ремонта попадает в `chunk_status.cost_usd` юнита (честная сумма, как ретраи). + +### Golden + +- **Гейт выключен → `repairSnap == nil` → payload снапшота байт-в-байт прежний → все `request_hash` прежние → `capture.golden` байт-идентичен. Пере-капчер НЕ нужен.** Это проверяется исполнением на фазе 2 (`TM_UPDATE_GOLDEN=1` + `git status` пуст), как в паке-15. +- Путь ремонта пинится **ОТДЕЛЬНОЙ фикстурой** (свой `testdata/repair/` + свой мок-провайдер + свой capture-файл), а не расширением `capture.golden`. Так инвариант «no-repair без пере-капчера» и требование «новый класс вызовов должен быть запинен» выполняются одновременно, без санкции на пере-капчер. +- **Вопрос на ратификацию:** если оркестратор предпочитает единый golden с ячейкой ремонта — это санкционированный пере-капчер; я рекомендую отдельную фикстуру. + +--- + +## D5. Деньги и отчётность + +**Атрибуция денег НЕ требует миграции.** `checkpoints` и `request_log` уже несут колонку `role`; repair-вызовы пишутся с `role="repair"` → книжная сумма = `SELECT SUM(c.cost_usd) FROM checkpoints c JOIN jobs j ON c.job_id=j.id WHERE j.book_id=? AND c.role='repair'` — тот же идиом, что `EscalationSpentUSD` (`ledger.go:236-245`). Это и есть запрошенный CostSource-маркер; изобретать второй не нужно. **Категорически нельзя** помечать repair-чекпойнты `Escalation: true` — они тихо съели бы `escalation.budget_usd`. + +- Потолок класса: `repairBudgetRemains()` по образцу `escalationBudgetRemains()` — мягкий пред-вызовный кап; админ через **свой мьютекс** `repairMu` по образцу `escMu` (`runner.go:92`, `escalation.go:123`), иначе N параллельных edit-воркеров каждый прочитают «бюджет есть» и перебор составит до N−1 вызовов. С мьютексом перебор ≤ 1 вызова — как задокументировано у эскалации. +- Флаг `estimated` (v10) продолжает работать как есть: repair-вызов, севший на нулевой usage, пометится тем же механизмом. + +**Счётчики качества — аддитивная миграция v11** (класс v7/v9: `ALTER TABLE ... DEFAULT 0`, прежние строки и wire не двигаются): + +``` +retrieval_state: n_repair_candidates, n_repair_calls, n_repair_applied, n_repair_declined, n_repair_rejected INTEGER DEFAULT 0 + repair_detail TEXT DEFAULT '' -- JSON: [{class, outcome, reason}], детерминированный порядок +``` + +Сводки: +- `QualityReport` (`quality.go`): `repair_applied/declined/rejected` всего и **по классам** — это и есть north-star-метрика «измеренный вклад петли»; плюс `defect_residual` (кандидатов осталось после ремонта). +- `ChapterPassport` (`status.go:36`): `repair_applied`, `repair_failed` рядом с `StyleFlags` — оператор видит, что глава чинилась. +- `declined` (модель сказала «и так верно») — **прямой замер ложных срабатываний наших чекеров**, побочный продукт, которого у нас никогда не было. + +--- + +## D6. $0-валидация + +Три эшелона, все бесплатные, ни один не требует live-санкции: + +1. **Синтетические фикстуры в репо** (`internal/checks/testdata/repair/`): по образцу дефект-классов, БЕЗ цитат книги (тест-данные пары легитимны, §0.3 норматива; книжный текст — нет). На них пинятся: извлечение спанов, гард однозначности (2 матча → нет кандидата), все гарды применения (пустой ответ, NO-CHANGE, длина вне коридора, перевод строки, CJK в замене), атомарный откат, детерминизм (два прогона байт-в-байт). +2. **Мок-провайдер** (идиом `golden_test.go`: `httptest` + чистая функция от тела запроса) — сквозной прогон ремонта без сети и без денег, включая kill-9-резюм (второй прогон = $0, тот же derived-хеш). +3. **Стенд, RE-ONLY, $0.** Ремонт-СКАН как read-only проекция (расширение `QualityReport`/`export --pairs`): пере-считать `RepairCandidates` над сохранёнными выходами rerun2 и выдать «класс → сколько кандидатов → сколько под гардом однозначности». Гейт при этом НЕ включается → снапшот не двигается → `--resnapshot` не нужен → **ноль долларов**. Данные для этого уже лежат готовыми: `export-*.json` несёт per-unit `{source, final_text}` (прил. D). + +**Порядок, который я рекомендую (north-star «измеренный вклад»):** сначала $0-скан (эшелон 3) → он даёт остаточный объём по классам **после** уже вшитой профилактики (`editor.md:10` про меры появился ПОСЛЕ прогона rerun2, значит стендовые цифры измеряют ДО-профилактическое состояние — см. §11, линза 5) → и только потом решение владельца о платном включении петли на реальном прогоне. Mini-live гейт — отдельное решение владельца, не в паке. + +--- + +## 10. Сводка расхождений с приорами оркестратора + +| № | Приор | Моё решение | Аргумент | +|---|---|---|---| +| 1 | Дотипизация = аддитивные поля в Result-структурах | Отдельный тип + чистая функция `RepairCandidates` | Result-структуры сериализуются в стор (`style_detail`/`postcheck_detail`, у `PostcheckMiss` golden-байты не пусты) — новый механизм держим вне персистентной поверхности | +| 2 | В петлю: юнит-конверсия, число-масштабы, broken-word, latin-residue | Принято + **percent_scale**; dc2 — только под NO-CHANGE | Собственный заголовок чекера объявляет 千万 гиперболо-рискованным; APE-прецедент из нашего же research требует первоклассного «без изменений» | +| 3 | Ремонт ПЕРВЫМ в лестнице (дешевле re-gen) | Формулировка вакуумна: множества классов не пересекаются | `retryable`={length,empty}, `escalatable`={echo/excision/loop/refusal/filter}, ремонт — на `ok`-стадии. Инвариант выходит сильнее: двойная оплата невозможна по построению | +| 4 | Суб-шаг edit-стадии | Принято, **и обосновано измерением**, а не удобством | Новая стадия двигает `isFinal`, edit-снапшот, read-back существующих книг и вешает `status` на `expected`-арифметике | +| 5 | 1 раунд на чанк | 1 раунд на **юнит** + потолок вызовов внутри юнита + книжный $-потолок | Шиппинг-единица в волновой модели — юнит | +| 6 | re-gate = чекер + cheap-сюита | + санитайзер + `classify` + 5 локальных гардов применения | cheap-сюита слепа к вреду, который вносит именно подмена спана | +| 7 | (в) «чинится чистым кодом» — отсортировать | Отсортировано: всё, что можно, УЖЕ в проде; новых (в)-кандидатов, безопасных для инварианта байт-идентичности, нет | Дефис→тире и т.п. правят экспортные байты на no-repair пути → отдельный пак с санкцией | +| 8 | Эскалация: budget 0 = выключено (по образцу) | При `enabled:true` и `budget_usd<=0` — **громкая ошибка** | Гейт, который не может сработать, — запрещённый класс (`pipeline.go:553-561`); тихий ноль здесь дал бы «включил и не работает» | + +--- + +## 11. Адверсариальный селф-ревью дизайна (5 линз; 4 обязательные + 1 своя) + +**Линза 1 — детерминизм и resume.** +*Атака:* ремонт вносит недетерминизм в текст, который уже оплачен, и резюм расходится с живым прогоном. +*Ответ:* весь ремонт — чистая функция от (текст, кандидаты, ответы моделей), ответы моделей чекпойнтятся по `request_hash`, результат фиксируется derived-чекпойнтом, а резюм ВООБЩЕ не входит в код ремонта (быстрый путь `stagerun.go:78-82` отдаёт готовый текст). Порядок применения кандидатов детерминирован (сортировка + убывающие смещения). Итерация по map отсутствует. **Остаточный риск:** `json.Marshal` мапы `PromptSHA256` в снапшоте — ключи сортируются стандартной библиотекой (тот же приём уже используется для `extra_body`, `snapshot.go:237`), риск снят. +*Найденные дыры (все три исправлены в дизайне, а не оставлены на стройку):* +(1) первая версия фолдила `repairSnap` в общий `buildSnapshotID` → сдвинула бы драфт-волновой снапшот и убила $0-резюм драфта → фолд только в волну финальной стадии; +(2) не был определён владелец `final_hash` при совпадении с банкнотой (draft-only пайплайн) → порядок ратифицирован явно, D3.6(б); +(3) неудавшийся ремонт выглядел как «повтор на каждом резюме» → механизм памяти найден и он бесплатный: чекпойнт запроса, D3.6(в). + +**Линза 2 — деньги.** +*Атака:* новый класс вызовов течёт мимо учёта/потолков или удваивает оплату. +*Ответ:* вызов идёт тем же `reserve→call→settle+checkpoint` (не копия пути); книжный/дневной потолок работает автоматически; класс-потолок — отдельная сумма по `role='repair'`, НЕ по `escalation=1` (иначе съел бы премиум-бюджет); мьютекс держит перебор ≤1 вызова при параллельных воркерах; отброшенный ремонт всё равно записан как расход (честно). Порядок величин: $0.0001/вызов, ≤$0.10/книга против $0.85 COGS; LLM-триггер ($204–255/книга) исключён по арифметике, а не по вкусу. +*Остаточный риск:* резервация сайзится по `min_max_tokens` модели (для flash это 8000) → мгновенная резервация $0.0023 на вызов. При потолке книги это лишь временно «поджимает» ceiling; на settle возвращается фактическая цена. Отмечаю как известное свойство, не дефект. + +**Линза 3 — дисциплина D2.** +*Атака:* петля превращает наблюдаемость в гейт / протаскивает мусор в TM и экспорт. +*Ответ:* **ни один класс не меняет диспозицию** — ни в одну сторону. Ремонт работает исключительно на `disposition == ok`; провал ремонта = ровно сегодняшнее состояние (текст как был, флаг в наблюдаемости). Мусор течь не может: любая подмена спана проходит 5 локальных гардов + 4 ре-чека, и при любом сомнении откатывается целиком. +*Честная поправка к формулировке промта:* «при неудаче прежний flag+skip» точно описывает санитайзерные классы, но выбранные мной классы **сегодня не диспозиции вовсе** — у них нет `skip`. Правильная формулировка для v1: «при неудаче — прежняя наблюдаемость». Это расхождение терминологии, а не дисциплины: инвариант «мусор не течёт в TM/экспорт» соблюдён строже (мы вообще не меняем судьбу чанка). +*Дыра, которую я НЕ закрываю в v1 и называю явно:* самый дорогой сегодня дефект — санитайзерный СУБСТАНТИВНЫЙ (преамбула/хвостовой блок): юнит теряется целиком, экспорт пуст. Ремонт мог бы его спасти, но это меняло бы диспозицию `flagged→ok`, то есть трогало вердикт-ось. Дизайн v2 набросан в §12 — решает оркестратор. + +**Линза 4 — общность («заработает ли на паре, которой в репо ещё НЕТ, без правки Go?»).** +*Ответ:* да. Новая пара = (а) каталог `prompts/<пара>/repair/*.md`, (б) её `dc-checkers.txt` с паттернами классов, (в) при необходимости target-данные (`broken_suffix`). Go не трогается; отсутствие любого файла при включённом гейте — громкая ошибка с названным путём. В Go попадают только: идентификаторы классов (движковые), сентинел `⟦TM-NOCHANGE⟧` (движковый, идиома банкноты), алгоритм спанов/гардов/ре-чеков (общий). +*Единственная точка, где я НЕ добавляю общности и объясняю почему:* класс `latin_residue` наследует существующий таргет-гейт `isRuTarget` (`disposition.go:296`) — потому что на →en-таргете «латинское слово в выходе» флагает всё подряд. Это ратифицированный target-aware гейт слоя 7, а не новая утечка: петля **не заводит ни одного нового языкового предиката**, а наследует гейт того чекера, который породил кандидата. +*Остаточный риск:* расширение спана до предложения использует общую таблицу терминаторов — на паре без терминаторной разметки (напр. тайский без пробелов/точек) спан выродится в весь текст. Гард длины спана (доля от юнита) отсекает такой случай в «кандидата нет». Отмечено как известное ограничение, а не как скрытая поломка. + +**Линза 5 (моя) — «а не измеряем ли мы вчерашний день».** +*Атака:* классы петли выбраны по дефектам пере-прогона rerun2, но с тех пор часть дефектов закрыта ПРОФИЛАКТИКОЙ. +*Проверка исполнением (git, не память):* строка «МЕРЫ приводи к привычным читателю единицам (时辰 = 2 часа … 六成六 = 66%, а не „6,6“)» в `editor.md:10` пришла коммитом **`2f91b04` от 2026-07-24** (пак-13, «release QA»); отчёт пере-прогона `rerun2/RERUN_REPORT.md` датирован **2026-07-24 00:09**, а сами экспорты сделаны ДО него. То есть DC1 и percent_scale получили промпт-профилактику **после** того, как были сняты стендовые цифры: наблюдаемые «2 из 7 юнитов с неверным 时辰» измеряют ДО-профилактическое состояние. +*Следствие для дизайна (учтено в D6):* сначала $0-скан остаточного объёма, потом платное включение. Петля, построенная под дефект, который уже вылечен промтом, — это ровно та «вера вместо замера», которую промт запрещает. Дизайн от этого не меняется (механизм общий), но **порядок работ меняется**, и я это фиксирую как рекомендацию к фазе 2. + +--- + +## 12. Что НЕ в паке v1 (и почему) — кандидаты v2 с набросками + +1. **Ремонт СУБСТАНТИВНЫХ санитайзерных классов (преамбула/хвостовой edit-блок).** Сегодня они роняют юнит в пустой экспорт. Набросок безопасного механизма: **ремонт «только удаление»** — модель возвращает не новый текст, а границу удаляемого префикса/суффикса; Go проверяет детерминированно, что результат является суффиксом/префиксом исходного (то есть ремонт физически не может ничего дописать), и что санитайзер стал чист. Omission-safe по построению. Требует ратификации: меняет диспозицию. +2. **glossary_miss.** Ре-чек отличный, локатора нет. Нужен либо src→dst-локатор, либо приём «переспросить только предложение с сработавшим src-ключом». Прайор-арт (LinguaGacha, retranslation-by-keyword) — ровно про это; у нас уже есть src-позиции ключей в матчере. +3. **Детерминированные (в)-фиксы экспортного контракта** (дефис→тире и родня) — только отдельным паком с санкцией на пере-капчер golden. +4. **Дифф-формат вместо спан-замены.** Наш research фиксирует, что цифры search/replace (EM 0.95/0.94/0.68) получены на КОДЕ, русской прозы никто не мерил, а арм Q4b **никогда не гонялся** (`11-implementation-plan.md:882` — парковка). Поэтому v1 умышленно берёт самую узкую форму диффа — «один спан ↔ одна замена» с детерминированным Go-применением, без диалекта диффа как такового. Это осознанно СЛАБЕЕ, чем «diff-редактор» в названии пака: полноценный search/replace-протокол — гипотеза, требующая замера, а не дефолта. + +--- + +## 13. Вопросы к ратификации (решения не мои) + +1. **Класс-набор v1:** утверждаю ли 5 классов (dc1, dc2, percent, latin_residue, broken_word) — или dc2 исключить как гиперболо-рискованный даже под NO-CHANGE? **⚠ Отвечать по §15.1: замер показал, что на прод-арме эти пять классов дают 1 настоящий дефект и 1 ложное срабатывание (именно dc2-гипербола) на 7 юнитов.** +2. **Golden:** отдельная фикстура пути ремонта (моя рекомендация) — или санкция на пере-капчер `capture.golden` с ячейкой ремонта? +3. **Порядок работ фазы 2 — ГЛАВНЫЙ ВОПРОС ПАКА, теперь с цифрами (§15.1):** строим в паке-16 всё $0-е (позиционная детекция + скан-поверхность + два пар-данных бага-фикса `ru_hours_re`/`shichen_re`), а платный вызов кладём за `enabled: false` до замера остатка на пост-профилактическом прогоне — или строим петлю целиком и меряем на первом платном прогоне? +4. **Субстантивный санитайзерный ремонт (§12.1)** — в этот пак под «только удаление», или в следующий? +5. **v11-миграция счётчиков** (`retrieval_state`, аддитивно) — санкционирована? +6. **Расширение сигнатуры `runStage` одним параметром** (`injected []membank.PickedEntry`, D3.6(а)) ради полного re-gate — принимается? Отказ означает, что глоссарный ре-чек из петли выпадает и ремонт сможет тихо сломать канон термина. + +--- + +## Приложение A. Инвентаризация (сжато; полные `file:line` — в теле) + +**A.1 Позиции чекеров.** Уже материализуют позиции: санитайзер-преамбула (`sanitizer.go:247`), edit-мета (`:276`), CJK-утечка (`:617-634`), glossSpans `[][2]int` (`:554-570`), latin-residue (`checkers.go:290-303`), DC6 (`checkers.go:225-232`), translit-interj (`cheapgates.go:350-357`), число-токены (`regressionguard.go:97-128`). Позиция достижима без смены семантики: DC1/DC2/percent/万億 (замена `FindStringSubmatch`→`...Index`, `Contains`→`Index`). Позиция недостижима по природе: coverage, length-collapse, classify-пороги, glossary post-check. Особый случай: broken-word и ё идут через `text.TokenizeCyrillic`, который лоуэркейсит и теряет оффсеты (`runes.go:32-50`) — нужен отдельный оффсет-сохраняющий проход. + +**A.2 Role/stage/wave-поверхность.** Ветвления по роли: `waverun.go:41-50`, `snapshot.go:184-204`, `chunkrun.go:76,96-99`, `stagerun.go:106`, `banknote.go:167`, `pipeline.go:466-473,528-531`. По индексу: `stagerun.go:88-91,117`, `export.go:173-177`, `quality.go:210-213`. По количеству: **`status.go:307`** (`expected` — самый опасный), `quality.go:185-207`, `runner.go:293-326`, `models.go:488-492`, `snapshot.go:374-411`. + +**A.3 Чек-лист «новый класс оплаченных вызовов».** 14 пунктов; несущие: единственные два места `RequestHash` (`stagerun.go:296`, `escalation.go:105`) — третье обязано зеркалить набор полей; `JSONOnly` **хешируется, но не доезжает на wire** (`render.go:245+273` vs `stagerun.go:411-417`) — ловушка, которую наш дизайн обходит (JSON-режим не используем); опт-ин фичи фолдятся ТОЛЬКО указателем+omitempty; лишняя строка `chunk_status` вешает `status`. + +## Приложение B. Стенд-аудит (пред-проверка санкционированного строгого декода `book.yaml`) + +**Вердикт: внесхемных ключей НЕТ ни в одном book-образном yaml.** Проверено 13 файлов на стенде (`acceptance/book.yaml`, `rerun/book.yaml`, `rerun2/{book,book-dspro,book-mistral}.yaml`, 8 якорей `exp15/anchors/*.yaml`) + 2 в репо (`example/book.yaml`, `testdata/golden/book.yaml`) + `configs/models.yaml` + 3 пар-конфига + инлайн-литералы book.yaml в 9 Go-тестах. **Флип `KnownFields(true)` на `LoadBook` — нулевая миграция для всего, что существует сегодня.** Строгость сегодня: только `LoadPipeline` (`pipeline.go:331-332`); `LoadBook` (`book.go:103`), `LoadModels` (`models.go:176`), `LoadPair` (`pair.go:69`) — обычный `yaml.Unmarshal`. + +**Побочная находка (хвост владельцу, НЕ бэкенду):** легаси-конфиги стенда `acceptance/pipeline-acceptance.yaml` и `rerun/pipeline-rerun.yaml` **уже сегодня не проходят** строгий `LoadPipeline` — несут снятые `context.stm_depth`/`overlap_tokens` и retired `prompt:`. Трио `rerun2/pipeline-*.yaml` чистое (мигрировано на `prompt_override`). То есть повторный прогон СТАРЫХ стендовых конфигов упадёт громко на загрузке — это ожидаемое поведение пака-15, но владельцу полезно знать заранее. + +## Приложение C. Прайор-арт (идеи; ни строки чужого кода) + +- **AiNiee ProofreadTask** (AGPL-3.0): триггер — флаг чекера, НЕСУЩИЙ ТИП (`error_type`); действие — однострочный proofread (`ProofreadTask.py:250` по research/22, спот-чек оркестратора подтверждал цитату). Что именно получает модель, политика ретраев и наличие ре-чека — **в нашей документации НЕ зафиксировано (UNVERIFIED)**, поэтому эти детали я НЕ переносил в дизайн. +- **LinguaGacha** retranslation-by-keyword: после правки глоссария пере-переводятся ТОЛЬКО строки с термином. Мотив идентичен нашему; наш аналог — кандидат v2 (§12.2). Оговорка: их 3-классовый чекер описан у нас по deepwiki (AI-ген), доказательная сила слабая; сильный, код-верифицированный аналог — GalTransl (8 категорий + per-category redrive). +- **Эмпирика диффов** — вся из КОДА: Diff-XYZ generation EM 0.95/0.94/0.68 (search-replace) vs 0.77/0.82/0.23 (unified); aider format-compliance 92.9–98.2% у среднего тира ⇒ 2–9% ответов малформед — у нас это закрыто гардами применения + откатом. Переносимость на русскую прозу нашим research прямо названа НЕизмеренной. +- **Предупреждения, учтённые в дизайне:** APE над сильным черновиком не бьёт «ничего не делать» (WMT19 en→ru) ⇒ NO-CHANGE как первоклассный выход + замер против do-nothing; «negative optimization» у явных critique-артефактов; чекеры GalTransl на пунктуацию/паритет строк ВОЮЮТ с нашим reflow (`22:118`, `22:181`) ⇒ **вывод «строковые якоря запрещены» — мой, из этого конфликта, а не цитата из research/22**; неоднозначный якорь обязан отвергать, а не патчить первый матч. +- **Дисциплина цитирования:** отчёт прошёл собственную проверку на ОВЕР-АТРИБУЦИЮ (известный класс провала при лендинге research/N). Одна нашлась и исправлена — см. D1.1: формулировка про строчные якоря была подана как цитата research/22, а является моим выводом из зафиксированного там конфликта. + +## Приложение D. Объём и деньги (живые выходы rerun2, read-only, книга вне git) + +Срез: 5 глав = 7 юнитов, источник 14 545 zh-символов, экспорты 42–46k ru-символов на арм. + +| Сигнал | dspro | glm | mistral | +|---|---|---|---| +| Хард-флаги (все — `sanitizer_stripped`, CJK-утечка) | **3/7 юнитов (42.9%)** | 0 | 0 | +| Глоссарные промахи (судья) | 1 | 3 | 5 | +| «час»-формы в выходе / из них НЕВЕРНЫХ (时辰≠час) | 3 / **2** | 2 / **2** | 3 / **1** | +| latin-residue / «-йть» в теле экспорта | 0 / 0 | 1 / 1 | 0 / 0 | +| markdown-заголовки / CJK в теле | 0 / 0 | 0 / 0 | 0 / 0 | + +Ground truth среза: 时辰 ×3, 千万 ×1, 数十万 ×1. + +**Книжный масштаб — ПЕРЕ-МЕРЕН МНОЙ ИСПОЛНЕНИЕМ** (агент-инвентаризатор дал близкие, но не совпавшие числа; беру свои): +`guzhenren-utf8.txt` = **7 778 990 символов** (не 7 994 664); глав-节 = **2 106 на начале строки / 2 131 вхождений** (не 2 283; `第N章` в книге всего 29 — книга размечена 节, что и отражает `cjk-section.txt`). При наблюдаемых ~2 078 исходных символах на юнит это **≈3 700–3 900 юнитов** (диапазон агента 3 200–4 000 включает истину, но шире). Частоты сигнатур классов, пере-считанные grep'ом по всей книге: **时辰 ×266** (совпало с агентом), **千万 ×234**, **数十万 ×62**, `[цифра]成` ×708. + +Вывод от этой поправки не меняется: объём ремонт-кандидатов — сотни, не десятки тысяч; порядок стоимости петли сохраняется. + +**Три вывода, влияющие на решения:** +1. **Доминирующий «дефект» книги уже чинится кодом:** CJK-утечка = 42.9% юнитов у dspro, и `StripCosmetic` её снимает автоматически. Проблема не в починке, а в том, что ~1 400–1 700 юнитов на книгу останутся ПОМЕЧЕННЫМИ для человека — при таком объёме флаг перестаёт быть сигналом. Это находка о ПОЛИТИКЕ флагов, не о ремонте; пингую оркестратору отдельно (§14). +2. **LLM-триггер экономически невозможен:** судейский скрин $0.0637/юнит → $204–255/книга при цели COGS $0.85. Триггер обязан быть детерминированным. +3. **Данные для $0-скана готовы:** `export-*.json` = `{book_id, total_units, …, chunks[{chapter, chunk_idx, disposition, final_text, source}]}` — per-unit источник И финал, пере-чанкинг не нужен. + +## 13-bis. Хвосты первого касания (байт-нейтральные, тем же паком) — диспозиции + +Промт называет четыре; фиксирую, что с каждым будет, чтобы ни один не «растворился»: + +| Хвост | Диспозиция | Что уже проверено сейчас | +|---|---|---| +| Строгий декод `book.yaml` (САНКЦИОНИРОВАН) | делаем; риска нет | Пред-проверка выполнена: **внесхемных ключей нет ни в одном стендовом/репозиторном `book.yaml`** (прил. B) → флип `KnownFields(true)` в `LoadBook` (`book.go:103`) — нулевая миграция | +| Строгий декод пар/models-слоёв | делаем тем же приёмом | `LoadModels` (`models.go:176`) и `LoadPair` (`pair.go:69`) сегодня не строгие; внесхемных ключей в `configs/models.yaml` и в трёх `configs/pairs/*.yaml` нет; на стенде своих `models.yaml`/`pairs/` вообще НЕТ (все книги смотрят в репо) | +| Unknown-key-тест пинит ИМЯ ключа | делаем | Сегодняшний `TestUnknownConfigKeyIsRejected` проверяет факт отказа, но не то, что сообщение называет опечатанный ключ — ужесточаем ассерт | +| Zero-value-паники `PickedEntry`/`Bank` | гард ИЛИ документирование — решаю на стройке по факту чтения обоих типов | Не диктую заранее: выбор между гардом и package-doc зависит от того, достижимо ли нулевое значение с живого колл-сайта; отчёт фазы 2 назовёт выбор и причину | + +Все четыре — байт-нейтральные (только error-пути, доки и тесты), то есть не трогают ни wire, ни снапшот, ни golden. + +## 14. Пинги оркестратору (вне скоупа пака, замечено попутно) + +1. **Политика флагов при книжном масштабе** (прил. D, вывод 1): косметический авто-стрип помечает ~40% юнитов; на 3 000+ юнитов это шум, а не сигнал. Стоит решить: остаётся ли `sanitizer_stripped` флагом для человека, или становится чистой телеметрией. +2. **Док-дрейф:** `12-go-style-notes.md` §0.4 всё ещё описывает retired-форму `prompts: {<пара>: файл}`, которую пак-15 заменил конвенцией `prompts/<пара>/<роль>.md` (D39.23). Норматив надо поправить, иначе следующая сессия сошлётся на снятый механизм. +3. **Легаси-конфиги стенда** (прил. B) упадут на строгом декоде — ожидаемо, но лучше знать до прогона. + +--- + +**Статус: ФАЗА 1 ЗАВЕРШЕНА. Кода не тронуто (`git status` по `backend/` чист). Жду ратификации перед фазой 2.** + +--- + +# §15. РЕВИЗИЯ 2 — результаты адверсариальной панели и вытекающие поправки + +**Что прогнано.** Дизайн (в виде §0–§14) отдан 6 независимым атакующим линзам — детерминизм/резюм · деньги · +дисциплина D2/вердикт-ось · общность движка · доказательная база («стоит ли вообще строить») · критик полноты +против текста промта. Каждое возражение уровня BLOCKER/MAJOR затем передано ОТДЕЛЬНОМУ скептику с установкой +«по умолчанию возражение неверно — опровергни его по коду». Из поднятого **выжило 4 MAJOR** (13 верификаций). +Сверх панели я **воспроизвёл исполнением сам** пять фактов — их и держу как несущие, потому что агентам верить +на слово нельзя (правило проекта), а тут расхождения были у обеих сторон. + +## 15.1. ГЛАВНОЕ: замер сменил рекомендуемый скоуп пака + +Панель заметила, что вход для «$0-скана» (эшелон 3 моего же D6) лежал готовым, и прогнала его. Я **воспроизвёл +скан независимо** (порт пяти детекций строго по `dc-checkers.txt` + алгоритму `checkers.go`, над +`rerun2/export-*.json`, где на юнит лежат И источник, И финал): + +| Арм | Юнитов | dc1 | dc2 | percent | latin | broken | ИТОГО кандидатов | +|---|---|---|---|---|---|---|---| +| **deepseek-v4-pro (РАТИФИЦИРОВАННЫЙ прод-редактор, D39.22)** | 7 | 1 | 1 | 0 | 0 | 0 | **2** | +| glm-5 (резервный арм) | 7 | 1 | 0 | 1 | 1 | 1 | 4 | +| mistral (ИСКЛЮЧЁН, D39.20) | 7 | 1 | 2 | 1 | 0 | 0 | 4 | + +**И один из двух кандидатов прод-арма — ЛОЖНОЕ СРАБАТЫВАНИЕ.** Проверено глазами по тексту: +источник `杀了千万人的性命`, перевод «загубил тысячи и тысячи жизней» — это штатная ГИПЕРБОЛА, ровно тот случай, +о котором предупреждает заголовок самого чекера (`checkers.go:186-188`). То есть на единственном арме, который +имеет значение, пять предложенных классов дают **1 настоящий дефект и 1 ложный на 7 юнитов**. + +Три дополнительных факта, каждый пере-мерен мной: + +1. **Флагманский класс слабее, чем я написал.** 时辰 встречается 266 раз на книгу, но `shichen_re` требует + цифру/числительное перед 时辰, а **самая частая форма — 半个时辰, 84 из 266 (32%) — не матчится вообще**; + всего срабатываний паттерна 120, а не 266. Мой §D3.3/прил. D завышали детектируемую поверхность в 2.2 раза. +2. **Профилактика уже стоит НА ОБЕИХ стадиях и никогда не мерилась.** Не только `editor.md:10`, но и + `translator.md:13` — и там прямо назван пропущенный случай: «时辰 = 2 часа (三个时辰 ≈ шесть часов, + **半个时辰 ≈ час**)». Обе строки пришли коммитом `2f91b04` (2026-07-24 03:19), а отчёт пере-прогона датирован + 2026-07-24 00:09 — то есть профилактика легла **через ~3 часа ПОСЛЕ** снятия цифр, на которых я строил выбор классов. +3. **У DC1 живое ЛОЖНОЕ СРАБАТЫВАНИЕ в пар-данных.** `ru_hours_re = (…|три|…)\s+час` не имеет правой границы + слова, поэтому **«три части» матчится как «три час»** (проверено исполнением на 5 пробах). Сегодня это + безвредно (наблюдаемость), но АКТУАТОР на этом паттерне перепишет «три части» → «шесть часов» — и такая + правка ПРОЙДЁТ ре-гейт. + +**Вывод, который я обязан доложить, даже приняв «фаза 1 принята в текущем виде»:** строить платную LLM-петлю +под эти цифры сейчас — это вера, а не замер, то есть ровно то, что north-star промта запрещает. Механизм +спроектирован полностью и ратифицируем как есть; но **порядок работ рекомендую сменить**: + +> **Рекомендуемый скоуп пака-16 (ревизия): всё, что стоит $0, — строим; платный вызов — за выключенным гейтом +> до замера остатка.** А именно: (1) `checks.RepairCandidates` (позиционная детекция, вердикт-нейтральная); +> (2) read-only скан-поверхность (измеряет остаток по классам за $0 на любой уже прогнанной книге); +> (3) **два пар-данных бага-фикса**: правая граница в `ru_hours_re` и слепота `shichen_re` к 半个时辰 — +> оба $0, оба чинят СЕГОДНЯШНЮЮ наблюдаемость, оба обязательны ДО того, как класс станет актуатором; +> (4) машинерия ремонт-вызова по §D3–D5 с поправками §15.2 — под `enabled: false`, включение отдельным +> решением владельца после того, как скан на пост-профилактическом прогоне покажет остаточный объём. + +Это НЕ отказ от петли и не сужение задачи по своей инициативе — это перестановка платного шага за замер, +и решение остаётся за оркестратором (вопрос §13.3 теперь имеет цифры). + +## 15.2. Выжившие возражения и поправки к дизайну + +**(A) MAJOR — ре-чек всех пяти классов удовлетворяется УДАЛЕНИЕМ.** Проверено мной по коду: удали спан — +`lintTimeUnits` не найдёт `ru_hours_re` (`checkers.go:140-142`), `lintMagnitudeScale` не найдёт fire-word +(`:199-200`), `lintPercentScale` не найдёт fraction-cue (`:266`), `lintLatinResidue` не найдёт токен (`:305`), +`lintBrokenWord` не найдёт слово (`:353`) — все вернут 0. Это ровно тот дисквалификатор, по которому я сам +отверг dc6. Мой коридор длины 0.5–2× не спасает: удаление короткого токена из предложения его не нарушает. +**Поправка — ре-гейт становится ПОЗИТИВНЫМ, а не «отсутствие флага»:** +- dc1: в замене ОБЯЗАН найтись hours-оборот, и его число обязано равняться `2n` (утверждение, а не молчание); +- dc2: ok-суппрессор обязан СМАТЧИТЬСЯ (`qianwan_ok_re`/`shushiwan_ok_re`), а не просто «fire-word исчез»; +- percent: в замене обязана быть процентная форма со значением, равным вычисленному `pct`; +- latin/broken: дефектный токен исчез **И** число словных токенов замены ≥ (исходное − 1) — анти-удаление; +- глобально: каждое АРАБСКОЕ число исходного спана обязано присутствовать в замене, кроме числа-цели класса. + +**(B) MAJOR — счётчики в `retrieval_state` обнулялись бы на каждом резюме.** `persistRetrievalState` +собирает строку с нуля (`chunkrun.go:106-112`), `UpsertRetrievalState` перезаписывает ВСЕ перечисленные +колонки (`glossary.go:256-274`), драфт-волна выполняется ДО edit-волны и зовёт запись безусловно +(`waverun.go:371-375`) — а ремонт на резюме не повторяется и переписать счётчики некому. +**Поправка — миграция v11 ОТМЕНЯЕТСЯ, счётчики выводятся из durable-артефактов, которые уже есть:** +`calls` = число чекпойнтов с `role='repair'` для юнита; `declined` = те, чей ответ равен сентинелу; +`applied` = `final_hash` несёт префикс `tm-repair-v1:`; `rejected` = остальные. Это self-healing на резюме +и ровно доктрина репозитория («recomputed deterministically each run»). Ноль новых колонок, ноль миграции. + +**(C) MAJOR — перекрывающиеся спаны.** Гард однозначности — ПО КЛАССУ, поэтому два кандидата разных классов +в одном (или соседнем) предложении дают перекрытие всегда, а не в углу; сплайс по убыванию смещения тогда +режет уже изменённую строку → в худшем случае паника по границам среза или разрез посреди руны — **в оплаченном +прогоне**. **Поправка:** после расширения до предложения кандидаты проверяются на пересечение; при пересечении +остаётся первый в детерминированном порядке, остальные ОТБРАСЫВАЮТСЯ (не чинятся). Перед сплайсом — +явное утверждение непересечения. + +**(D) MAJOR — отказ резервации по потолку убил бы книгу.** `runAttempt` возвращает ошибку с +`errReserveCeiling` (`stagerun.go:373-376`); во всём репозитории её ловит РОВНО одно место — `escalation.go:137`, +и его доктрина дословно: опциональный хоп деградирует, а не роняет книгу. Ремонт опциональнее того хопа, но +стоит ДО записи `chunk_status`, поэтому проброс ошибки потерял бы строку уже оплаченной успешной редактуры, +и так на каждом резюме. **Поправка:** ремонт ловит `errReserveCeiling` и деградирует в «ремонта не было»; +любая другая инфра-ошибка по-прежнему падает громко. + +**(E) MAJOR-половина — `classify` над ответом ремонта НУЖЕН, а не вреден.** Скептик развернул возражение: +модель, ответившая «Не могу помочь…», проходит ВСЕ пять моих гардов (непусто, `finish=stop`, коридор длины, +без переводов строк, CJK-подмножество) и была бы вклеена в прозу; `classify` ловит это как `soft_refusal` +(`disposition.go:247-249` + `lang/data/refusal.txt`). **Поправка:** ответ ремонта прогоняется через `classify` +(и, если гейт включён, через санитайзер) как шестой гард. + +**(F) Уточнения, принятые без спора:** +- **Хеш ремонта — ПОЛНАЯ позиционная ось** (`BookID/Chapter/ChunkIdx/Attempt/Stage/Role/Model/SnapshotID/Messages`), + как в `runAttempt`; формулировку «одинаковые сообщения → один хеш → бесплатный дедуп» из D3.4 снимаю: это + не фича, а следствие, и подавать её как фичу опасно. +- **Бюджет обходится чекпойнтом:** уже оплаченный ремонт реплеится независимо от остатка бюджета + (`escalation.go:99-114` — иначе резюм после исчерпания бюджета отдал бы ДРУГИЕ байты). +- **`buildSnapshotID` не знает волны** (`snapshot.go:219`) — фолд «только в волну финальной стадии» требует + явного третьего параметра, который передаёт `snapshotIDForWave`. Механизм назван, а не подразумевается. +- **`style_check_version` — обязательство доказательством.** Позиционная детекция не смеет двигать вердикт + ни одного существующего чекера; `CheapGateVersion` НЕ бампится, и это доказывается исполнением + (golden байт-идентичен + счётчики на фикстурном корпусе не сдвинулись). Если хоть один счётчик поехал — + это громкий бамп и пере-капчер, то есть провал дизайна, а не «мелочь». +- **`budget_usd` в снапшот НЕ фолдится** (скептик показал: эскалационный бюджет тоже не фолдится, а влияет на + результат сильнее; фолд доллара пере-биллил бы книгу при правке потолка). +- **Мягкий кап зависит от планировщика** — да, но это ратифицированное свойство уже шиппящегося кап-механизма + (`escalation.go:48-55` + мьютекс, пиненный `TestWaveEscalationBudgetSerializedUnderParallelism`), а не новый + класс. Формулировку линзы 1 «весь ремонт — чистая функция» смягчаю: чист сам ремонт; ПОРЯДОК допуска под + исчерпанным бюджетом — нет, и это наследуемое, задокументированное свойство. +- **Деньги: thinking-токены.** DeepSeek считает thinking внутри `completion_tokens` (subset), а он обязан + быть ВКЛЮЧЁН — значит выход ремонта больше «ста токенов». Честная вилка: **$0.0001–0.0006 за вызов**, + то есть **$0.05–0.30 на книгу** при сотнях вызовов (было «$0.05–0.10» — занижено). +- **D13.6 для repair-модели** невозможно применить к блоку без ключа: либо `gates.repair.reasoning_max_tokens`, + либо запрет additive-провайдера в этой роли. Выбираю **запрет** (least mechanism): repair — дешёвая роль, + ей нечего делать на xAI; попытка = громкая ошибка на загрузке. +- **Формулировка про диспозиции уточнена.** «Выбранные классы сегодня не диспозиции» верно для CHEAP-гейтов, + но у санитайзера есть СУБСТАНТИВНЫЕ однофамильцы (`LatinInsert`/`BrokenWord`, `sanitizer.go:146-147,161-162`), + и он включён во всех шиппинг-конфигах. Противоречия в механизме нет (ремонт входит только на `DispOK`, + то есть видит лишь текст, который санитайзер пропустил), но фраза была неточной. +- **Общность — честная граница.** «Новая пара = только данные» верно для ДОБАВЛЕНИЯ пары. Но три вещи + привязаны к target=ru в Go **до этого пака и независимо от него**: гейт `isRuTarget` живёт на колл-сайтах + (`waverun.go:476`, `chunkrun.go:51`), `text.TokenizeCyrillic` кириллице-специфичен (`runes.go:41`), + и множитель «时辰 = ×2» захардкожен (`checkers.go:148`, при том что `dc-checkers.txt` объявляет алгоритм + generic). Ремонт этого не добавляет, но делает АКТУАТОРОМ — поэтому фиксирую как унаследованный долг + и как ограничение: класс работает на новой ПАРЕ, но не на новом ТАРГЕТЕ без правки Go. +- **Fail-loud промпта — только для применимых классов:** файл `repair/<класс>.md` требуется, если класс + включён **И** у пары есть данные этого класса; иначе пара была бы обязана заводить промты под чужие дефекты. + +## 15.3. Дополнения к таблице расхождений §10 (панель нашла пропуски) + +| № | Приор/инвариант промта | Что я делаю | Почему это надо было объявить | +|---|---|---|---| +| 9 | Конвенция промптов `prompts/<пара>/<роль>.md` (ратифицирована D39.23) | `prompts/<пара>/repair/<класс>.md` | Ремонт — не роль стадии; ключ — класс дефекта. Это ОТКЛОНЕНИЕ от ратифицированной конвенции, и в §10 его не было | +| 10 | D3: «src-спан + draft-спан + **типизированная ошибка** → исправление» | Тип ошибки доезжает до модели как ВЫБОР ФАЙЛА промпта, а не как интерполированная строка | Диагностика чекеров англо-первичная (`checkers.go:150` и др.); вставлять её в русский промпт — языковая утечка. Но это именно отклонение от буквы D3, а не её исполнение | +| 11 | «no-repair путь байт-идентичен» | Доказывается не только `repairSnap == nil`, но и обязательством не двигать `CheapGateVersion` | Позиционная детекция трогает `checkers.go`, который версионируется и фолдится в ОБЕ волны | + +## 15.4. Что панель НЕ смогла сломать (проверено скептиками, держится) + +Форма петли: суб-шаг вместо стадии · derived-чекпойнт с namespace-id · отсутствие новых строк `chunk_status` · +фолд указателем+omitempty · атрибуция денег через `role='repair'` без миграции · вывод D3a о непересечении +множеств классов (двойная оплата невозможна по построению) · отказ от фолда `budget_usd` · мьютекс-кап по +образцу эскалации. Ни одно из этих решений не пало. + +## 15.5. Итог фазы 1 + +Дизайн ратифицируем — с поправками §15.2 (они внутри дизайна, переархитектуры не требуют) и с **открытым +вопросом о скоупе §15.1**, который теперь подкреплён цифрами, а не мнением. Кода по-прежнему не тронуто.