Ratify pack-17 channel-B design as D39.26 in its §14 revision, add three acceptance findings, mark phase 2 exactly, land design report with review header
This commit is contained in:
parent
27ab567298
commit
0e88f83b17
7 changed files with 432 additions and 16 deletions
|
|
@ -14,7 +14,7 @@ Go-бэкенд издательского художественного пер
|
|||
|
||||
## Источники истины (по убыванию)
|
||||
|
||||
1. **`docs/architecture/05-decisions-log.md` (D1–D39.25)** — ратифицированный контракт; при конфликте с любым другим доком побеждает он; сверху файла — карта актуальности (что чем superseded). **Правило чтения (онбординг-диета 25.07): карта + живая голова (эра D39.x); корпус D1–D38 = справочник — по ссылкам/grep по D-номеру, целиком НЕ читать.**
|
||||
1. **`docs/architecture/05-decisions-log.md` (D1–D39.26)** — ратифицированный контракт; при конфликте с любым другим доком побеждает он; сверху файла — карта актуальности (что чем superseded). **Правило чтения (онбординг-диета 25.07): карта + живая голова (эра D39.x); корпус D1–D38 = справочник — по ссылкам/grep по D-номеру, целиком НЕ читать.**
|
||||
2. `docs/experiments/00-provider-quirks.md` — wire-квирки провайдеров (читать ПЕРЕД правкой адаптеров/вызовами провайдеров).
|
||||
3. `docs/architecture/09-target-architecture.md` (7 слоёв; статус стройки — шапка-таблица) · `12-go-style-notes.md` (норматив общности §0) · `10-prompt-architecture.md` (промпт-тема).
|
||||
4. `docs/architecture/01-decisions.md` (Р1–Р10), `02-mvp-plan.md`, `04-unhappy-paths.md`, `06-memory-risk-registry.md` — тела исторические, читать через ⚠-баннеры 25.07.
|
||||
|
|
@ -39,11 +39,11 @@ Go-бэкенд издательского художественного пер
|
|||
2. `docs/architecture/05-decisions-log.md`: карта актуальности (шапка) + живая голова (D39.x). Целиком НЕ читать — корпус D1–D38 по ссылкам/grep по мере надобности.
|
||||
3. По роли: Бэкенд — `backend/README.md` + `03-implementation-notes.md` (через баннер) + `12-go-style-notes.md`; Полигон — `eval/README.md` + `experiments/00` + `09-pilot-protocol.md`; всем — шапка-таблица `09-target-architecture.md` (статус слоёв).
|
||||
|
||||
## Текущее состояние (2026-07-25, пост-пак-16/D39.25 — эра «механизация выпускного качества»)
|
||||
## Текущее состояние (2026-07-25, пост-пак-16 / дизайн пака-17 ратифицирован D39.26 — эра «механизация выпускного качества»)
|
||||
|
||||
- **Фазы:** Ф0 ✅ · Ф1-инфра ✅ (D20–D28; golden = инвариант детерминизма №8) · арка «качество-первым» D26→D38.5 закрыта · **арх-ресет D39 исполнен ЦЕЛИКОМ**: 7 слоёв построены паками 11–16 (D39.13–D39.24; статус слоёв — шапка-таблица `09-target-architecture.md`; слой-3 diff-редактор — за `enabled:false`, включение = решение владельца по замеру остатка).
|
||||
- **Пере-прогон rerun2 прочитан (D39.20):** операционка живьём ($0-резюм ×3 арма, $6.63<$15, echo 0, майнинг-стоп→подпись); планка ≤2 НЕ пройдена; анти-корреляция гладкость↔верность ×3 → mistral-редактор вон. **Эксп ЗАКРЫТ (D39.22), итерация №2 отложена; редактор = deepseek-v4-pro ИНТЕРИМ** (glm-5 резерв). Стайл-каноны D39.21 (时辰=2ч · generic-«гу» ср. род · стихи смыслом · 资质=«талант»).
|
||||
- **Курс — разработка бэкенда:** **пак-17 «канал B вживую» ЗАПУЩЕН 25.07** (двухфазный; **D39.25: в движке НЕТ понятия «18+»** — generic content-labels × provider-capabilities, refusal причино-агностичен) → следующий платный прогон (разом: итерация №2 dspro-vs-glm · замер остатка ремонта · живой канал B) → сид-дельта (元 · 赤城 · 学堂家老 · 春秋蝉) → Ф2-механизмы (голос/состояние D21 · native-Gemini судья) · ja→ru §B5 → пилот Ф2.5.
|
||||
- **Курс — разработка бэкенда:** **пак-17 «канал B вживую»: фаза 1 (дизайн) ПРИНЯТА в редакции §14 — D39.26, фаза 2 (стройка) размечена и открыта** (**D39.25: в движке НЕТ понятия «18+»** — generic content-labels × provider-capabilities, refusal причино-агностичен; лейблы вне хешей · резолв до валидации · один хоп `chain[0]` · гранулярность = книга; на владельце — вокабуляр лейблов и редактор под лейблом) → следующий платный прогон (разом: итерация №2 dspro-vs-glm · замер остатка ремонта · живой канал B) → сид-дельта (元 · 赤城 · 学堂家老 · 春秋蝉) → Ф2-механизмы (голос/состояние D21 · native-Gemini судья) · ja→ru §B5 → пилот Ф2.5.
|
||||
- **Стек:** draft deepseek-v4-flash thinking-ON → editor deepseek-v4-pro → судья gemini-preview + grok-фолбэк 18+ (~$0.85/ранобэ D30.4). Ключи: DEEPSEEK, ZAI, KIMI, OPENAI, GEMINI, XAI, MISTRAL (xAI: единый, data-sharing, off перед продом D27).
|
||||
- **Висит на владельце + открытые петли:** трекер в CURRENT-STATE `docs/PROGRESS.md` (единственное место).
|
||||
- **Стенд:** WSL2, GTX 1070 8GB (localhost из-под прокси = 403 — no-proxy транспорт для local). Книга `/home/ubuntu/books/gu-zhenren/` (GB18030; текст и производные ВНЕ git).
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
# Промт бэкенд-сессии: ПАК-17 «канал B (18+) вживую» (ДВУХФАЗНЫЙ: дизайн → ратификация → стройка)
|
||||
|
||||
**Статус: ВЫДАН 25.07.2026, оркестратор №7.** Цель: 18+ контент (уровни 1–2) идёт через волновой стек по adult-каналу — permissive-провайдеры, refusal-обработка, механические гарантии маршрутизации. Ратифицированная база: D14 (состав канала B) · D19 (Mistral = переводчик канала B; register-оговорка эха D19.1; судьи 18+ п.6: Gemini fail-closed на эротике, Grok первичный) · D22 (развязки 18+/эха; L3-спека D22.7) · D4 (adult-цепочка = permissive-провайдеры; уже в конфигах: `escalation.chains.adult: [grok-4.3]`). Эмпирика полигона: `experiments/10-explicit-benchmark` (violence) · `11-erotica-benchmark`.
|
||||
**Статус: ФАЗА 1 ИСПОЛНЕНА И РАТИФИЦИРОВАНА (`D39.26`, 25.07) → ФАЗА 2 РАЗМЕЧЕНА И ОТКРЫТА.** Дизайн-отчёт `docs/archive/reports/PACK17_DESIGN_2026-07-25.md` принят **В РЕДАКЦИИ §14** (тело §0–13 читать ТОЛЬКО через §14; при расхождении побеждает §14) + ревью-шапка оркестратора. **Фаза 2 стартует по §«ФАЗА 2» ниже** — там ратифицированный «ровно так»-список; §«ФАЗА 1» оставлена как история задания.
|
||||
|
||||
**Статус фазы 1: ВЫДАН 25.07.2026, оркестратор №7.** Цель: 18+ контент (уровни 1–2) идёт через волновой стек по adult-каналу — permissive-провайдеры, refusal-обработка, механические гарантии маршрутизации. Ратифицированная база: D14 (состав канала B) · D19 (Mistral = переводчик канала B; register-оговорка эха D19.1; судьи 18+ п.6: Gemini fail-closed на эротике, Grok первичный) · D22 (развязки 18+/эха; L3-спека D22.7) · D4 (adult-цепочка = permissive-провайдеры; уже в конфигах: `escalation.chains.adult: [grok-4.3]`). Эмпирика полигона: `experiments/10-explicit-benchmark` (violence) · `11-erotica-benchmark`.
|
||||
|
||||
## Инварианты — «РОВНО ТАК» (ратифицировано; конфликт с кодом/доками → пинг оркестратору через владельца, НЕ интерпретация)
|
||||
|
||||
|
|
@ -27,9 +29,40 @@
|
|||
|
||||
**Приёмка фазы 1:** отчёт `docs/archive/reports/PACK17_DESIGN_<дата>.md` — НАЧИНАЕТСЯ эхо-блоком (≤10 строк: скоуп/инварианты/не-делать); по каждому B: варианты, рекомендация, риски; адверсариальный селф-ревью (линзы: L3-линия/гардрейлы · маршрутизация-fail-loud · байт-точность канала A · деньги/резюм · общность §0). **СТОП — ратификация.**
|
||||
|
||||
## ФАЗА 2 — СТРОЙКА (после ратификации; список станет размеченным «ровно так»)
|
||||
## ФАЗА 2 — СТРОЙКА (ратифицировано `D39.26`; читай вместе с §14 отчёта — там file:line на каждый пункт)
|
||||
|
||||
Инварианты фазы: golden байт-идентичен · парити EXACT · `-race` · **сэндбокс-репродукции через драйвер** (правило 11: mock-провайдер, сквозные сценарии — refusal-каскад, non-permissive-реджект, маршрутизация) · дифф `^func Test` исполнением · заявление=команда. Хвосты из петель: ниты общности (Detail-строки чекеров → из данных · множитель ×2 `lintTimeUnits` → ключ данных).
|
||||
**Читать перед работой:** §14 отчёта фазы 1 (несущее) + `D39.26` в `05-decisions-log.md` (что я изменил против §14 и три добора приёмки). Промт даёт РЕШЕНИЯ; код-факты вытаскивай сам, отчёт — карта.
|
||||
|
||||
### «РОВНО ТАК» (ратифицировано; отступление = пинг оркестратору через владельца, не тихая девиация)
|
||||
|
||||
1. **Лейблы НЕ входят ни в один хеш.** Назначенный слой снапшота (разрядка D20.2-Q3) = **резолвнутая модель стадии + её wire-фолд, пер-стадийно и пер-волново** (`stageSnap`): лейбл на редакторе двигает ТОЛЬКО edit-волну, оплаченный драфт резюмится за $0. Канон brief ЗАМОРОЖЕН — `"adult":false` остаётся 7-м полем, omitempty-хвост НЕ нужен; `stageSnap.Escalate*` сохраняют имя/тег/позицию/правило заполнения, любой фолд хопов — строго аддитивный указатель+omitempty. Провенанс лейблов — шапка отчёта, не хеш.
|
||||
2. **Порядок: РЕЗОЛВ → ВАЛИДАЦИЯ → все читатели видят резолвнутое.** Лейблы приходят в `LoadPipeline` тем же швом, что `pair` (доктрина «все проблемы конфига одним списком»); резолвнутые поля — `yaml:"-"`; «достижимое» ЛЕЙБЛ-ЗАВИСИМО (`st.EscalateTo`/`chains.default` под лейблом недостижимы); дедуп — только по цепочкам активных лейблов ИСПОЛНЯЕМОГО конфига (C2-заглушка не блокирует загрузку); резолв работает и на read-путях (`export`/`status` пере-считывают снапшот).
|
||||
3. **Эскалация под лейблом = ОДИН хоп `chain[0]`** (Q4 = B3-A′): `len(chain) > 1` — громкая ошибка загрузки «мульти-хоп не построен» (тихого усечения нет); `escalation.budget_usd > 0` ОБЯЗАТЕЛЕН для route-лейбла (иначе гейт не может выстрелить); `escMu` — как сегодня (лок-на-итерацию ОТМЕНЁН: он ломает пин «перерасход ≤ 1 хоп»).
|
||||
4. **Гранулярность v1 = КНИГА** (аргумент гарантии: авто-скрина нет, пер-главность сводит ToS-гарантию к точности ручного чтения). `labelsFor()` — БЕЗ аргумента, с ассертом книго-константы; диапазоны глав НЕ обещать формой.
|
||||
5. **Вокабуляр данных:** книга — `content_labels:`, провайдер/модель — `accepts_labels:` (разные ключи: свойство контента ≠ разрешение получателя); пер-модельный оверрайд — `*[]string`, только СУЖАЕТ (nil = наследовать, указатель на пустой = «ни одного», модельный ⊆ провайдерского, load-тест на вычитание); реестр политик — **УПОРЯДОЧЕННЫЙ СПИСОК** (`action: route|terminal`), «первая политика, претендующая на стадию, выигрывает», равное притязание = громко; лейбл книги без записи в реестре = громко; эффективный список хопов — упорядоченное объединение с дедупом ПОСЛЕ резолва.
|
||||
6. **Отставки — ОДНИМ диффом:** `permissive` (громко при наличии) · `adult:` книги (громко ТОЛЬКО на `true`; `false`/отсутствие терпимо — ключ несут 13 файлов вне git + `example/book.yaml`; обязателен `LoadBook`-тест на все три формы) · **`Stage.Channel` + его enum + блок изоляции D4.1** (иначе `providerPermissive` вечно false и выживший `channel: adult` = операторский тупик) · `CheckAdultChannel`. Go-идентификаторы — БЕЗ слова adult (тег = замороженный ключ данных). Два существующих adult-теста перепиши/удали тем же диффом.
|
||||
7. **Механическая гарантия:** инвариант «для книги с набором лейблов L каждая модель, которую прогон может вызвать, несёт все лейблы L». Рубеж 1 — загрузка (сообщение называет модель, провайдера и НЕДОСТАЮЩИЙ лейбл). Рубеж 2 — рантайм-ассерт в `runAttempt` **ДО `Reserve`** через `clientFor(model, labels)` (компиль-энфорс: путь на провод не может не объявить контент). Ассерт НЕ переносить до `GetCheckpoint` (реплей ничего не отправляет; перенос отнял бы $0-реплей). Негативный тест — на СВЕЖЕМ вызове, ассертит идентичность ОШИБКИ, не дельту `spend`.
|
||||
8. **Отказы загрузки — за `forWrite`; резолв — ВСЕГДА.** Книга, помеченная после оплаты, обязана остаться читаемой ($0 read-only, D20.4) — иначе единственный выход «снять лейбл» = тихий обход; на read-пути те же условия дают громкое предупреждение + флаг в шапке отчёта.
|
||||
9. **`internal/llm/failover.go` — УДАЛИТЬ** (вызывающих нет; пересылает тот же запрос другому провайдеру — ровно то, что лейбл запрещает; nil-panic-дыра D4 п.1). Тесты файла уходят с ним — объявить в цензе `^func Test` ИСПОЛНЕНИЕМ. Гард-тест шва оставить: `\.Complete(` вне `internal/llm` и вне `stagerun.go` — пусто, с ИМЕНОВАННЫМ аллоулистом `live_conformance_test.go`.
|
||||
10. **Реестр-пример — только `route` на синтетическом значении.** `prohibited` из примеров убрать (не воспроизводить выходы будущего L3); механизм `terminal` построить, значение НЕ заводить; будущий L3-скрин подключается НАЗВАННЫМ пер-лейбловым хуком политики, а не через `terminal`. **D22.7 этим паком НЕ снимается** — пер-чанковый L3-скрин остаётся предусловием первой erotica-книги в проде.
|
||||
11. **Отказ РЕДАКТОРА под лейблом терминален** (D12 pinned: flag+skip без хопа). Refusal остаётся причино-агностичным: новых классов нет, `refusal.txt` не переписывается.
|
||||
12. **Закрыть тихий выход `reasoning:"off"` на аддитивном провайдере (добор приёмки B).** Сегодня гейт `pipeline.go:541` гейтится внешним `st.Reasoning != "off"`, а шиппинг-стадия edit объявляет ровно `off` (no-op для dspro, НО не для grok: `control: effort, off_effort: none` шлёт явный `none`) ⇒ деньги честны, а эхо-зона D19.1 п.2 («reasoning-off + плотный CJK — свойство класса, DeepSeek и ОБА grok-слага») открыта, и `echoMineViolation` выстрелить не может (`echoes_when_thinking_off` только у deepseek). **Форму выбираешь сам и аргументируешь** (данные: флаг у xai ⇒ `off` становится громким | код: аддитивный гейт над резолвнутой моделью независимо от `off`), закрытие — обязательно, с тестом.
|
||||
13. **Рейт-гарды утверждать ПОЗИТИВНО (добор C):** `rateGuard()` возвращает nil (= без лимита) молча, в отличие от громкого `r.client`; лейблованный переводчик — `mistral-large-2512` с `max_concurrency: 2`. Тест: резолвнутая лейблованная модель получила свой гард.
|
||||
14. **Тесты (все обязательны, правило 11 — гоняем, не читаем):** негативный load (книга с лейблом + модель без способности) · рантайм-ассерт до резервации · сквозняк refusal→хоп→OK→ре-гейт · сквозняк «хоп тоже отказал» → flag+skip · **генеральность на СИНТЕТИЧЕСКОМ значении лейбла** (если движок знает слово «adult» — тест красный) · второй лейбл только данными · шиппинг-конфиги в лейблованном сценарии.
|
||||
15. **Док-правки §10 отчёта (5 расхождений док↔код)** — тем же паком, включая устаревшие против D30.1 комменты про «вход редактора — русский черновик».
|
||||
|
||||
### «РЕШИ САМ И АРГУМЕНТИРУЙ»
|
||||
|
||||
- Форма закрытия п.12 · где физически живут резолвнутые поля и как именно `labelsFor` доезжает до `runAttempt` · формы валидационных сообщений · отчётность B6 (шапка `report`/`status` с резолвнутыми лейблами и таблицей маршрутизации · `Hops` из durable-чекпойнтов · read-only агрегат `SpendByModel`) — состав и объём на твоё усмотрение, миграций схемы НЕ вводить, `Role` синтетическую под лейбл НЕ заводить (ось `RequestHash` = новый платный вызов).
|
||||
- **Данные редактора/переводчика под лейблом НЕ прибивай сам:** переводчик = `mistral-large-2512` (D19, ровно так); **редактор и вокабуляр лейблов ждут владельца** (единый `adult` против `violence`/`sexually-explicit` — D14 п.1 оставляет DeepSeek допустимым на violence; кандидаты редактора только из способных, dspro запрещён на sexually-explicit по ToS, mistral отвергнут как несущий D39.20, glm-5 — одна строка данных ТОЛЬКО после сверки ToS/AUP Z.AI по первоисточнику). Строй механизм так, чтобы ответ был строкой данных; получишь его релеем.
|
||||
|
||||
### ВЫНЕСЕНО ИЗ ПАКА (не делать)
|
||||
|
||||
`escalation_model` = ОТВЕТИВШАЯ модель (вердикт-изменение: golden пинит `esc_model` на отказавшем хопе ⇒ отдельная ратификация с санкционированным пере-капчером) · `--accept-rebill` (D20.2-Q2, открытая петля) · полный обход цепочки · авто-детекция лейблов · L3-классификатор · судья 18+ (eval-сторона).
|
||||
|
||||
**Хвосты общности из петель — УСЛОВНО:** Detail-строки чекеров из данных · множитель ×2 `lintTimeUnits` (`checkers.go:174`) → ключ данных. Берутся ТОЛЬКО в байт-нейтральной форме (данные в пар-конфиге, фолд лишь при отличии от дефолта) с доказательством ИСПОЛНЕНИЕМ (выход чекеров байт-идентичен до/после над корпусом); иначе `CheapGateVersion`/`pack.Version()` двигают снапшот стендовых книг и убивают $0-резюм драфта под замер остатка пака-16 — тогда отложи и скажи явно. Молча bump-нуть версию нельзя, молча не bump-нуть — тоже.
|
||||
|
||||
Инварианты фазы: **golden байт-идентичен без пере-капчера** · парити EXACT · `-race` · **сэндбокс-репродукции через настоящий драйвер** (правило 11: mock-провайдер, сквозные сценарии — refusal-каскад, реджект неспособной модели, маршрутизация) · **дифф `^func Test` ИСПОЛНЕНИЕМ, не памятью** · заявление = команда (приёмка ре-ранит манифест) · самопроверка ревью исполнением своего кода и результатов · сессия НЕ коммитит, `git mv` декларировать.
|
||||
|
||||
## НЕ делать
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -11,17 +11,17 @@
|
|||
|
||||
## Структура
|
||||
|
||||
- `architecture/` — синтез. **Источник истины по решениям — [`05-decisions-log.md`](architecture/05-decisions-log.md) (D1–D39.25); при конфликте с любым доком он выше.**
|
||||
- `architecture/` — синтез. **Источник истины по решениям — [`05-decisions-log.md`](architecture/05-decisions-log.md) (D1–D39.26); при конфликте с любым доком он выше.**
|
||||
- `01-decisions.md` — принципы Р1–Р10; `02-mvp-plan.md` — фазы и приёмка (v3, 09.07); `03-implementation-notes.md` — контракты Фазы 0; `04-unhappy-paths.md` — ~70 режимов отказа → механизм; `06-memory-risk-registry.md` — реестр рисков банка памяти; **[`09-target-architecture.md`](architecture/09-target-architecture.md) — целевая 7-слойная архитектура (D39; статус стройки — шапка-таблица; инвариант общности §0.1)** · [`10-prompt-architecture.md`](architecture/10-prompt-architecture.md) — консолидированная промпт-заметка (концерн 4) · [`12-go-style-notes.md`](architecture/12-go-style-notes.md) — норматив общности §0 + Go-ответы. **Исполненные (архив 25.07, санкция владельца, → `archive/architecture/`):** [`07-strategic-review.md`](archive/architecture/07-strategic-review.md) (стратаудит 09.07 — курс исполнен) · [`08-sync-audit-ledger.md`](archive/architecture/08-sync-audit-ledger.md) (ледджер синк-аудита — отработан) · [`11-implementation-plan.md`](archive/architecture/11-implementation-plan.md) (план пака-11 — исполнен целиком); `components.puml`/`pipeline.puml` — диаграммы (перерисованы 25.07 под пост-пак-16 реальность: волновой исполнитель, пакетный сплит пака-15, repair-петля за `enabled:false`, generic content-labels D39.25; владелец смотрит PlantUML-расширением VS Code; вручную НЕ рендерить).
|
||||
- `experiments/` — эмпирика «Полигона»: `00-provider-quirks` (читать перед любым вызовом провайдера), `01-token-calibration`, `02-refusal-benchmark`, `03-local-stand`, `04-editor-quality`, `06-local-extraction`, `07-coverage-precision`, `08-cost-model-v2` (актуальная денежная модель), `09-pilot-protocol` (пилот Ф2.5 + поправки D13), `10-explicit-benchmark` (18+ violence-рука канала B), `11-erotica-benchmark` (erotica по трём парам/регистрам — закрытие D14.4, D22), `12-quality-diagnosis`/`13-translator-bakeoff`/`14-quality-empirics`/`14b-meaning-battery` (дуга качества «мерить→строить», закрыты → D30/D32/D37/D38), `15-segmentation-empirics` (exp15: когезия под floor-шумом, фертильность — закрыт → D39.7/D39.8), `16-bank-mining` (exp16: WHICH/WHAT банк-майнинга — закрыт → D39.10).
|
||||
- `research/` — фактура исследований 04–05.07: `01–10` базовые, `11-gap-*` добор критиком, `12-*` режимы отказа/отзывы/таксономии (+ два внешних материала с провенанс-шапками), `13` валидация памяти, `14` адаптивная память, `15` голос и состояние (принят, D21), **`16` ридер-IDE (принят с ревью-шапкой, D29)**, **`17` внешняя критика GPT-5.6 (принят с ревью-шапкой, D25)** — у 16/17 читать шапку прежде тела. **`18` рычаги качества (два отчёта, D36)** · **`19` нарезка+когезия+контракт t/e (D39.1)** · **`20` банк-майнинг W1.5 (D39.6)** · **`21` обзор LLM-транспорта чужих харнессов (23.07: наш транспорт опережает/вровень со всеми 11)** · **`22` доменные харнессы перевода (24.07: калибрующий — ядро подтверждено, впереди COGS/18+/общность/измеренность, позади Q4-выпуск и gate+repair-петля; сиквел `02`/`05`)** — у всех ревью-шапки. ⚠ Часть под superseded-баннерами (01/02/03/04/05/09 и gap-1/2/5) — **читай баннер прежде содержимого**.
|
||||
- `PROGRESS.md` — **журнал** (CURRENT-STATE сверху, ниже хронология; НЕ источник решений).
|
||||
- **Активные хендофф-промты (пост-пак-16, 25.07):** [`BACKEND_PACK17_CHANNEL_B_SESSION_PROMPT.md`](BACKEND_PACK17_CHANNEL_B_SESSION_PROMPT.md) (**пак-17 канал B (18+) вживую — ТЕКУЩИЙ, ЗАПУЩЕН 25.07, ДВУХФАЗНЫЙ**: дизайн B1–B6 → ратификация → стройка; L3 — жёсткая линия, live только по санкции владельца) · [`ORCHESTRATOR_SESSION_PROMPT.md`](ORCHESTRATOR_SESSION_PROMPT.md) (хендофф №6, 25.07 — только живое: роль/методология/уроки/нормы §1–11; состояние НЕ дублирует) · [`POLYGON_PACKAGE4_SESSION_PROMPT.md`](POLYGON_PACKAGE4_SESSION_PROMPT.md) (residual пилот/18+/echo). **Пак-16 ЗАЛЕНДЁН 25.07** (слой-3 diff-редактор: двухфазный дизайн D39.24 → стройка за `enabled:false`; отчёты с ревью-шапками — [`archive/reports/PACK16_DESIGN_2026-07-25.md`](archive/reports/PACK16_DESIGN_2026-07-25.md), [`archive/reports/PACK16_DIFF_EDITOR_REPORT_2026-07-25.md`](archive/reports/PACK16_DIFF_EDITOR_REPORT_2026-07-25.md)). **Паки 12/13/14/15 ЗАЛЕНДЕНЫ 24–25.07** (приёмка воркфлоу-линзами; отчёты с ревью-шапками — [`archive/reports/PACK14_GENERALITY_REPORT_2026-07-24.md`](archive/reports/PACK14_GENERALITY_REPORT_2026-07-24.md), [`archive/reports/PACK15_STRUCTURE_REPORT_2026-07-25.md`](archive/reports/PACK15_STRUCTURE_REPORT_2026-07-25.md); норматив — [`architecture/12-go-style-notes.md`](architecture/12-go-style-notes.md)). Дальше: **пак-17 = канал B вживую** (батч со следующим платным прогоном: вопросы итерации №2 + замер остатка ремонта → решение о включении петли) → голос/состояние D21 + C2/C3 → пилот Ф2.5. Закрытые — в `archive/prompts/`.
|
||||
- **Активные хендофф-промты (пост-пак-16, 25.07):** [`BACKEND_PACK17_CHANNEL_B_SESSION_PROMPT.md`](BACKEND_PACK17_CHANNEL_B_SESSION_PROMPT.md) (**пак-17 канал B (18+) вживую — ТЕКУЩИЙ, ДВУХФАЗНЫЙ: ФАЗА 1 ПРИНЯТА (D39.26, 25.07 — дизайн в редакции §14 отчёта), ФАЗА 2 РАЗМЕЧЕНА «ровно так» И ОТКРЫТА**; отчёт фазы 1 с ревью-шапкой — [`archive/reports/PACK17_DESIGN_2026-07-25.md`](archive/reports/PACK17_DESIGN_2026-07-25.md); L3 — жёсткая линия, live только по санкции владельца) · [`ORCHESTRATOR_SESSION_PROMPT.md`](ORCHESTRATOR_SESSION_PROMPT.md) (хендофф №6, 25.07 — только живое: роль/методология/уроки/нормы §1–11; состояние НЕ дублирует) · [`POLYGON_PACKAGE4_SESSION_PROMPT.md`](POLYGON_PACKAGE4_SESSION_PROMPT.md) (residual пилот/18+/echo). **Пак-16 ЗАЛЕНДЁН 25.07** (слой-3 diff-редактор: двухфазный дизайн D39.24 → стройка за `enabled:false`; отчёты с ревью-шапками — [`archive/reports/PACK16_DESIGN_2026-07-25.md`](archive/reports/PACK16_DESIGN_2026-07-25.md), [`archive/reports/PACK16_DIFF_EDITOR_REPORT_2026-07-25.md`](archive/reports/PACK16_DIFF_EDITOR_REPORT_2026-07-25.md)). **Паки 12/13/14/15 ЗАЛЕНДЕНЫ 24–25.07** (приёмка воркфлоу-линзами; отчёты с ревью-шапками — [`archive/reports/PACK14_GENERALITY_REPORT_2026-07-24.md`](archive/reports/PACK14_GENERALITY_REPORT_2026-07-24.md), [`archive/reports/PACK15_STRUCTURE_REPORT_2026-07-25.md`](archive/reports/PACK15_STRUCTURE_REPORT_2026-07-25.md); норматив — [`architecture/12-go-style-notes.md`](architecture/12-go-style-notes.md)). Дальше: **пак-17 = канал B вживую** (батч со следующим платным прогоном: вопросы итерации №2 + замер остатка ремонта → решение о включении петли) → голос/состояние D21 + C2/C3 → пилот Ф2.5. Закрытые — в `archive/prompts/`.
|
||||
- `archive/` — `prompts/`: закрытые сессионные промты (только история, инструкции оттуда не исполнять) · `reports/`: отчёты паков/ресёрч-сессий с ревью-шапками (живая фактура приёмок — на них ссылаются D-лог и активные промты) · `architecture/`: исполненные арх-доки (07 стратревью · 08 синк-ледджер · 11 план пака-11) · три слайса хроники `PROGRESS-2026-07-{04-10,10-13,13-25}.md`.
|
||||
|
||||
## Статус (2026-07-25, пост-пак-16/D39.25 — механизация выпускного качества)
|
||||
## Статус (2026-07-25, пост-пак-16 / дизайн пака-17 ратифицирован D39.26 — механизация выпускного качества)
|
||||
|
||||
Фаза 0 ✅; Ф1-инфра ✅ (D20–D28). **АРХ-РЕСЕТ D39 ИСПОЛНЕН ЦЕЛИКОМ:** 7-слойная архитектура (`09-*`) → исследовательская программа (D39.1–D39.10) → план (D39.12, `11-*`) → стройка пака-11 (D39.13–D39.19: волновой исполнитель · чанкер output-бюджет · src→dst-редактор · Go-майнер паритет-EXACT · банкнота · `internal/lang`+langpacks · reject-set · echo-сплит). **ПЕРЕ-ПРОГОН rerun2 ИСПОЛНЕН И ПРОЧИТАН (D39.20, 23.07):** операционка вся подтверждена живьём (ОДИН resnapshot · **$0-резюм драфта ×3 арма** · echo 0 · $6.63<$15 · майнинг-стоп→подпись→reject-set); **планка ≤2 НЕ пройдена** — но дефект-классы полностью механизируемы. Три сигнала (судья dspro 0.679>glm 0.589>>mistral 0.232, floor-шум 0 · два слепых чтения): **анти-корреляция гладкость↔верность доказана трижды**; **mistral исключён как несущий редактор** (голос = north-star промпта); dspro-vs-glm → итерация №2 после механизации (драфт $0). research/21: транспорт наш опережает/вровень со всеми 11, рефакторинг отвергнут. **Эксп ЗАКРЫТ (D39.22), итерация №2 отложена; редактор = deepseek-v4-pro ИНТЕРИМ (glm-резерв); стайл-каноны D39.21 ратифицированы.** **Очередь (курс — разработка бэкенда):** паки 12–16 залендены (транспорт-гигиена · выпускной QA · генеральность · структура: движок разложен `internal/{text,seed,chunk,miner,checks,membank}`+композит-корень, конфиг-слой пары, строгий декод · слой-3 diff-редактор ПОСТРОЕН за `enabled:false` по двухфазному дизайну D39.24 — позиционная детекция + read-only скан остатка + машинерия ремонта §15.2; включение платного пути = решение владельца по замеру остатка на пост-профилактическом прогоне) → пак-17 = канал B вживую (ЗАПУЩЕН 25.07, двухфазный; D39.25: в движке НЕТ понятия «18+» — generic content-labels × provider-capabilities; батч со следующим платным прогоном: вопросы итерации №2 + замер остатка ремонта) → сид-дельта (元/赤城/学堂家老/春秋蝉) → Ф2-механизмы (D21 голос/состояние, native-Gemini судья) + ja→ru §B5 → пилот Ф2.5 (блокеры прежние: билингв-якорь D25.9-Q1, корпус, судья-дублёр D22.6). Судья 18+ = grok-фолбэк (квирк D22.6 подтверждён живьём). Ключ xAI: единый, data-sharing, off перед продом (D27).
|
||||
Фаза 0 ✅; Ф1-инфра ✅ (D20–D28). **АРХ-РЕСЕТ D39 ИСПОЛНЕН ЦЕЛИКОМ:** 7-слойная архитектура (`09-*`) → исследовательская программа (D39.1–D39.10) → план (D39.12, `11-*`) → стройка пака-11 (D39.13–D39.19: волновой исполнитель · чанкер output-бюджет · src→dst-редактор · Go-майнер паритет-EXACT · банкнота · `internal/lang`+langpacks · reject-set · echo-сплит). **ПЕРЕ-ПРОГОН rerun2 ИСПОЛНЕН И ПРОЧИТАН (D39.20, 23.07):** операционка вся подтверждена живьём (ОДИН resnapshot · **$0-резюм драфта ×3 арма** · echo 0 · $6.63<$15 · майнинг-стоп→подпись→reject-set); **планка ≤2 НЕ пройдена** — но дефект-классы полностью механизируемы. Три сигнала (судья dspro 0.679>glm 0.589>>mistral 0.232, floor-шум 0 · два слепых чтения): **анти-корреляция гладкость↔верность доказана трижды**; **mistral исключён как несущий редактор** (голос = north-star промпта); dspro-vs-glm → итерация №2 после механизации (драфт $0). research/21: транспорт наш опережает/вровень со всеми 11, рефакторинг отвергнут. **Эксп ЗАКРЫТ (D39.22), итерация №2 отложена; редактор = deepseek-v4-pro ИНТЕРИМ (glm-резерв); стайл-каноны D39.21 ратифицированы.** **Очередь (курс — разработка бэкенда):** паки 12–16 залендены (транспорт-гигиена · выпускной QA · генеральность · структура: движок разложен `internal/{text,seed,chunk,miner,checks,membank}`+композит-корень, конфиг-слой пары, строгий декод · слой-3 diff-редактор ПОСТРОЕН за `enabled:false` по двухфазному дизайну D39.24 — позиционная детекция + read-only скан остатка + машинерия ремонта §15.2; включение платного пути = решение владельца по замеру остатка на пост-профилактическом прогоне) → пак-17 = канал B вживую (двухфазный: **фаза 1 ПРИНЯТА — D39.26**, фаза 2 размечена и открыта; D39.25: в движке НЕТ понятия «18+» — generic content-labels × provider-capabilities; лейблы вне хешей · резолв до валидации · один хоп `chain[0]` · гранулярность = книга; на владельце — вокабуляр лейблов и редактор под лейблом; батч со следующим платным прогоном: вопросы итерации №2 + замер остатка ремонта) → сид-дельта (元/赤城/学堂家老/春秋蝉) → Ф2-механизмы (D21 голос/состояние, native-Gemini судья) + ja→ru §B5 → пилот Ф2.5 (блокеры прежние: билингв-якорь D25.9-Q1, корпус, судья-дублёр D22.6). Судья 18+ = grok-фолбэк (квирк D22.6 подтверждён живьём). Ключ xAI: единый, data-sharing, off перед продом (D27).
|
||||
|
||||
## Доступные ключи от моделей
|
||||
DEEPSEEK_API_KEY, ZAI_API_KEY, KIMI_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, XAI_API_KEY, MISTRAL_API_KEY
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -18,7 +18,7 @@
|
|||
> | 6 Export | ✅ ПОСТРОЕН: `checks.ExportNormalize` на каждом финале; санитайзер-классы strip+export | D39.2 · D38.3 |
|
||||
> | 7 Гигиена | ЧАСТИЧНО: реестр role→инъекция · вынос `foldModelWire` · снос сирот/`gatesEnabled` — сделаны; **target-aware гейты — ОТКРЫТО** (ru-target-долг = ограничение, D39.24 §15.3; едет с канал-B/таргет-работой) | D39.2 · паки 11–16 |
|
||||
>
|
||||
> §1 «как есть» читать как снимок ДО стройки (const-чанкер · glm-редактор · мёртвые `STMDepth`/`OverlapTokens` — всего этого больше нет). §4 отработан целиком: трек B исполнен (exp15/exp16 — программа ЗАВЕРШЕНА, D39.7/D39.10), гейт пере-прогона пройден фактом rerun2 (D39.20; планка ≤2 НЕ пройдена, эксп ЗАКРЫТ D39.22, редактор = deepseek-v4-pro интерим). Канал B — пак-17 (дизайн); инженерная форма переопределена **D39.25**: в движке НЕТ понятия «18+» — generic content-labels × provider-capabilities, refusal причино-агностичен; «18+ линия» в §6 читать как продукт-линию, не механизм движка.
|
||||
> §1 «как есть» читать как снимок ДО стройки (const-чанкер · glm-редактор · мёртвые `STMDepth`/`OverlapTokens` — всего этого больше нет). §4 отработан целиком: трек B исполнен (exp15/exp16 — программа ЗАВЕРШЕНА, D39.7/D39.10), гейт пере-прогона пройден фактом rerun2 (D39.20; планка ≤2 НЕ пройдена, эксп ЗАКРЫТ D39.22, редактор = deepseek-v4-pro интерим). Канал B — пак-17: **дизайн РАТИФИЦИРОВАН D39.26** (лейблы вне хешей — назначенный слой D20.2-Q3 = резолвнутая модель стадии пер-стадийно/пер-волново · резолв ДО валидации, «достижимое» лейбл-зависимо · один хоп `chain[0]` · гранулярность = книга · `content_labels`(книга) ⊆ `accepts_labels`(модель) · `Stage.Channel`/`permissive`/D4.1-блок отставляются одним диффом), фаза 2 = стройка; инженерная форма переопределена **D39.25**: в движке НЕТ понятия «18+» — generic content-labels × provider-capabilities, refusal причино-агностичен; «18+ линия» в §6 читать как продукт-линию, не механизм движка.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
344
docs/archive/reports/PACK17_DESIGN_2026-07-25.md
Normal file
344
docs/archive/reports/PACK17_DESIGN_2026-07-25.md
Normal file
|
|
@ -0,0 +1,344 @@
|
|||
# ПАК-17, ФАЗА 1 — ДИЗАЙН: generic content-labels × provider-capabilities (канал B «вживую»)
|
||||
|
||||
> **Ревью-шапка оркестратора №8 (25.07, приёмка пропорциональная — инлайн-пасс исполнением, без воркфлоу; РАТИФИЦИРОВАНО `D39.26`).** **Принято В РЕДАКЦИИ §14: тело §0–13 читать ТОЛЬКО через неё** (при расхождении побеждает §14).
|
||||
> Пере-выведено мной, не с чужих слов: **M1 ре-деривирован независимо** — все пять дайджестов посимвольно (`c2021b1b…cab0df` = `capture.golden:6`; omitempty-хвост при пустых лейблах байт-в-байт тот же); **фантом-гард D-ссылок чист** (D2/D3/D4.1/D6.2/D12/D13.6/D14.1/D15.2/D19/D19.1/D20.2-Q1,Q2/D20.4/D22.5-22.7/D25.4/D30.1/D39.20-25 существуют и говорят заявленное); спот-чеки подтвердили §14.1 (`BriefHash` book-global в общем билдере обеих волн при пер-стадийном `stageSnap`), §14.2 (сырое `reachableModels`; `chains.default[0]` = `escalate_to` драфта), §14.3 (`escMu`-пин «перерасход ≤ 1 хоп»; `Waves.Workers` дефолт 1), §14.5.6 (`GetCheckpoint:337` → `Reserve:379` → `client:410` → `Complete:442`), §14.6.1 (golden пинит `esc_model` на ОТКАЗАВШЕМ хопе), §14.6.2 (`style_check_version` фолдится безусловно), 13 файлов вне git с `adult:`.
|
||||
> **Ноль опровержений §14; три добора оркестратора (в D39.26):** (A) единый лейбл «adult» схлопывает ратифицированную violence-развязку D14 п.1 (DeepSeek допустим на violence) ⇒ вокабуляр лейблов — решение владельца; (B) `reasoning:"off"` — тихий выход из аддитивного гейта (`pipeline.go:541` внешним условием) И из эхо-гейта (`echoes_when_thinking_off` только у deepseek, обобщение D19.1 п.2 в данные не доехало) ⇒ закрытие обязательно; (C) рейт-гард молча деградирует в «неограниченно» (`ratelimit.go:92-97`) при лейблованном переводчике mistral `max_concurrency:2`.
|
||||
> **Неточности, выводов не меняющие:** манифестное «`accept-rebill` грепом пусто» — 9 попаданий, все в `backend/docs/D15.2-*.md` (в коде/CLI действительно нет); «23 сайта `LoadPipeline`» включает строку определения.
|
||||
> **Скоуп-выносы приняты:** `escalation_model`=ответившая (вердикт-изменение, отдельная ратификация) · два нита общности — условно (байт-нейтральная форма + доказательство исполнением) · D22.7 паком НЕ снимается · `--accept-rebill` (D20.2-Q2) — открытая петля. **Разметка фазы 2 — в промте пака-17.**
|
||||
|
||||
> **Статус: ДИЗАЙН-ОТЧЁТ, СТОП ПЕРЕД РАТИФИКАЦИЕЙ.** Сессия: бэкенд, 2026-07-25. Промт: `docs/BACKEND_PACK17_CHANNEL_B_SESSION_PROMPT.md` (фаза 1). Кода НЕ трогал — дерево чистое (`git status --porcelain` пусто на старте и на сдаче, кроме этого файла). Ратифицированная база: D39.25 (директива владельца: в движке нет понятия 18+) · D22.7 (L3 = политика входа) · D19 (переводчик канала B = Mistral) · D12/D2 (лестница отказов) · D4.1 (изоляция типом) · D15.2 §13-Q3 (первый рантайм-потребитель обязан назначить слой снапшота).
|
||||
|
||||
## §0. Эхо-блок (что я понял — ДО работы)
|
||||
|
||||
1. **Скоуп фазы 1:** только дизайн B1–B6 + адверсариальный селф-ревью; ни строчки кода; отчёт → СТОП → ратификация.
|
||||
2. **Инвариант №1 (архитектурный):** в движке НЕТ понятия «18+/adult/канал B». Механизм generic: content-label книги (данные книги) × capability провайдера (данные models.yaml) × политика цепочки (конфиг). «adult» — ЗНАЧЕНИЕ данных.
|
||||
3. **Инвариант №2:** refusal-обработка причино-агностична — детектим ФАКТ отказа (D12), причина = операторская диагностика; путь один: регенерация → эскалация по цепочке лейбла → flag+skip (D2).
|
||||
4. **Инвариант №3 (жёсткая линия):** L3 не строим, не анализируем; `eval/data/refusal_corpus/*` не открывал (проверяемо: ни одного чтения оттуда).
|
||||
5. **Инвариант №4:** live-вызовы с 18+ контентом — только по санкции владельца; дизайн и будущие тесты — на mock-провайдере и СИНТЕТИЧЕСКИХ безобидных фикстурах.
|
||||
6. **Инвариант №5:** канал A байт-идентичен — golden без пере-капчера, парити EXACT; adult-механика включается ТОЛЬКО явной маркировкой.
|
||||
7. **Инвариант №6:** capability-исключения (DeepSeek/Anthropic без adult) — СТРОКИ ДАННЫХ, не Go-литералы; гарантия «label ⊄ capabilities → провайдер не получает контент» компилируемая/тестируемая, fail-loud.
|
||||
8. **Мысленный тест генеральности:** второй лейбл (archaic-register) добавляется ДАННЫМИ, без правки Go.
|
||||
9. **Не делать:** L3-классификатор · live 18+ без санкции · реальный 18+ текст в репо · пар/книго/провайдер-литералы в Go · расширение скоупа без пинга.
|
||||
10. **Канал вопросов:** конфликт промта с кодом → пинг оркестратору через владельца (в §5/§6 ниже — 4 таких пинга и 3 вопроса владельцу).
|
||||
|
||||
## §1. Метод и что верифицировано исполнением
|
||||
|
||||
Метод — образец пака-16 (D39.24): эхо → инвентаризации → дизайн → адверсариальные линзы → ревизия отдельным слоем.
|
||||
|
||||
1. Онбординг: CLAUDE.md → `docs/README.md` → D-лог целиком по несущим блокам (D2/D3/D4/D12/D14/D19/D22/D39.20–D39.25) → `architecture/12-go-style-notes.md` §0 → нормы промтов §7–11.
|
||||
2. Личное чтение кода (не по памяти): `internal/config/{pipeline,models,book}.go`, `internal/pipeline/{disposition,escalation,stagerun,snapshot,waverun,runner,repair,clients}.go`, `configs/models.yaml`, `configs/pipeline-c1.yaml`, `prompts/zh-ru/editor.md`.
|
||||
3. Семь параллельных read-only инвентаризаций (снапшот/деньги/отчётность/транспорт/тесты/конфиги/refusal-поток), каждая с требованием `file:line` + цитата; их выводы ниже перепроверены точечно мной.
|
||||
4. **Ключевой численный факт проверен МОИМ исполнением** (не агентским): brief-канон golden-книги даёт ровно записанный в фикстуре `brief_hash` — см. §7 манифест, строка M1. Это фундамент §B5.
|
||||
|
||||
## §2. Инвентаризация: 8 фактов, на которых стоит дизайн
|
||||
|
||||
**Ф1. Сегодняшняя adult-поверхность в Go — ровно 6 точек, все load-time.** `config.Book.Adult` (`backend/internal/config/book.go:32`) · `config.Stage.Channel` `""|sfw|adult` (`pipeline.go:166`) · `config.Provider.Permissive` (`models.go:60`) + `Models.providerPermissive` (`models.go:271-276`) · `Pipeline.CheckAdultChannel` (`pipeline.go:336-346`) · enum-валидация канала (`pipeline.go:549-553`) · D4.1-изоляция типом (`pipeline.go:569-576`). Единственный вызов из движка — `runner.go:147`, тоже load-time.
|
||||
|
||||
**Ф2. Рантайм-влияния на выбор модели у книги НЕТ вообще.** Выбор модели 100% статичен из конфига: `r.client(...)` вызывается только с `model ∈ {st.Model, st.EscalateTo, Gates.Repair.Model}` (`runner.go:299-323`, `stagerun.go:410`). Единственное книжное свойство, меняющее поведение рантайма, — `TargetLang`, и оно гейтит ГЕЙТЫ (`isRuTarget`/`isCJKTarget`) и выбирает ДАННЫЕ (`lang.*For`), но никогда модель. `Stage.Channel` имеет НОЛЬ рантайм-читателей.
|
||||
|
||||
**Ф3. Единственная точка-удавка на провода — одна, и она уже есть.** `(*Runner).runAttempt` (`stagerun.go:326`) — единственный вызов `r.client(model)` (`stagerun.go:410`) и единственный `client.Complete(...)` во всём продовом коде (`stagerun.go:442`). Через неё проходят ВСЕ три класса вызовов: праймари (`stagerun.go:131`), эскалационный хоп (`escalation.go:132`), ремонт (`repair.go:389`). Failover-декоратор (`internal/llm/failover.go`) — мёртвый код: `NewFailoverClient` не имеет ни одного вызывающего вне своих тестов. Следствие: один клиент = один провайдер = одна модель, второго пути на провод нет.
|
||||
|
||||
**Ф4. Ремонтный путь ТЕРЯЕТ стадийный канал.** `repair.go:386`: `rst := config.Stage{Name: st.Name, Role: roleRepair, Model: model}` — синтетическая стадия копирует только Name/Role/Model, `Channel` отбрасывается. Любой ассерт, ключёванный на `st.Channel`, слеп на ремонтных вызовах; ассерт на `(лейблы книги/чанка, model)` — не слеп. Это решает форму B4.
|
||||
|
||||
**Ф5. `escalation.chains` — мёртвые данные рантайма.** Единственные потребители: валидация существования моделей (`pipeline.go:578-584`) и префлайт ключей под бюджетом (`models.go:507-518`). Исполняется ТОЛЬКО single-hop через `stage.escalate_to` (`escalation.go:90,132`). При этом `chains.adult` во всех 4 шиппинг-конфигах = **[grok-4.3], ОДИН элемент** (`configs/pipeline-c1.yaml:101`, `pipeline-arm-{glm,mistral,deepseek-pro}.yaml`). То есть «мульти-хоп по цепочке лейбла» сегодня по данным = один хоп.
|
||||
|
||||
**Ф6. `Book.Adult` фолдится в снапшот транзитивно — и документация это ОТРИЦАЕТ (док↔код).** `Adult bool json:"adult"` внутри канона `BriefHash()` (`book.go:221,228`) → `snap.BriefHash` (`snapshot.go:338,413`) → оба волновых snapshotID → `Request.SnapshotID` в каждом `RequestHash` (`render.go:247,271-274`). А комментарий `pipeline.go:331-332` утверждает: «Adult stays wire/verdict-NEUTRAL (no snapshot layer … never a hash input)» — **неверно**. Проверено численно (§7/M1): убрать `"adult":false` из канона = `c2021b1b…` → `58883ff5…`, то есть обязательный `--resnapshot` и пере-оплата КАЖДОЙ книги.
|
||||
|
||||
**Ф7. Байт-стабильность brief-канона — это защита денег следующего платного прогона, а не косметика.** На стенде 5 book.yaml, все с `adult: false`; `rerun2/book-dspro.yaml` и `book-mistral.yaml` по шапке «IDENTICAL to book.yaml (same book_id + all BriefHash fields, so the shared paid draft resumes at $0)». D39.20 зафиксировал $0-резюм драфта на трёх армах, а пак-16 оставил ОТКРЫТУЮ петлю (замер остатка ремонта на пост-профилактическом прогоне, D39.24 §15.1) — она поедет на следующем платном прогоне тех же книг. Сдвиг `BriefHash` = потеря $0-резюма драфта.
|
||||
|
||||
**Ф8. Тестовая поверхность adult = 2 теста, оба load-only, исполняемого adult-пути нет.** `config_test.go:37-64` (`CheckAdultChannel`, чистые in-memory структуры) и `runner_test.go:1552-1572` (`NewRunner`+`Close`, `TranslateBook` НЕ вызывается). НЕ покрыто: ветка «escalate_to должен быть permissive» (`pipeline.go:573-575`), enum-валидация канала, `providerPermissive` напрямую, любой хоп на adult-стадии, refusal→хоп→УСПЕХ→ре-гейт (все «хоп вылечил» тесты гоняют `cjk_artifact`), `soft_refusal`→хоп, `content_filter`→хоп. Ни одна фикстура не ставит `adult: true`. Мока на Go-шве не существует вообще: подделка живёт на HTTP (`httptest`, `runner_test.go:60-76`), ответы ключуются ПО ТЕЛУ запроса (имя модели / `max_tokens` / маркер в источнике).
|
||||
|
||||
## §3. B1 — Схема content-labels и гранулярность
|
||||
|
||||
### Вариант 1 (рекомендуемый): лейблы книги + capability провайдера + реестр политик в ран-конфиге
|
||||
|
||||
**Данные книги** (`book.yaml`): `content_labels: [<label>…]` — свободные строковые значения; на загрузке lower-case + сортировка (ровно дисциплина `StyleAllowlist`, `book.go:188-194`), пустой по умолчанию.
|
||||
|
||||
**Данные модели/провайдера** (`models.yaml`): `content_labels: [<label>…]` на ПРОВАЙДЕРЕ + опциональный пер-модельный оверрайд (слоение «провайдер → модель», как у `capabilities`, `models.go:327-338`). Одно и то же имя ключа с двух сторон читается как сам механизм: `labels(книга) ⊆ content_labels(модель)`.
|
||||
- **Почему НЕ переиспользуем существующий блок `capabilities:`**: он про ФОРМУ ПРОВОДА (budget_field/temperature/reasoning, `models.go:79-88`). Смешать «что модель принимает по контенту» с «как собрать тело запроса» — гарантированный футган на ревью. Имена разные, слоение одинаковое.
|
||||
- Миграция данных: `permissive: true` (3 строки — `models.yaml:67,105,113`) → `content_labels: [adult]`. Ключ `permissive` становится ОТСТАВНЫМ и падает ГРОМКО при наличии (дисциплина отставных ключей `Stage.LegacyPrompt`, `pipeline.go:134-142`). Стендовые книги указывают `models:` на репозиторный `configs/models.yaml` — правка одного файла достаточна, стендовой возни нет.
|
||||
|
||||
**Политика лейбла** (ран-конфиг, новый верхний блок): реестр, который даёт (а) допустимость лейбла, (б) действие, (в) цепочку хопов.
|
||||
```yaml
|
||||
content_policy: # движок знает ФОРМУ, не значения
|
||||
adult: { action: route, chain: adult } # route → маршрутизация по данным
|
||||
# prohibited: { action: terminal } # будущее значение: книга не обрабатывается вовсе
|
||||
```
|
||||
- `action: route|terminal`. `terminal` = загрузка книги падает ГРОМКО, ни одного вызова (это generic-механизм под политику ВХОДА D22.7 — без всякого классификатора).
|
||||
- Лейбл книги БЕЗ записи в реестре = громкая ошибка загрузки. Иначе «лейбл, о котором ран-конфиг не слышал» тихо означал бы «маршрутизируй дефолтами» — ровно класс тихой подмены, который репо уже отвергает (`pipeline.go:360-364`).
|
||||
|
||||
**Пер-стадийный оверрайд модели** (там же, где `model:`/`escalate_to:`):
|
||||
```yaml
|
||||
stages:
|
||||
- name: draft
|
||||
role: translator
|
||||
model: deepseek-v4-flash # путь без лейблов — не двигается
|
||||
label_models: { adult: mistral-large-2512 } # D19: переводчик канала B
|
||||
```
|
||||
Почему на стадии, а не отдельной таблицей: пер-стадийный фолд снапшота уже существует (`stageSnap`, `snapshot.go:278-336`), валидация уже пер-стадийная, eager-build клиентов уже пер-стадийный (`runner.go:299-323`) — оверрайд ложится в три готовых места без новой сущности.
|
||||
|
||||
**Разрешение конфликтов без приоритетов.** Go-map порядок не хранит, поэтому «приоритет по порядку объявления» нереализуем честно. Вместо приоритетов — ГРОМКИЙ отказ на загрузке: если фактический набор лейблов книги даёт для ОДНОЙ стадии два разных оверрайда, это ошибка конфига (набор лейблов книги известен на загрузке — проверка полная, не эвристическая). Альтернатива «упорядоченный список политик» отмечена как расширение, если владелец захочет приоритеты.
|
||||
|
||||
**Гранулярность v1 — КНИГА.** Резолвер объявляется как `labelsFor(ch chunk.Chunk) []string` (шов), но в v1 возвращает набор книги.
|
||||
Аргумент (не догадка): (а) источника данных для пер-главных лейблов в v1 нет — авто-детекция вне скоупа по L3-линии, а ручная разметка 1000+ глав неоперабельна; (б) любая правка лейблов — это `--resnapshot` при ЛЮБОЙ гранулярности (лейбл влияет на модель, модель фолдится пер-стадийно), так что пер-главность не дешевле; (в) добавление пер-главности потом = данные + один фолд, без переархитектуры.
|
||||
**Цена v1, честно:** книга с редкими explicit-главами едет ПЕРЕСТИКОМ вся — весь объём через permissive-провайдера. Это прямо задевает качество и COGS (см. B2). Вариант «диапазоны глав» (`{adult: {chapters: ["12-14","80-95"]}}`) операбелен руками и стоит немного — но это спекулятивный спрос; вынесен вопросом владельцу (§6/Q1).
|
||||
|
||||
**Мысленный тест второго лейбла (archaic-register).** Нужны ровно три правки ДАННЫХ: `content_policy.archaic-register: {action: route, chain: archaic}` · `escalation.chains.archaic: [<модель>]` · `content_labels: [archaic-register]` у способного провайдера (+ при желании `label_models`). Go не трогается. **Механическая проверка генеральности в фазе 2:** сквозной тест гоняет СИНТЕТИЧЕСКОЕ значение лейбла (не «adult») — если движок хоть где-то знает слово «adult», такой тест красный.
|
||||
|
||||
### Вариант 2 (отклонён): лейбл как перечисление в Go (`type ContentLabel string` + константы)
|
||||
Даёт валидацию значений компилятором и автодополнение. Отклонён: прямо нарушает D39.25 (значение = данные), и второй лейбл потребовал бы правки Go — мысленный тест провален.
|
||||
|
||||
### Вариант 3 (отклонён): оставить `Stage.Channel`, переименовав значения
|
||||
Минимум правок, но сохраняет «канал» как ОСЬ КОДА (стадия объявляет свой канал), тогда как директива требует ось ДАННЫХ КОНТЕНТА (книга/глава несёт свойство). Дополнительно: `Channel` теряется на ремонтном пути (Ф4) — то есть ось стадии структурно не годится для гарантии B4.
|
||||
|
||||
**Риски B1:** (а) два места, где живут лейблы (книга + models.yaml), рассинхронизируются — закрыто требованием B4 (все достижимые модели обязаны нести все лейблы книги, иначе загрузка падает); (б) свободные строки допускают опечатку — закрыто: опечатанный лейбл не объявлен ни одним провайдером → громко; (в) реестр политик добавляет верхний блок в ран-конфиг (строгий декод `KnownFields`, `pipeline.go:366`) — старые конфиги без блока валидны, пока книга без лейблов.
|
||||
|
||||
## §4. B2 — Маршрутизация ролей под лейблом
|
||||
|
||||
**Переводчик = `mistral-large-2512`** — D19 п.1, «ровно так»: `label_models: {adult: mistral-large-2512}` на draft-стадии. Провайдер уже несёт способность (`models.yaml:105`), цена $0.5/$1.5 (`models.yaml:220`), не reasoning-модель ⇒ эхо-мины нет, есть rate-guard `max_concurrency: 2` (`models.yaml:226`) — под волновым параллелизмом это уже учтено.
|
||||
|
||||
**Редактор — открытый вопрос, разбираю по данным.** Способность сегодня несут только `xai`, `mistral`, `local` (`models.yaml:67,105,113`). Значит кандидатов ровно три, и:
|
||||
- `deepseek-v4-pro` (текущий интерим-редактор, D39.22) **НЕЛЬЗЯ**: ToS DeepSeek §3.4(5) прямо запрещает sexually explicit (D14 п.1, верифицировано по первоисточнику). Это данные, не код.
|
||||
- `mistral-large-2512` — **отвергаю как несущий редактор**: D39.20 исключил mistral именно в этой роли (анти-корреляция гладкость↔верность доказана трижды; критические смысловые аварии вне гейтов). Годится как дешёвый фолбэк-хоп, не как несущий.
|
||||
- `local-qwen3-8b` (abliterated) — скрининг/дешёвый фолбэк (D19 п.1: 8/10), не несущий редактор.
|
||||
- **`grok-4.3` — рекомендация, ИНТЕРИМ.** Аргументы: (1) бейк-офф exp04 кормил моделей БИЛИНГВАЛЬНО и дал grok ранг-1 по верности / ранг-2 по стилю (D3 «правка редактора»), а сегодняшний редактор — ровно билингв (D30.1; `prompts/zh-ru/editor.md:35-37` подставляет `{{text}}` = исходник); (2) это ратифицированный первичный судья эротики (D22.6), т.е. на этом контенте он мерян как не-отказывающий; (3) единственный из способных, кто вообще претендует на «несущий».
|
||||
**Цена и ловушки — тоже честно:** $1.25/$2.50 (`models.yaml:183`) — это ×~2.9 по output к dspro ($0.435/$0.87, `models.yaml:131`); провайдер `xai` биллит reasoning АДДИТИВНО (`models.yaml:66`), а D30.1 снял reasoning-off из редакторских ролей ⇒ стадия обязана объявить `reasoning_max_tokens > 0`, иначе загрузка падает по D6.2/D13.6 (`pipeline.go:541-547`) — это не опция, а гейт.
|
||||
**Замера НЕТ:** билингв-редактор на лейблованном контенте не мерян никем (exp04 — короткие SFW-фрагменты, exp10/11 мерили ПЕРЕВОДЧИКОВ и СУДЕЙ). Выбор интерим до замера; замер — довесок к следующему платному прогону (полигон), как и вопросы итерации №2.
|
||||
- **`glm-5` — естественный дешёвый кандидат, но требует ДАННЫХ, которых у нас нет.** Способность выдаётся только после проверки ToS/AUP Z.AI по первоисточнику (правило двух направлений, `experiments/00-provider-quirks.md`); угадывать ToS запрещено. Если проверка положительна — это ОДНА строка данных (`content_labels: [adult]` у `zai`) и переключение `label_models`, без правки Go.
|
||||
|
||||
**Эскалация под лейблом** — цепочка лейбла (`chains.adult: [grok-4.3]`, `pipeline-c1.yaml:101`). Внимание на коллизию: если редактор = grok-4.3, то grok в цепочке ПЕРЕВОДЧИКА остаётся корректен (разные стадии), но валидация обязана запрещать хоп в модель, равную праймари той же стадии (сегодня запрет есть только для `escalate_to`, `pipeline.go:557-558`).
|
||||
|
||||
**Судья** — вне пака (eval-сторона, проба-гейт D22.6).
|
||||
|
||||
**Риск-заметка (NOTE, не задача пака).** Обоснование «редактору эхо-класс не страшен, вход — русский черновик» в `models.yaml:37` (про GLM) и `configs/pipeline-c1.yaml:62` (про dspro) **устарело против D30.1**: редактор билингв и ВИДИТ плотный CJK (`prompts/zh-ru/editor.md:35-37`). Гейт `echoMineViolation` (`models.go:385-401`) прикрывает только провайдеров с `echoes_when_thinking_off` (сегодня — DeepSeek), у которого thinking всё равно ON. Для выбора редактора под лейблом это значит: у любого reasoning-off/эхо-склонного редактора экспозиция эха НЕ измерена, а D19.1/D22.5 показали, что на архаичном плотном zh даже reasoning-ON не спасает. Ловит эхо-гейт вниз по потоку (`disposition.go:210-214`), но платно.
|
||||
|
||||
## §5. B3 — Refusal-политика цепочки (GENERIC, причино-агностичная)
|
||||
|
||||
**Что НЕ меняется (семантику молча не двигаем).** Лестница D12/D2 остаётся: ретраим на той же модели только `{length, empty}` (`disposition.go:130-132`); детерминированные контент-провалы (`cjk_artifact`, `excision_suspect`, `loop_degenerate`, `hard_refusal`, `soft_refusal`, `content_filter`) эскалируются (`disposition.go:142-149`); всё остальное — flag+skip. **Отказ по эротике/политике/чему угодно уже сегодня идёт одним путём** — классификатор ловит ФАКТ (finish_reason=refusal / blacklist-совпадение на аномально коротком выходе), причина живёт только в диагностике (`chunk_status.detail`, `request_log.degraded`). Новых классов «18+ отказ» не вводим; `refusal.txt` не переписываем.
|
||||
|
||||
**Что добавляется:** источник хопов становится ЦЕПОЧКОЙ ЛЕЙБЛА для лейблованных стадий; путь без лейблов не двигается вовсе.
|
||||
|
||||
### Рекомендация B3-R: обход цепочки, включённый только лейблом
|
||||
- `content_policy.<label>.chain` → `escalation.chains.<name>` (упорядоченный список). Для стадии, у которой лейбл активен, цепочка ЗАМЕЩАЕТ `escalate_to` целиком (один источник истины на вызов); пустая цепочка у лейблованной стадии = громкая ошибка загрузки.
|
||||
- Обход: пока флаг эскалируем И остались хопы И бюджет допускает — следующий хоп, ре-гейт (`classifyOutput` уже гоняется на выходе хопа, `stagerun.go:168`), первый OK (или `FlagSanitizerStripped`) — авторитетен; цепочка исчерпана → остаётся флаг праймари → flag+skip (D2). Потолки: длина цепочки + существующий `escalation.budget_usd` + (рекомендую) `content_policy.<label>.max_hops` как явный кноб политики.
|
||||
- Отказ по потолку USD на хопе — деградирует, как сегодня (`escalation.go:133-143`), но ОСТАНАВЛИВАЕТ обход, а не пропускает один хоп.
|
||||
- **Обязательные к закрытию формы (иначе это латентная мина, а не механизм)** — 9 несовпадений, найденных инвентаризацией:
|
||||
1. `escalationOutcome` держит РОВНО ОДНУ попытку (`escalation.go:73-76`), а `stagerun.go:164-166` суммирует только `esc.fb.*` ⇒ хопы 1..N-1 были бы оплачены и НЕ учтены в `chunk_status.cost_usd`. → срез + суммирование.
|
||||
2. Ось request-hash: у `Request` нет поля хопа (`render.go:235-249`), хоп отличается только `Model` при `Attempt: 0` (`escalation.go:106`). Цепочка, повторяющая модель (или совпадающая с праймари/ретраем), даёт КОЛЛИЗИЮ хеша, а `checkpoints.request_hash` — PRIMARY KEY (`store/migrate.go:46`) ⇒ тихий реплей чужого хопа. → **валидация дедупа цепочки на загрузке** (члены различны, ≠ праймари стадии, ≠ `escalate_to`); НЕ вводить `Hop` в `RequestHash` (это `tm-request-v2`→v3 = инвалидация всех чекпойнтов).
|
||||
3. `escMu` держится `defer`-ом до конца функции (`escalation.go:123-124`) ⇒ при обходе мьютекс накрыл бы ВСЕ N вызовов и сериализовал волну. → лок/анлок на итерацию.
|
||||
4. `reachableModels` знает только `st.Model ∪ st.EscalateTo ∪ repair` (`runner.go:303-321`) ⇒ клиент для хопа ≥2 не собран и `r.client` падает громко посреди волны (`runner.go:347-351`). → расширить (это и есть «третья модельная ось», о которой предупреждает комментарий `runner.go:301-302`).
|
||||
5. Снапшот фолдит РОВНО ОДНУ эскалационную модель (`snapshot.go:317-335`) ⇒ правка capability хопа №3 была бы ТИХИМ false-hit. → массив фолдов хопов через указатель/omitempty (дисциплина `banknoteSnap`, `snapshot.go:108-112`), nil у книг без лейблов ⇒ байт-идентичность канала A.
|
||||
6. `chunk_status.escalation_model` — одна строка, и сегодня в неё пишется модель, которую ПОПРОБОВАЛИ, а не которая ОТВЕТИЛА (`stagerun.go:167`). → писать ответившую; путь обхода выводить из чекпойнтов (`escalation=1` + `model_requested`) — прецедент пака-16 «счётчики из durable-чекпойнтов, миграция ОТМЕНЕНА» (`store/ledger.go:99-100`).
|
||||
7. `Escalations` в read-моделях — булев счётчик на юнит (`status.go:226-228,338-341`) ⇒ обход из 3 хопов отчитается как 1. → оставить как есть, число хопов выводить (omitempty), см. B6.
|
||||
8. Предикат авторитетности написан под один `fb` (`stagerun.go:168`) → правило терминального выбора «первый OK».
|
||||
9. Валидация способности смотрит только `st.Model`/`st.EscalateTo` (`pipeline.go:569-576`) ⇒ маршрут по цепочке её обходил бы. → закрывается формулировкой B4 (все достижимые модели).
|
||||
|
||||
**Почему всё-таки B3-R, а не «сделаем позже».** `chains.adult` = один элемент (Ф5), поэтому обход сегодня исполняет ≤1 хоп: риск-поверхность мала, а механизм становится честным и `chains` перестаёт быть мёртвыми данными (репо уже наказан за мёртвые кнобы: снятые STMDepth/OverlapTokens, `pipeline.go:96-99`). При этом пп. 2 и 5 обязательны ДАЖЕ для одного хопа, если цепочка вообще становится источником.
|
||||
|
||||
### Альтернатива B3-A (санкционированный минимум): пер-лейбловый ОДИН хоп
|
||||
`label_escalate_to: {adult: <model>}` на стадии; ноль изменений формы снапшота, ноль риска по оси хеша. Дешевле примерно вдвое. Минус: `chains` остаются мёртвыми, и при росте цепочки нужна вторая миграция дизайна. Если владелец хочет минимальный пак — беру A.
|
||||
|
||||
### Вопрос владельцу (несущий): редактор под лейблом ПРИКОЛОЧЕН, значит его отказ терминален
|
||||
D12 разрешает `escalate_to` только на роли translator, и это проверяется на загрузке (`pipeline.go:563-565`; причина — дрейф стиля, 2605.13368). Для лейблованной книги это значит: **отказ РЕДАКТОРА = flag+skip, без хопа** — а буквальное чтение B3 («отказ любой причины → следующий хоп») этого не даёт. Варианты: (i) сохранить D12 (моя рекомендация — контракт, риск дрейфа стиля реален, отказ редактора на permissive-модели ожидаемо редок); (ii) разрешить хоп редактора ТОЛЬКО внутри цепочки того же лейбла и ТОЛЬКО для refusal-класса. Молча не выбираю — §6/Q3.
|
||||
|
||||
## §6. B4 — Механическая гарантия маршрутизации
|
||||
|
||||
**Формулировка инварианта (одна, сильная):** *для книги с набором лейблов L каждая модель, которую прогон может вызвать, обязана нести все лейблы из L.*
|
||||
Это ToS-гигиена в терминах D39.25: провайдер без способности не должен ПОЛУЧИТЬ контент — независимо от того, откажет ли он. Формулировка «все достижимые модели» строго сильнее старой D4.1-проверки (`pipeline.go:569-576`), потому что покрывает и цепочку, и ремонтную модель, и стадии без оверрайда.
|
||||
|
||||
**Рубеж 1 — загрузка (fail-loud, в общий список проблем `LoadPipeline`/`openRunner`):**
|
||||
1. каждый лейбл книги имеет запись в `content_policy` (иначе громко);
|
||||
2. `action: terminal` у любого лейбла книги → отказ запуска, ни одного вызова (generic-механизм под политику входа D22.7);
|
||||
3. `∀ m ∈ reachableModels(): L ⊆ content_labels(m)` — сообщение называет модель, провайдера и НЕДОСТАЮЩИЙ лейбл;
|
||||
4. дедуп цепочки (B3-R п.2) и запрет хопа в праймари той же стадии;
|
||||
5. конфликт двух лейблов на одной стадии (B1) — громко.
|
||||
`CheckAdultChannel` (`pipeline.go:336-346`) при этом ИСЧЕЗАЕТ как отдельная сущность: п.3 закрывает её задачу целиком и без слова «adult» (сегодня она лишь требует наличия какой-нибудь adult-стадии — слабее: не проверяет, что ВСЕ вызовы способны).
|
||||
|
||||
**Рубеж 2 — рантайм-ассерт в точке-удавке.** В `runAttempt` (`stagerun.go:326`) ДО `Reserve` (то есть деньги даже не резервируются): `labelsFor(ch) ⊆ content_labels(model)`, иначе громкая инфра-ошибка (книга durable-паузится, как на любой инфра-ошибке, D4) — это ассерт программной/конфигурационной ошибки, а НЕ контентный вердикт, поэтому не флаг.
|
||||
**Форма, которую рекомендую (компиль-энфорс, а не дисциплина):** заменить `r.client(model)` на `r.clientFor(model, labels)` (`runner.go:344-352`) — тогда НИ ОДИН будущий путь на провод не сможет получить клиента, не объявив, какой контент он отправляет. Это ровно стиль репо: `buildClients` уже громко падает на неперечисленной модели, а `foldModelWire` был вынесен, чтобы третья модельная ось не расползлась копипастой (`snapshot.go:488-494`). Ассерт обязан ключеваться на данных книги/чанка, а не на стадии — из-за Ф4 (ремонт теряет `Channel`).
|
||||
|
||||
**Тесты — обязательны оба рубежа (правило 11: сквозняк через настоящий драйвер, не чтение диффа):**
|
||||
- негативный load-тест: книга с лейблом + модель без способности → `NewRunner` падает, сообщение называет модель и лейбл;
|
||||
- рантайм-ассерт: конструируем ситуацию, обходящую загрузку (пост-конструкционный флип поля, прецедент `repair_integration_test.go:336`) → ассерт стреляет ДО резервации (проверяем: `spend` не двинулся);
|
||||
- позитивный сквозняк: лейблованная книга с синтетическим лейблом идёт через mock-провайдера, отказ на праймари → хоп по цепочке → OK → ре-гейт → `DispOK`; и «хоп тоже отказал» → flag+skip, edit skipped, exit 2. **Этот сценарий сегодня НЕ покрыт ни для одного refusal-класса** (Ф8) — то есть тест закрывает и старую дыру.
|
||||
|
||||
**Риски B4:** (а) `reachableModels` — единственный источник истины о «достижимом»; если новая ось вызовов появится и его не расширит, инвариант обойдут — прикрыто рантайм-ассертом (второй рубеж) и громким `clientFor`; (б) ассерт на горячем пути — стоимость O(|L|) сравнений строк на вызов, шум нулевой рядом с сетевым вызовом; (в) `local`-провайдер несёт способность и $0 — соблазн «прогнать лейблованное локально» существует и легитимен (D19: abliterated-скрининг), но качество 8b как несущего редактора не годится (B2).
|
||||
|
||||
## §7. B5 — Wire / снапшот / резюм / фикстуры
|
||||
|
||||
**Р1. Лейблы получают ЯВНЫЙ слой снапшота — это требование контракта, а не выбор.** D15.2 §13-Q3 ратифицировано: «Первый рантайм-потребитель `Adult` (если появится) обязан ЯВНО вернуть его в `wireSnapshotID`/`verdictSnapshotID`». Пак-17 и есть тот первый потребитель. Назначение: (а) НАБОР ЛЕЙБЛОВ едет в `BriefHash` (wire-ось, транзитивно во оба волновых снапшота, `snapshot.go:413`); (б) РЕШЕНИЕ маршрутизации едет через `stageSnap.Model`, который становится РЕЗОЛВНУТОЙ моделью (routing применяется ДО рендера снапшота) — то есть в снапшоте лежит модель, которую реально позовут, как и сегодня по смыслу; (в) цепочка хопов — массивом фолдов (B3-R п.5). Отдельный фолд самих `content_labels(модель)` НЕ нужен: их эффект целиком захвачен резолвнутой моделью + её wire-фолдом, а изъятие способности падает ГРОМКО на загрузке (никогда не «тихо перемаршрутизируем»).
|
||||
|
||||
**Р2. Байт-идентичность канала A достигается ровно одним способом — и он проверен численно.** Канон `BriefHash` (`book.go:214-228`) сегодня рендерит `"adult":false` СЕДЬМЫМ полем. Мои замеры (M1):
|
||||
|
||||
| состояние канона | brief_hash | следствие |
|
||||
|---|---|---|
|
||||
| сегодня | `c2021b1b…cab0df` (совпадает с `testdata/golden/capture.golden:6`) | — |
|
||||
| поле убрано | `58883ff5…b39822` | `--resnapshot` для КАЖДОЙ книги, пере-капчер golden, потеря $0-резюма стенда |
|
||||
| `content_labels: null` на месте поля | `29a3031d…8c87b` | то же |
|
||||
| omitempty-хвост, лейблов нет | **байт-в-байт `c2021b1b…`** | ноль пере-оплаты, golden не трогается |
|
||||
| omitempty-хвост, `["adult"]` | `0e2f3111…c9d2c` | лейблованная книга — другой бриф (корректно и громко) |
|
||||
|
||||
⇒ **Рекомендация:** `adult:` становится ОТСТАВНЫМ ключом книги (`LegacyAdult bool yaml:"adult"`, дисциплина `Stage.LegacyPrompt`, `pipeline.go:134-142`): значение `true` = громкая ошибка миграции, называющая `content_labels: [adult]`; `false`/отсутствие — терпимо (это ровно то, что стоит в 5 стендовых book.yaml и в `example/book.yaml:9`, а стенд вне git ⇒ терпимость экономит владельцу правки и не даёт ложного «книга не грузится»). В каноне поле остаётся на своём 7-м месте с постоянным `false` (заморожённая раскладка brief-канона, документируется как таковая), а `content_labels []string json:"content_labels,omitempty"` дописывается В КОНЕЦ. Условия байт-идентичности (все обязательны): порядок полей не меняется · тег не переименовывается · новое поле с `omitempty` · `LoadBook` НЕ дефолтит лейблы ни во что непустое (в отличие от `Encoding`/`YoPolicy`, `book.go:172-187`).
|
||||
**Альтернатива:** просто выкинуть поле и классифицировать сдвиг как «version-only». Отвергаю: цена — потеря $0-резюма драфта на стендовых книгах (Ф7), а открытая петля пака-16 (замер остатка) поедет именно на них.
|
||||
|
||||
**Р3. Резюм.** Лейблованные вызовы — обычные чекпойнты: `Model` уже в `RequestHash` (`render.go:271-274`), поэтому смена маршрута = честный промах чекпойнта на новую модель, а не ложное попадание. Правка лейбла = `--resnapshot` (через `BriefHash`) — громко и с пере-оплатой; операционное правило: **лейблы объявляются ДО первого платного вызова** (в v1 это ручное действие владельца при заводке книги). Пер-главность этого не улучшает (см. B1).
|
||||
|
||||
**Р4. Ремонт (пак-16) под лейблом.** `gates.repair.model` попадает в `reachableModels` (`runner.go:319-321`) ⇒ автоматически подпадает под инвариант B4. Отдельно отмечаю существующую асимметрию фолда: `repairSnap.ModelWire` берёт только capability (`snapshot.go:171`), без провайдерской тройки и `extra_body`, в отличие от праймари/эскалации — не задача пака, но если ремонт когда-нибудь поедет под лейблом с другой моделью, эту асимметрию надо закрыть.
|
||||
|
||||
**Р5. Фикстуры — только синтетика.** Реального 18+ текста в репо не появляется. Отказ моделируется маркером-прокси ровно так, как это уже делает golden: `goldenRefusalMarker` → «Не могу помочь с этим фрагментом.» + `finish_reason=refusal` (`golden_test.go:87-89`). Значение лейбла в тестах — СИНТЕТИЧЕСКОЕ (проверка генеральности, B1); отдельный дата-тест утверждает, что шиппинг-`models.yaml` объявляет `adult` ровно у трёх провайдеров, которые сегодня несут `permissive` — это утверждение о данных, без контента.
|
||||
|
||||
**Р6. Что фолдить НЕ надо** (чтобы не переплатить впустую): `content_policy` целиком (реестр может содержать лейблы, которых у книги нет) · `max_hops`/бюджеты (деньги — не wire и не вердикт, прецедент `BudgetUSD` не фолдится, `snapshot.go` repairSnap) · `content_labels` провайдеров (Р1).
|
||||
|
||||
## §8. B6 — Отчётность
|
||||
|
||||
**Дёшево сейчас (ноль миграций, дисциплина omitempty — «книга, не использовавшая фичу, байт-идентична прошлому», `quality.go:79-84`):**
|
||||
1. **Шапка `tmctl report`/`status`:** резолвнутые лейблы книги + таблица маршрутизации (стадия → модель под лейблом) — чистая проекция конфига, $0, без схемы.
|
||||
2. **QualityReport:** `Hops int` (число фактических хопов, выводится из чекпойнтов `escalation=1`) — закрывает искажение «булев `Escalations` = 1 при обходе» (B3-R п.7). Всё omitempty.
|
||||
3. **Атрибуция денег без миграции:** новый read-only агрегат `SpendByModel(bookID)` (`GROUP BY model_requested` — таких запросов сегодня нет ни одного, `store/*.go`), а сопоставление «какие модели лейбл-маршрутизированы» делается на стороне конфига. Даёт ответ «сколько стоил лейблованный маршрут», не трогая ни `spend`, ни ceiling-арифметику.
|
||||
**Чего делать НЕ надо:** (а) не переиспользовать `Checkpoint.Escalation` (конфликт с `escalation.budget_usd`); (б) НЕ вводить синтетическую роль под лейбл — `Role` есть ось `RequestHash` (`render.go:241`), новая роль = новый хеш = новый ПЛАТНЫЙ вызов, а лейбл-маршрутизация обязана сохранить роль `translator`/`editor` (прецедент `roleRepair` легитимен ровно потому, что это НОВЫЙ вызов).
|
||||
4. **Паспорт главы** (`ChapterPassport`, `status.go:35-55`) — сегодня менять не нужно: он агрегирует диспозиции и деньги, а «через какого провайдера шла глава» выводится из `chunk_status`+`request_log` при необходимости.
|
||||
|
||||
**В очередь (не этот пак):** пер-хоповая телеметрия пути (`[m1,m2,m3]`) как отдельная проекция · колонка лейблов на `chunk_status` (миграция v11 — не стоит, выводимо) · пометка в экспорте/ридере — это фронт по D39.25.
|
||||
|
||||
## §9. Открытые петли и вопросы владельцу (диспозиция обязательна — норма 8)
|
||||
|
||||
| # | Вопрос | Почему решает владелец | Моя рекомендация |
|
||||
|---|---|---|---|
|
||||
| Q1 | Гранулярность v1: книга или диапазоны глав? | Определяет качество/COGS: книга-целиком гонит ВЕСЬ объём через permissive-тир (в т.ч. редактора) | книга (нет источника данных для глав; правка лейбла — `--resnapshot` при любой гранулярности) |
|
||||
| Q2 | Редактор под лейблом: grok-4.3 интерим? | Деньги (×~2.9 output к dspro + аддитивный reasoning-буфер) против качества; замера нет | grok-4.3 ИНТЕРИМ; glm-5 — только после проверки ToS Z.AI по первоисточнику (данные, не код) |
|
||||
| Q3 | Отказ РЕДАКТОРА под лейблом: терминален (D12 pinned) или хоп внутри цепочки лейбла? | Дрейф стиля против потери юнита | сохранить D12 (терминален) |
|
||||
| Q4 | Скоуп B3: обход цепочки (B3-R) или пер-лейбловый один хоп (B3-A)? | Объём пака | B3-R (цепочка = 1 элемент сегодня ⇒ риск мал, `chains` перестают быть мёртвыми) |
|
||||
| Q5 | `chains.default` (3 модели) остаётся мёртвым для канала A? | Сделать живым = изменение денег канала A и не байт-идентично | оставить мёртвым в этом паке; отдельное решение |
|
||||
| Q6 | Нужно ли значение `action: terminal` уже сейчас (нет данных под него)? | Политика входа — человеческий уровень | построить механизм (он тривиален), значение не заводить |
|
||||
|
||||
## §10. Находки-побочки (док↔код; чинятся в фазе 2 попутно)
|
||||
|
||||
1. `pipeline.go:331-332` — «Adult … never a hash input» **неверно** (Ф6, проверено численно). Комментарий уезжает вместе с `CheckAdultChannel`.
|
||||
2. `models.yaml:37` и `configs/pipeline-c1.yaml:62` — «вход редактора — русский черновик, эхо-класс вырожден» **устарело против D30.1** (редактор билингв, `prompts/zh-ru/editor.md:35-37`). Правка комментариев + NOTE о неизмеренной экспозиции (§4).
|
||||
3. `configs/pipeline-c1.yaml:7-8` — «channel-aware фильтр цепочек (Anthropic вне 18+)» описывает несуществующий механизм и провайдера, удалённого из стека (`models.yaml:70`). Переписать под лейблы.
|
||||
4. `escalation.go:15` — «Channel B (18+) Ф2 extends EXACTLY this file (permissive chains)» — после пака формулировка станет generic.
|
||||
5. `stagerun.go:167` пишет в `escalation_model` ПОПРОБОВАННУЮ модель, не ответившую (B3-R п.6) — латентная неточность телеметрии уже сегодня, чинится тем же диффом.
|
||||
|
||||
## §11. Адверсариальный селф-ревью (5 линз промта)
|
||||
|
||||
**Линза 1 — L3-линия и гардрейлы.** Классификатор не проектируется и не упоминается как задача; `refusal_corpus` не открывался; реальный 18+ текст в репо/фикстуры не заводится (Р5). Механизм `action: terminal` — это ОТКАЗ ВХОДА по данным, а не автоматическая классификация уровня, т.е. он усиливает L3-политику, не подменяя её (D22.7 п.г остаётся невыполненным намеренно). Риск, который вижу: `terminal` может быть прочитан как «мы умеем детектить L3» — поэтому в §9/Q6 предлагаю построить механизм, но НЕ заводить значение.
|
||||
**Линза 2 — маршрутизация fail-loud.** Инвариант «все достижимые модели несут все лейблы книги» строго сильнее D4.1-проверки; ассерт стоит в единственной точке-удавке, доказанной греп-исчерпанием (Ф3), и ключуется на данных книги, а не на стадии (Ф4 — иначе ремонт слеп). Слабое место: `reachableModels` как источник истины — прикрыто вторым рубежом и `clientFor`. Второе слабое место: `local`-провайдер способен и $0 (см. риск B4в).
|
||||
**Линза 3 — байт-точность канала A.** Единственный путь без пере-оплаты найден и проверен численно (Р2, M1); все прочие варианты канона двигают хеш. Условия байт-идентичности выписаны в виде проверяемого списка; фазе 2 предписан прямой тест исполнением: `sha256` фикстуры golden не меняется и `git status` по `testdata/` пуст (как в приёмке пака-16). Опасность, которую держу на виду: любое непреднамеренное дефолтирование лейблов (например «sfw» по умолчанию) немедленно ломает байт-идентичность — поэтому пустой набор обязан оставаться пустым.
|
||||
**Линза 4 — деньги и резюм.** Ни одной новой оси `RequestHash` (осознанный отказ от `Hop` — это v3 и инвалидация всех чекпойнтов, B3-R п.2). Обход цепочки не сбрасывает retry-бюджет (D12) и наследует пре-хоповый софт-кап; отмечено, что при обходе перерасход деградирует с «≤1 хоп» до «≤1 цепочка» — поэтому `max_hops` вынесен в политику явным кнобом. Учёт стоимости всех хопов — п.1 обязательного списка (иначе оплаченные хопы не попали бы в `chunk_status.cost_usd`). Атрибуция — без миграций (B6.3). $0-резюм стенда защищён (Ф7/Р2).
|
||||
**Линза 5 — общность §0.** Ни одного нового Go-идентификатора/ветки с именем adult/18+/канал B: значения приходят строками из данных, движок сравнивает множества. Мысленный тест второго лейбла проходит на трёх правках данных (B1) и получает МЕХАНИЧЕСКУЮ проверку — сквозной тест на синтетическом значении лейбла. Остаточная честность: слово «adult» останется в (а) отставном ключе книги `yaml:"adult"`/`json:"adult"` (ровно чтобы миграция была громкой и канон не сдвинулся) и (б) данных `models.yaml`/`content_policy`. Первое — цена денег (Р2), второе — по директиве и есть данные. Считаю это соответствием, а не исключением, но это ровно тот пункт, который владельцу стоит ратифицировать явно.
|
||||
|
||||
**Что мой дизайн НЕ закрывает (честно):** судью 18+ (eval) · авто-детекцию лейблов · L3 · ja→ru §B5 · унаследованный ru-target-долг (D39.24 §15.3 — ограничение остаётся) · измеренный выбор редактора под лейблом (интерим до замера).
|
||||
|
||||
## §12. Манифест «заявление = команда» (норма 7; приёмка ре-ранит)
|
||||
|
||||
| # | Заявление | Команда |
|
||||
|---|---|---|
|
||||
| M1 | brief-канон golden = `c2021b1b…cab0df`; удаление `"adult":false` → `58883ff5…`; omitempty-хвост при пустых лейблах → байт-в-байт тот же хеш | `python3 -c` с каноном из `book.go:214-228` (см. §7 таблицу) + `sed -n '6p' backend/internal/pipeline/testdata/golden/capture.golden` |
|
||||
| M2 | `escalation.chains` не имеет рантайм-потребителей | `grep -rn "Escal.Chains\|Escal\." --include='*.go' backend/internal \| grep -v _test.go` |
|
||||
| M3 | единственный `client.Complete` в проде — `stagerun.go:442`; единственный `r.client(` — `stagerun.go:410` | `grep -rn "\.Complete(" --include='*.go' backend \| grep -v _test.go` ; `grep -rn "r\.client(" --include='*.go' backend \| grep -v _test.go` |
|
||||
| M4 | failover — мёртвый код | `grep -rn "NewFailoverClient\|FailoverConfig" --include='*.go' backend \| grep -v _test.go` |
|
||||
| M5 | `permissive: true` ровно у 3 провайдеров | `grep -n "permissive" backend/configs/models.yaml` |
|
||||
| M6 | `chains.adult` = один элемент во всех шиппинг-конфигах | `grep -rn "adult:" backend/configs/` |
|
||||
| M7 | ни один шиппинг-конфиг не объявляет `channel:`; `adult: true` нет нигде | `grep -rnE "^\s*channel:" backend/configs backend/internal/pipeline/testdata` ; `grep -rnE "^\s*adult:\s*true" .` |
|
||||
| M8 | adult/channel/permissive трогают ровно 2 теста | `grep -rn "adult\|Adult\|Permissive\|Channel: \"" --include='*_test.go' backend \| grep -v banknote \| grep -v miner` |
|
||||
| M9 | `Stage.Channel` не фолдится в снапшот | `grep -n "Channel" backend/internal/pipeline/snapshot.go` |
|
||||
| M10 | база тестов 433 (`^func Test`, греп — фаза 2 обязана пере-считать ИСПОЛНЕНИЕМ) | `grep -rn "^func Test" backend \| wc -l` |
|
||||
| M11 | редактор билингв: подставляется исходник | `grep -n "{{text}}\|Исходный текст" backend/prompts/zh-ru/editor.md` |
|
||||
| M12 | дерево чистое, сессия не коммитила | `git status --porcelain` |
|
||||
|
||||
## §13. Список работ фазы 2 (для разметки владельцем «ровно так» / «реши сам»)
|
||||
|
||||
1. Данные: `permissive` → `content_labels: [adult]` (3 строки models.yaml) + отставной `permissive` громко; `content_policy` в шиппинг-конфиги (пустой/отсутствующий = сегодняшнее поведение).
|
||||
2. Книга: `content_labels` (lower+sort, omitempty-хвост канона), отставной `adult:` (true → громко), канон заморожен.
|
||||
3. Резолвер `labelsFor(chunk)` (v1 — набор книги) + пер-стадийный `label_models` + резолв ДО рендера снапшота.
|
||||
4. Валидация (5 проверок §6 рубеж 1) + удаление `CheckAdultChannel`.
|
||||
5. Ассерт `clientFor(model, labels)` + расширение `reachableModels`.
|
||||
6. B3-R: обход цепочки с 9 закрытыми формами (или B3-A по решению владельца).
|
||||
7. Снапшот: массив фолдов хопов через указатель/omitempty; `stageSnap.Model` = резолвнутая модель.
|
||||
8. Отчётность: шапка отчёта, `Hops`, `SpendByModel`.
|
||||
9. Тесты: негативный load, рантайм-ассерт до резервации, сквозняк refusal→хоп→OK→ре-гейт, сквозняк «хоп тоже отказал», генеральность на СИНТЕТИЧЕСКОМ лейбле, второй лейбл только данными.
|
||||
10. Хвосты общности из петель: Detail-строки чекеров из данных · множитель ×2 `lintTimeUnits` в ключ данных.
|
||||
11. Док-правки §10 (5 расхождений).
|
||||
Инварианты фазы 2: golden байт-идентичен (без пере-капчера) · парити EXACT · `-race` · сэндбокс-репродукции через настоящий драйвер (правило 11) · дифф `^func Test` ИСПОЛНЕНИЕМ · сессия не коммитит.
|
||||
|
||||
---
|
||||
|
||||
# §14. ПОСТ-ПАНЕЛЬНАЯ РЕВИЗИЯ (отдельный слой; тело §0–13 НЕ переписано — метод пака-16, D39.24)
|
||||
|
||||
> Прогнано 11 адверсариальных агентов: 5 предписанных линз (L3/гардрейлы · маршрутизация-fail-loud · байт-точность · деньги/резюм · общность §0) + 6 скептиков с мандатом «ОПРОВЕРГНИ ПО КОДУ». Вердикты: 4× ACCEPT_WITH_FIXES, 2× REJECT, 2× REFUTED, 1× PARTIALLY_REFUTED (по одному скептику ответ не пришёл — его клейм и так трижды проверен: мой замер + две независимые ре-деривации всех пяти дайджестов M1). **Три решения тела ОТМЕНЯЮ, два рекомендации ПЕРЕВОРАЧИВАЮ.** Ниже — только то, что я проверил сам; клеймы агентов, оказавшиеся неверными, помечены как отклонённые.
|
||||
|
||||
## §14.1. ОТМЕНА №1 (CRIT, несущая): лейблы НЕ идут в `BriefHash`
|
||||
|
||||
Тело (§7/Р1а, Р2) предлагало вести набор лейблов omitempty-хвостом brief-канона. **Это противоречит ратифицированному D20.2-Q1 — с тем же самым рабочим примером.** `docs/architecture/05-decisions-log.md:245`: «**Q1 — пер-стадийная гранулярность wireSnapshotID, ДА** (живой довод: вероятная смена редактора glm-5→**grok-4.3** при book-global пере-оплатила бы весь translator-бэклог)». Пак-17 — ровно этот сценарий: лейбл маршрутизирует РЕДАКТОРА на grok-4.3. `BriefHash` book-global (`book.go:214-228` → `snapshot.go:413` в общем билдере обеих волн), поэтому лейбл в каноне пере-оплатил бы и ДРАФТ-волну.
|
||||
|
||||
Второе, независимое основание: `backend/docs/D15.2-content-addressed-resume-spec.md:188` — `brief_hash` в v2 **задропан** → `content_hash`, и «ЛЮБОЕ brief-поле, потребляемое ВНЕ рендера, обязано получить явную судьбу»; два ратифицированных прецедента этого же класса назначены явно «**verdictSnapshotID** (не через `brief_hash`)» (`:227` YoPolicy, `:228` StyleAllowlist). Лейблы ни в один промпт не рендерятся ⇒ `content_hash` их не поймает ⇒ «через BriefHash» — это ровно тот путь, который §13-Q3 исключает, с отложенным окном тихой потери.
|
||||
|
||||
**Исправленное назначение слоя (это и есть разрядка §13-Q3):** набора лейблов в хешах НЕТ; вся его wire-судьба — **РЕЗОЛВНУТАЯ модель стадии + фолд её wire-формы, пер-стадийно и пер-волново** (`stageSnap`, `snapshot.go:278-336`; фильтр стадий по волне `snapshot.go:253-262`). Следствия, которые это покупает: лейбл, маршрутизирующий только редактора, двигает ТОЛЬКО edit-волновой снапшот — оплаченный драфт резюмится за $0 (ровно дисциплина repair-фолда, `snapshot.go:224-227`: «Folding it into both would move the DRAFT-wave snapshot … destroying the $0 draft resume»). Провенанс набора лейблов — НЕ хеш, а шапка отчёта (B6.1). Тихой расходимости не возникает: единственные эффекты лейбла вне резолвнутой модели — load-time (реестр, `terminal`, capability), а они падают ГРОМКО на загрузке.
|
||||
**Что из Р2 ОСТАЁТСЯ в силе:** заморозка `"adult":false` на 7-й позиции канона (байт-идентичность, M1) — она теперь ЕДИНСТВЕННОЕ, что делается с каноном, и omitempty-хвост не нужен вовсе. Условия байт-идентичности из §7 сохраняются + **шестое условие** (находка панели): поля `stageSnap.{EscalateTo,EscalateCapability,EscalateExtra,EscalateProviderTemp,EscalateProviderMaxTok,EscalateProviderModel}` сохраняют имя, тег, позицию и правило заполнения `if st.EscalateTo != ""` (`snapshot.go:323-335,463-472`); любой фолд хопов — строго АДДИТИВНЫЙ хвост через указатель/omitempty. Иначе «унификация оси хопов» вынесет 4 отрендеренных ключа из payload golden-фикстуры (`testdata/golden/pipeline.yaml:11` объявляет `escalate_to: fake-fallback`) и двинет оба снапшота.
|
||||
|
||||
## §14.2. ОТМЕНА №2 (CRIT, конструктивная): резолв маршрутизации ДО валидации; «reachable» становится ЛЕЙБЛ-ЗАВИСИМЫМ
|
||||
|
||||
Тело (§6 рубеж-1 п.3 + §13 п.5 «расширить `reachableModels`») задаёт инвариант над СЫРЫМ `reachableModels()` (`runner.go:311-314` безусловно добавляет `st.Model` и `st.EscalateTo`). Проверил: для лейблованной книги на шиппинг-конфиге это **ложное падение загрузки** — `deepseek-v4-flash`/`deepseek-v4-pro` (`configs/pipeline-c1.yaml:36,51,63`) не несут способности (`models.yaml:26` без флага), хотя под лейблом они замещены. Три агента сошлись на этом независимо; я подтверждаю по коду. Плюс тот же дефект имеет ещё пять проявлений, каждое проверено:
|
||||
- **Гейт аддитивного reasoning обходится** (`pipeline.go:541-547` смотрит только `st.Model`/`st.EscalateTo`): `edit{model: deepseek-v4-pro (subset), reasoning_max_tokens: 0, label_models:{adult: grok-4.3}}` грузится, а в рантайме `AdditiveReasoningTokens(grok,…,0)=0` (`models.go:298-305`) ⇒ резерв не видит reasoning ⇒ потолок книги слепнет. Это ровно дыра D6.2/D13.6 — и она открывается на РЕКОМЕНДОВАННОМ мной редакторе.
|
||||
- **Коллизия request_hash выживает** заявленный дедуп: все существующие «та же модель» гарды сравнивают КОНФИГОВЫЙ `st.Model` (`pipeline.go:557-558`). При `label_models:{adult: X}` и хопе в `X` хоп строит ТОТ ЖЕ хеш (`Attempt: 0`, тот же stage/role/temp/reasoning/snapshot/msgs, `escalation.go:105-109`) ⇒ `GetCheckpoint` ПОПАДАЕТ ⇒ «хоп» бесплатно реплеит собственный отказ праймари, `stagerun.go:164` двойным счётом добавляет `esc.fb.cumCost` в `chunk_status.cost_usd`, `Escalated=true` пишется, а отказ на лейблованной книге НЕ пере-маршрутизируется. `checkpoints.request_hash` — PRIMARY KEY (`store/migrate.go:46`), то есть это тихий реплей, не ошибка.
|
||||
- **`CheckKeys`** (`models.go:507-518`) под `budget_usd>0` требует ключи ВСЕХ цепочек — для лейблованной книги это ключи цепочек, которые она не пройдёт.
|
||||
- **Дедуп цепочки, заданный неограниченно**, отвергает все 4 шиппинг- и 3 стендовых конфига (`chains.default` содержит `deepseek-v4-pro`, равный `escalate_to` драфта: `pipeline-c1.yaml:100` vs `:51`).
|
||||
- **`gates.repair.model`** для лейблованной книги обязана быть способной И не-аддитивной (`pipeline.go:606-610`) ⇒ из способных остаются только `mistral-large-2512` (исключён D39.20 как несущий) и `local-qwen3-8b`; grok падает на загрузке. Поле глобальное и фолдится (`snapshot.go:143`) ⇒ правка под лейблованную книгу инвалидирует ремонт канала A.
|
||||
|
||||
**Исправленная конструкция (закрывает все шесть одним ходом):** **РЕЗОЛВ → ЗАТЕМ ВАЛИДАЦИЯ → ЗАТЕМ ВСЁ ЧИТАЕТ РЕЗОЛВНУТОЕ.**
|
||||
1. Лейблы книги приходят в `LoadPipeline(path, models, pair, labels)` — тем же способом, каким уже приходит `pair` (один прод-вызов `runner.go:137`, 22 тестовых). Это сохраняет доктрину «все проблемы конфига одним списком» (`models.go:6-7`), которую вариант «второй метод после загрузки» ломает.
|
||||
2. Резолв заполняет `yaml:"-"`-поля стадии (`ResolvedModel`, `ResolvedHop`) — установившаяся конвенция резолвнутых полей (`pipeline.go:145` `PromptPath`, `:217` `PromptsDir`).
|
||||
3. ВСЕ существующие гарды пере-прогоняются над резолвнутым: «хоп ≠ праймари», аддитивный буфер, роль-скоуп D12, capability-инвариант, `CheckKeys`, `reachableModels`, фолд снапшота, рантайм.
|
||||
4. «Достижимое» для лейблованной книги = `{резолвнутые модели стадий} ∪ {хоп(и), выбранные ЛЕЙБЛОМ} ∪ {repair-модель}`; `st.EscalateTo` и `chains.default` под лейблом НЕДОСТИЖИМЫ по построению. Дедуп применяется ТОЛЬКО к цепочкам, выбранным политикой лейблов книги — `chains.default` не валидируется (согласовано с Q5).
|
||||
5. Резолв обязан работать и на READ-путях: `status`/`export` пере-считывают `snapshotIDForWave` для дрейф-детекции (`export.go:274`), значит без резолва их проекция разошлась бы с translate.
|
||||
|
||||
**Отклоняю** сопутствующий клейм панели «ни один тест не грузит шиппинг-конфиги, дефект всплыл бы только на платном прогоне»: грузят — `prompt_pack_test.go:178` (c1) и `:232` (все пять), `echo_mine_test.go:196` (c1/c2), `:245` (армы). Неограниченный дедуп упал бы в CI, а не на прогоне. Фикс остаётся, страшилка — нет.
|
||||
|
||||
## §14.3. ПЕРЕВОРОТ РЕКОМЕНДАЦИИ №1 (Q4): пак-17 берёт ОДИН хоп из цепочки лейбла, обход цепочки — отложен
|
||||
|
||||
Тело рекомендовало B3-R (полный обход). Панель довела список обязательных к закрытию форм с 9 до **≥15** (добавлены: гейт аддитивного reasoning над членами цепочки · `CheckKeys` над резолвнутым набором · `budget_usd: 0` как молчаливый выключатель всего механизма · взаимодействие пер-хопового реплея с бюджетом · роль-скоуп D12 · коллизия «две метки → две цепочки с общим членом»). Плюс `escMu`: тело предлагало лок на итерацию — **отменяю**, это ломает ратифицированную гарантию «перерасход ≤ 1 хоп», запинённую тестом `waverun_test.go:333` (бюджет 0.001 < один хоп ≈0.00182, `workers: 4`, ассерт равенства ровно одному хопу). При этом `Waves.Workers` по умолчанию 1 (`pipeline.go:414-415`) и ни один шиппинг-конфиг не объявляет `waves:` — то есть оптимизируемая конкуренция вне тестов не существует.
|
||||
|
||||
**Исправленная рекомендация B3-A′ (минимальная, честная, без мёртвых данных):** источник хопа под лейблом — `escalation.chains.<name>`, но исполняется **ОДИН хоп = `chain[0]`**; `len(chain) > 1` = ГРОМКАЯ ошибка загрузки «мульти-хоп не построен» (никакого тихого усечения). Сегодня `chains.adult: [grok-4.3]` — ровно один элемент во всех 4 шиппинг- и 3 стендовых конфигах, поэтому данные уже совместимы. Что это даёт: ноль изменений ФОРМЫ снапшота (существующего одиночного `Escalate*`-фолда достаточно) · ноль риска по оси хеша сверх §14.2 · сохранённая гарантия `escMu` (лок как сегодня) · `chains` перестают быть мёртвыми данными. Полный обход остаётся спроектированным (§5 + 6 добавленных форм) и ждёт данных, которые его требуют.
|
||||
**Дополнительно обязательно (находка панели, принята):** для книги, чей лейбл даёт `action: route`, требовать на загрузке `escalation.budget_usd > 0`, называя лейбл и цепочку. Иначе (шиппинг-дефолт `budget_usd: 0`, `pipeline-c1.yaml:106`) все проверки зелены, хопов НОЛЬ, первый отказ = flag+skip, и лейблованные юниты теряются молча — класс «гейт, который не может выстрелить», который репо уже отвергает для coverage/repair.
|
||||
|
||||
## §14.4. ПЕРЕВОРОТ РЕКОМЕНДАЦИИ №2 (Q1): аргумент за книжный уровень был неверным; вывод остаётся, но по ДРУГОЙ причине
|
||||
|
||||
Тело обосновывало книжный уровень «нет источника данных». **Опровергнуто:** глава-диапазоны — установившаяся идиома книжных данных (`internal/seed/seed.go:35-36` `until_ch`, потребляется `internal/membank/memory.go:507-511`), то есть ручная разметка арок операбельна. Второй аргумент тела («правка лейбла = пере-оплата при любой гранулярности») **тоже опровергнут** — после §14.1 пер-стадийный фолд делает пере-оплату локальной. И COGS-аргумент панели реален: лейблованная книга целиком едет по цене permissive-тира (draft mistral $0.5/$1.5 против flash $0.14/$0.28; editor grok $1.25/$2.50 + аддитивный reasoning против dspro $0.435/$0.87 — `models.yaml:119,131,183,220`).
|
||||
|
||||
**Но вывод я сохраняю, и вот НЕСУЩИЙ аргумент, которого не было ни в теле, ни у панели:** автоматического скрина контента НЕТ и не будет в этом паке (L3-линия). Поэтому при пер-главной разметке ToS-гарантия «провайдер без способности не ПОЛУЧИТ такой контент» деградирует до точности ручного чтения владельцем каждой главы: одна пропущенная сцена = лейблованный контент ушёл к не-способному провайдеру — ровно то, что D39.25 запрещает. При книжном уровне утечка структурно невозможна. То есть выбор — не «дорого против дешёво», а **«гарантия против стоимости»**, и это решение владельца (Q1), а не моё.
|
||||
Дополнительно (находка панели, принята): B1(в) «пер-главность потом = данные + один фолд» **неверно** — снапшот считается ОДИН раз на волну до пер-чанкового цикла (`waverun.go:104,114,150,163`), поэтому пер-главная маршрутизация требует снять фолд модели с book-global оси (карта лейбл→модель вместо скаляра) = переархитектура фолда, не «один фолд». Резолвер v1 объявляется КНИЖНЫМ (`labelsFor()` без аргумента, с ассертом «в v1 книго-константа»), чтобы не обещать формой того, чего механизм не держит.
|
||||
|
||||
## §14.5. Принятые точечные фиксы дизайна (проверены по коду)
|
||||
|
||||
1. **Реестр политик — УПОРЯДОЧЕННЫЙ СПИСОК, не map.** Тело оставило неопределённым случай ДВУХ активных лейблов на одной книге (две цепочки и два `max_hops` на один вызов), а map-порядок в Go не хранится — то есть Go выбирал бы итерацией. Форма: `content_policy: [{label: …, action: route, chain: …}, …]`, правило «первая объявленная политика, претендующая на стадию, выигрывает», громкая ошибка при равном притязании. Эффективный список хопов — упорядоченное объединение по активным лейблам с дедупом ПОСЛЕ резолва (иначе `chains.adult:[grok]` + `chains.archaic:[grok]` дают два идентичных хеша ⇒ второй хоп — бесплатный реплей первого).
|
||||
2. **Пер-модельный оверрайд способности — `*[]string`, и только СУЖАЕТ.** Копирование дисциплины `capabilities` («пусто = наследуй», `models.go:419-441`) на список означает, что модель НЕ МОЖЕТ вычесть лейбл провайдера: `content_labels: []` неотличим от «не задано» ⇒ модель наследует способность, ⊆-инвариант зелен, контент уходит к явно исключённой модели. Форма: указатель (nil = наследовать, непустой указатель на пустой список = «ни одного»), плюс правило «модельный набор ⊆ провайдерского» (расширять нельзя) и load-тест на вычитание.
|
||||
3. **Разные имена ключей для двух сторон** (панель права: один ключ конфлатит «свойство контента» и «разрешение получателя»): книга — `content_labels:`, провайдер/модель — `accepts_labels:`. Инвариант читается `content_labels(книга) ⊆ accepts_labels(модель)`.
|
||||
4. **Go-имя отставного ключа — без слова adult.** Буква D39.25 («ни один Go-идентификатор … не именуется adult/18+/каналом B») требует: поле `LegacyContentFlag bool` с ТЕГАМИ `yaml:"adult"`/`json:"adult"` (тег = замороженный ключ данных, идентификатор — generic). То же для канона.
|
||||
5. **Две РАЗНЫЕ семантики отставки — намеренно, с причиной в коде.** `permissive` в `models.yaml` — громко ПРИ НАЛИЧИИ (один файл репо, пак его и миграцирует). `adult:` в book.yaml — громко только на `true`, `false`/отсутствие терпимо: ключ несут **13 файлов вне git** (проверено мной: 5 book.yaml стенда + 8 `exp15/anchors/*.yaml`) + `example/book.yaml:9`. Тело называло 5 — **недоучёт, исправлен**; и он усиливает вывод: терпимость обязательна, а не удобна. Обязателен `LoadBook`-тест на все три формы (нет / `false` / `true`), потому что golden-фикстура ключа не несёт вовсе и ветку терпимости не упражняет.
|
||||
6. **Ассерт: место оставляю, тест исправляю.** Панель требовала перенести ассерт в самое начало `runAttempt` (до `GetCheckpoint`). **Отклоняю:** реплей чекпойнта провайдеру ничего не отправляет (ToS-экспозиции нет), а перенос отнял бы $0-реплей уже оплаченной работы при правке данных, которую загрузка и так ловит громко. **Принимаю тестовую часть:** негативный тест обязан гонять СВЕЖИЙ вызов (без чекпойнта) и ассертить идентичность ОШИБКИ, а не дельту `spend` — иначе он зелен вакуумно (на реплее `spend` тоже не двигается).
|
||||
7. **Второй путь на провод существует в дереве — в тестах.** `BuildClient` экспортирован (`clients.go:15`) и `live_conformance_test.go:70,76` вызывает `Complete` против БОЕВОГО `configs/models.yaml`. Мой манифест M3 с `grep -v _test.go` структурно слеп к нему. Фиксы: (а) механический гард-тест — `grep` по `\.Complete(` вне `internal/llm` и вне `stagerun.go` обязан быть ПУСТ, включая тесты; (б) требовать лейблы в подписи сема (`clientFor`/`BuildClient`), чтобы новый путь физически не мог не объявить контент.
|
||||
8. **Мёртвый failover — решить, а не «отметить».** `failover.go:114,130,148` пересылают ТОТ ЖЕ `req` другому клиенту, а собственная шапка файла (`:19-24`) описывает именно канало-B-опасность. Ассерт на строке модели декоратор не покрывает. Варианты: удалить файл в этом паке ЛИБО потребовать набор лейблов в конструкторе (компиль-энфорс). Второе — прямая разрядка ратифицированного D4 п.1 («изоляцию канала B делать ТИПОМ … а не комментарием»). Рекомендую второе; удаление — санкционированная альтернатива.
|
||||
9. **Честность границ гарантии (в D-лог, не в код):** механизм не утверждает ничего про НЕОБЪЯВЛЕННЫЙ контент (лейблы — данные владельца) и про eval-сторону (`eval/*.py` зовут провайдеров сами, вне движка). Инвариант формулируется как «egress прогонов ДВИЖКА».
|
||||
10. **Тест-гард на шиппинг-конфиги расширить**: существующие `LoadPipeline`-тесты шиппинг-конфигов (§14.2) дополнить лейблованным сценарием, иначе лейбл-ветка валидации остаётся непокрытой на реальных данных.
|
||||
|
||||
## §14.6. Скоуп: что УХОДИТ из пака-17 (пинг оркестратору — норма 5, не тихая девиация)
|
||||
|
||||
1. **§13 п.6 «писать в `escalation_model` ОТВЕТИВШУЮ модель» — ВЫНОСИТСЯ.** Проверено: golden пинит `esc_model="fake-fallback"` на хопе, который ОТКАЗАЛ (`capture.golden:24,59,151,186`; мок отказывает и на праймари, и на хопе — `golden_test.go:87-89`). Смена семантики даёт `esc_model=""` на 4 строках → `TestGoldenDeterminism` красный, и маскировка это не скрывает (`maskGoldenLines` маскирует только payload-строки и ≥12-hex, `golden_test.go:366-381`) — то есть это ВЕРДИКТ-изменение, требующее санкционированного пере-капчера. Держу отдельным ратифицируемым изменением; в паке-17 семантика остаётся как есть.
|
||||
2. **§13 п.10 (два нита общности из петель: Detail-строки чекеров из данных · множитель ×2 `lintTimeUnits` в данные) — УСЛОВНО.** Множитель живёт в чекере (`checks/checkers.go:174` `expectedHours := n * 2`), который версионируется `CheapGateVersion` (`cheapgates.go:68`), а тот фолдится в оба волновых снапшота **безусловно, без omitempty** (`snapshot.go:399,432`; в фикстуре `capture.golden:3`). Плюс если данные лечь в langpack — двигается `pack.Version()` (`lang/langpack.go:159-162,218`), а его фолд (`langpack_version`) присутствует у СТЕНДОВЫХ книг (они объявляют `langpack_root`) ⇒ `--resnapshot` и потеря $0-резюма драфта, на котором поедет замер остатка пака-16. **Условие включения в пак:** нит берётся только в БАЙТ-НЕЙТРАЛЬНОЙ форме (данные в пар-конфиге + фолд ТОЛЬКО при отличии от дефолта, дисциплина указателя/omitempty) и с доказательством ИСПОЛНЕНИЕМ (выход чекеров байт-идентичен до/после над корпусом). Иначе — отложить. Молча «bump-нуть версию» нельзя: это пере-оплата всех книг; молча НЕ bump-нуть тоже нельзя: это правка версионируемого правила без сигнала.
|
||||
3. **Открытая петля не пака-17 (записать, чтобы не потерялась):** ратифицированный D20.2-Q2 механизм согласия на пере-оплату — флаг `--accept-rebill[=usd]` с порогом `min($0.50, 5%×ProjectedBookUSD)` (`05-decisions-log.md:245`) — **в коде отсутствует** (`grep -rn "accept-rebill\|AcceptRebill" backend/` пусто); `--resnapshot` пере-пиннит без суммы (`stagerun.go:52-61`).
|
||||
4. **D22.7 — восстановить формулировку.** Тело дважды называет `action: terminal` «generic-механизмом под политику входа D22.7». Точнее: D22.7 — пер-чанковый ingest-скрин с человеческим ack и redrive только по явному ack (`05-decisions-log.md:276`), а `terminal` — book-level отказ ВХОДА, механизм СЛАБЕЕ. И главное: там же ратифицировано «ОБЯЗАТЕЛЕН до первой erotica-книги в проде» — этот предусловие пак-17 НЕ снимает и обязано стоять отдельной строкой гейта, иначе оптовая ратификация пака прочитается как разрешение на первую erotica-книгу.
|
||||
|
||||
## §14.7. Итоговый вердикт по дизайну (мой, после панели)
|
||||
|
||||
**Механизм данных (B1) и денежный кейстоун (M1) выстояли** — все пять дайджестов ре-деривированы тремя независимыми путями. **Назначение слоя снапшота (B5) и формулировка энфорсмента (B4/B6-рубеж-1) переписаны** (§14.1–14.2): резолв до валидации, «достижимое» лейбл-зависимо, лейблы вне brief-канона. **Скоуп B3 сокращён** до одного хопа из цепочки (§14.3), два хвоста вынесены/условны (§14.6). **Q1 остаётся «книга», но по аргументу гарантии, а не данных** (§14.4). Вопросы владельцу §9 остаются в силе; Q4 моя рекомендация теперь B3-A′.
|
||||
|
||||
## §14.8. Четыре добора из последней линзы (все проверены мной по коду)
|
||||
|
||||
1. **`Stage.Channel` + его enum + блок D4.1 обязаны получить диспозицию В ТОМ ЖЕ диффе, что `permissive` (HIGH).** Тело §13 распорядилось тремя точками из шести (Ф1) — `permissive`, `adult:`, `CheckAdultChannel` — а `Stage.Channel` (`pipeline.go:166`), его enum (`:549-553`) и D4.1-изоляция (`:569-576`) остались без судьбы. Опасность конкретна: после отставки `permissive` `providerPermissive` (`models.go:271-276`) возвращает false ВСЕГДА, и любой выживший `channel: adult` (в репо и на стенде их нет — проверено M7 + мой греп стендовых `pipeline*.yaml`, но конфиги стенда вне git) упал бы с требованием ключа, который сам теперь падает громко = операторский тупик. Фикс: отставить `Channel` (громкий retired-ключ, называющий `content_labels`), удалить enum и блок D4.1 тем же диффом; и внести в §13.9 **переписывание/удаление двух существующих adult-тестов** (`config_test.go:37-64`, `runner_test.go:1552-1572`) — иначе они остаются семантически ложными.
|
||||
2. **Убрать `prohibited` из примера реестра (HIGH).** Значения `adult`/`prohibited` в моём примере (§3) воспроизводят два из ТРЁХ ратифицированных выходов будущего L3-классификатора (`05-decisions-log.md:310`, D25 п.4: «тройной выход `adult/prohibited/uncertain`, terminal uncertainty, human-confirm»), а мой набор действий (`route|terminal`) не выражает ни `uncertain`, ни ack-семантику. Это приглашение прочитать пак как «мы умеем детектить L3». Фикс: в примере — только `route` на синтетическом значении; отдельной строкой записать, что будущий L3-скрин подключается НАЗВАННЫМ пер-лейбловым хуком политики (набор действий открыт), а НЕ через `terminal`, и что пер-чанковые вердикты классификатора не могут жить в книжном поле (после §14.1 это уже структурно так: лейблы не в brief-каноне).
|
||||
3. **Load-time отказы не должны убивать $0 read-only контракт D20.4 (HIGH).** `CheckAdultChannel` стоит в БЕЗУСЛОВНОЙ части `openRunner` (`runner.go:147-152`), а `CheckKeys` — за `forWrite` (`:153-157`), потому что `status`/`report`/`export` делают 0 вызовов. Если новые отказы (терминальный лейбл, недостача способности) сядут безусловно, то книга, помеченная после оплаты, перестанет быть даже ЧИТАЕМОЙ — уже оплаченную работу нельзя ни осмотреть, ни выгрузить, а единственный выход — снять лейбл, то есть тихий обход. Фикс: **резолв маршрутизации — всегда** (read-путь пере-считывает снапшот, `export.go:274`), а ОТКАЗЫ — за `forWrite`; на read-пути то же самое становится громким предупреждением + флагом в шапке отчёта (B6.1).
|
||||
4. **Поправка к Ф5/M6 (MED, моя ошибка):** `chains.adult` несут **5** конфигов, не 4, и пятый указывает на НЕспособного провайдера: `configs/pipeline-c2.yaml:66` `adult: [kimi-k2.6]` (kimi без способности) — при этом сам файл помечен «⚠️ ИНТЕРИМ-ЗАГЛУШКА, НЕ контракт» (`:62`) и НЕ исполняем: `CheckRunnable` отвергает core C2 (`pipeline.go:307-312`). Для правила «len(chain)==1» это безразлично (длина 1 у обоих), для capability-инварианта — важно: проверка обязана применяться к цепочкам, выбранным политикой лейблов ИСПОЛНЯЕМОГО конфига, иначе C2-заглушка блокирует загрузку. Числа Ф5/M6 читать с этой поправкой.
|
||||
|
||||
**Дополнения к манифесту §12** (приёмка ре-ранит): `sed -n '245p' docs/architecture/05-decisions-log.md` (D20.2-Q1/Q2) · `sed -n '188p;227,228p' backend/docs/D15.2-content-addressed-resume-spec.md` (brief_hash задропан; прецеденты «не через brief_hash») · `grep -rln "^adult:" /home/ubuntu/books/ | wc -l` (13 файлов вне git) · `grep -rn "LoadPipeline(" --include='*.go' backend | wc -l` (23 сайта: 1 прод + 22 теста) · `grep -rn "\.Complete(" --include='*.go' backend | grep -v '/internal/llm/'` (второй сем — `live_conformance_test.go`) · `grep -n "escalate_to\|esc_model" backend/internal/pipeline/testdata/golden/capture.golden | head` (пин `esc_model` на отказавшем хопе) · `grep -rn "accept-rebill\|AcceptRebill" backend/` (пусто — петля §14.6.3) · `grep -rn "adult:" backend/configs/` (5 файлов с `chains.adult`, c2 → kimi) · `grep -rnE "^[[:space:]]*channel:" backend/configs /home/ubuntu/books/gu-zhenren/*/pipeline*.yaml` (пусто — `Stage.Channel` мёртв и в репо, и на стенде).
|
||||
|
||||
**СТОП.** Дизайн-фаза закрыта; кода не тронуто (`git status --porcelain` показывает ровно один новый файл — этот отчёт). Жду ратификации §9 (Q1–Q6, где Q4 теперь = B3-A′) и §14 (три отмены, два переворота, скоуп-выносы §14.6).
|
||||
Loading…
Add table
Reference in a new issue