73 lines
24 KiB
Markdown
73 lines
24 KiB
Markdown
# Промт бэкенд-сессии: ПАК-17 «канал B (18+) вживую» (ДВУХФАЗНЫЙ: дизайн → ратификация → стройка)
|
||
|
||
**Статус: ФАЗА 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`.
|
||
|
||
## Инварианты — «РОВНО ТАК» (ратифицировано; конфликт с кодом/доками → пинг оркестратору через владельца, НЕ интерпретация)
|
||
|
||
- **В ДВИЖКЕ НЕТ ПОНЯТИЯ «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 + нормы 7–11)
|
||
|
||
Фаза 1 — СТОП после отчёта. Фаза 2 — отчёт `docs/archive/reports/PACK17_CHANNEL_B_REPORT_<дата>.md`; сессия НЕ коммитит; `git mv` декларировать.
|