textmachine/docs/BACKEND_PACK17_CHANNEL_B_SESSION_PROMPT.md

73 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Промт бэкенд-сессии: ПАК-17 «канал B (18+) вживую» (ДВУХФАЗНЫЙ: дизайн → ратификация → стройка)
**Статус: ФАЗА 1 ИСПОЛНЕНА И РАТИФИЦИРОВАНА (`D39.26`, 25.07) → ФАЗА 2 РАЗМЕЧЕНА И ОТКРЫТА.** Дизайн-отчёт `docs/archive/reports/PACK17_DESIGN_2026-07-25.md` принят **В РЕДАКЦИИ §14** (тело §013 читать ТОЛЬКО через §14; при расхождении побеждает §14) + ревью-шапка оркестратора. **Фаза 2 стартует по §«ФАЗА 2» ниже** — там ратифицированный «ровно так»-список; §«ФАЗА 1» оставлена как история задания.
**Статус фазы 1: ВЫДАН 25.07.2026, оркестратор №7.** Цель: 18+ контент (уровни 12) идёт через волновой стек по 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`.
## Инварианты — «РОВНО ТАК» (ратифицировано; конфликт с кодом/доками → пинг оркестратору через владельца, НЕ интерпретация)
- **В ДВИЖКЕ НЕТ ПОНЯТИЯ «18+» (директива владельца 25.07 — архитектурная).** Движок несёт только GENERIC-механизм: **content-label** книги/глав (данные книги) × **capability провайдера** (данные models.yaml) × выбор цепочки (конфиг). «adult» — ЗНАЧЕНИЕ данных; ни один Go-идентификатор/ветка не именуется adult/18+/каналом B. Тот же механизм обязан нести будущие лейблы без правки Go — мысленный тест: archaic-register (D19.1-эхо grok на архаичном zh — та же фигура «свойство контента × способность модели»). Проактивный роутинг по лейблу — не оптимизация, а **ToS-гигиена**: провайдер без способности не должен ПОЛУЧИТЬ такой контент, независимо от того, откажет ли.
- **Refusal-обработка — GENERIC и причино-агностичная:** D12-таксономия детектит ФАКТ отказа (hard/soft), не причину; отказ по эротике/политике/чему угодно идёт ОДНИМ путём (регенерация → эскалация по цепочке лейбла → flag+skip, D2 — мусор не течёт в TM/экспорт). Причина отказа — только операторская диагностика. Параметризуется ПОЛИТИКА ЦЕПОЧКИ (конфиг), не код. Существующая таксономия не переписывается.
- **18+ уровень 3 — жёсткая линия:** НЕ обрабатывать, НЕ анализировать, классификатор L3 в этом паке НЕ строить (спека D22.7 ждёт решений владельца по валидации). `eval/data/refusal_corpus/*` НЕ открывать. L3 — политика ВХОДА (человеческий уровень), не механизм движка.
- **Live-вызовы с 18+ контентом — ТОЛЬКО по явной санкции владельца с его корпусом.** Пак строится и тестируется на mock-провайдере + СИНТЕТИЧЕСКИХ безобидных фикстурах (маркер-прокси, не реальная эротика); книга и производные вне git.
- **Capability-исключения — ДАННЫМИ:** **DeepSeek без adult-capability (ToS, стратревью 09.07)**, Anthropic без 18+ — строки в models.yaml, не Go-литералы имён; гарантия «label ⊄ capabilities → провайдер не получает контент» — компилируемая/тестируемая, fail-loud.
- **Байт-точность no-adult пути:** канал A (обычные книги) байт-идентичен — golden без пере-капчера; парити EXACT; adult-механика активируется только явной маркировкой.
- **Ключи:** xAI единый data-sharing, off перед продом (D27) — операционная петля владельца, в пак не тащить.
## Отправные точки в код (сверь САМ — код первичен, это entry-points)
`escalation.chains.adult` в шиппинг-конфигах и его валидация · channel-aware фильтр цепочек заявлен зоной раннера в комменте `pipeline-c1.yaml` (НЕ построен) · refusal-классы `internal/pipeline/disposition.go` + `internal/lang/data/refusal.txt` · провайдер-модель `internal/config/models.yaml`+`models.go` (есть ли слот под permissive-флаг — инвентаризуй) · волновой драйвер `waverun/stagerun` (где стадия выбирает модель/цепочку).
## ФАЗА 1 — ДИЗАЙН (кода не трогать; отчёт → СТОП → ратификация). Каждый пункт «реши сам и аргументируй»; советы = приоры, опровергаются аргументом/замером
- **B1. Схема content-labels и гранулярность.** Формат лейблов (данные книги: book.yaml/brief) и capabilities (models.yaml); книга / глава / чанк; кто маркирует в v1. *Приор: v1 = ручная маркировка владельцем на уровне книги или глав; значение v1 — одно («adult»), но СХЕМА generic (множество лейблов); ПОЛИТИКА лейбла — тоже generic: `route-to-chain` (adult → permissive-цепочка) ИЛИ `terminal` (не обрабатывать вовсе; будущее значение «prohibited» = данные, движок значения не знает); авто-детекция = НЕ этот пак (упирается в L3-спеку). Дизайн проверь мысленным тестом archaic-register: второй лейбл добавляется данными?*
- **B2. Маршрутизация ролей под лейблом.** Переводчик adult-контента = Mistral (D19 — ровно так). **Редактор — открытый вопрос:** dspro НЕЛЬЗЯ (без capability), кандидаты — mistral-large/glm/grok; предложи с аргументами (глоссарий/проза/цена; эмпирики нет — честно скажи, что выбор интерим до замера). Эскалация — цепочка лейбла (`chains.adult` уже в конфиге). Судья — eval-сторона, НЕ в паке (D22.6 проба-гейт у полигона).
- **B3. Refusal-политика цепочки (GENERIC).** Сегодня refusal → flag+skip; спроектируй per-chain политику: отказ ЛЮБОЙ причины → следующий хоп цепочки лейбла до потолка → flag+skip; потолки/бюджет; как ложится в существующую лестницу regenerate→escalate (семантику не менять молча). Никаких «18+ отказов» как отдельного класса.
- **B4. Механическая гарантия маршрутизации.** Enforcement «label ⊄ capability → не отправлять»: загрузка конфига (fail-loud на модели без способности в цепочке лейбла) + рантайм-ассерт перед вызовом. Тест обязателен обоим.
- **B5. Wire/снапшот/резюм.** Channel-маркировка фолдится в снапшот (какую волну двигает)? Чекпойнты/RequestHash adult-вызовов — штатно; no-adult путь байт-идентичен; фикстуры синтетические.
- **B6. Отчётность.** Канал в паспорте главы/quality-report/леджере (CostSource-атрибуция adult-вызовов) — что дёшево сейчас, что в очередь.
**Приёмка фазы 1:** отчёт `docs/archive/reports/PACK17_DESIGN_<дата>.md` — НАЧИНАЕТСЯ эхо-блоком (≤10 строк: скоуп/инварианты/не-делать); по каждому B: варианты, рекомендация, риски; адверсариальный селф-ревью (линзы: L3-линия/гардрейлы · маршрутизация-fail-loud · байт-точность канала A · деньги/резюм · общность §0). **СТОП — ратификация.**
## ФАЗА 2 — СТРОЙКА (ратифицировано `D39.26`; читай вместе с §14 отчёта — там file:line на каждый пункт)
**Читать перед работой:** §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` декларировать.
## НЕ делать
L3-классификатор · live 18+ вызовы без санкции · реальный 18+ текст в репо/фикстурах · копирование GPL/AGPL · пар/книго-литералы и провайдер-литералы в Go · расширение скоупа без пинга.
## Приёмка (мандат 12.07 + нормы 711)
Фаза 1 — СТОП после отчёта. Фаза 2 — отчёт `docs/archive/reports/PACK17_CHANNEL_B_REPORT_<дата>.md`; сессия НЕ коммитит; `git mv` декларировать.