Ratify D39.24 pack-16 diff-editor design with the reorder: build zero-cost parts, paid repair machinery behind enabled false pending residual measurement
This commit is contained in:
parent
4195ef190a
commit
811f561dee
3 changed files with 582 additions and 3 deletions
|
|
@ -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
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
571
docs/archive/reports/PACK16_DESIGN_2026-07-25.md
Normal file
571
docs/archive/reports/PACK16_DESIGN_2026-07-25.md
Normal file
|
|
@ -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" + <reqHash терминальной попытки> + "\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**, который теперь подкреплён цифрами, а не мнением. Кода по-прежнему не тронуто.
|
||||
Loading…
Add table
Reference in a new issue