28 KiB
ПАК-17, ФАЗА 2 — СТРОЙКА: generic content-labels × provider-capabilities (исполнено)
Ревью-шапка оркестратора №8 (25.07, приёмка EXECUTE-FIRST, инлайн; ПРИНЯТ и залендён —
D39.27). 0 блокеров. Каждый пункт манифеста ре-ранен мной: build/vet чисты ·-race12 пакетов ok · goldenf7641993…4f0020f8=HEAD-версия фикстуры байт-в-байт (git show HEAD:… | sha256sum),testdata/чист · парити EXACTn=13618 … recall 0.9655 (56/58)· ценз проверен ДИФФОМ ИМЁН: 433→451, удалены ровно 8 объявленных ·-shuffle=onзелёный · шов на провод один + именованный аллоулист. Рубеж-2 — три МОИХ мутации (не из списка сессии), все пойманы именованными тестами: лейблы обратно в канонBriefHash→ красныйTestEditorOnlyLabelLeavesTheDraftWaveUntouched(инвариант D39.26 п.1 запинён СЛЕДСТВИЕМ — снапшот драфт-волны — что сильнее ассерта хешей) · отключение лейбл-проверки вclientFor→ красныеTestLabelledRuntimeAssertPrecedesMoney/TestClientForRefusesUnknownModel· безусловный отказ маршрутизации → красныйTestLabelledReadPathStaysAlive. Подтверждено по коду: достижимость лейбл-зависима (ReachableModelsиз резолвнутых) ·r.client(больше не существует · порядокGetCheckpoint:337 → clientFor:374 → Reserve:390· добор C закрыт именованным тестом рейт-гардов · отчётность всяomitempty· шиппинг-конфиги без единой поведенческой правки (только комменты + 3 снятыхpermissive) · стенд чист (0 файлов сpermissive:/channel:вне git; все 5 книг ссылаются на репозиторныйmodels.yaml) ⇒ отставки операционку не ломают. Диспозиции пингов §3: форма добора B — ФИКСИРУЮ форму 2 (аргумент проechoMineViolation×control: effortпроверен; цена = консервативное пере-резервирование) · семантика хопа (§3.2) — ратифицирована как следствие D39.26 п.1 ·labelsFor()подписью — принято (сильнее рантайм-ассерта) · хвосты общности — «отложено-с-записью». Ответ владельца по вокабуляру: ПАРАviolence/sexually-explicit(сохраняет D14 п.1 и dspro-редактора) ⇒ дельта следующего касания: третье значениеaction— assert-only (routeтребует chain+бюджет+label_models,terminalне обрабатывает — чистого capability-ассерта в enum нет, а пара без него невыразима данными). Q2 (редактор) отложен с записью — связывает только explicit-книги, которых нет. Фикс-лист: стейл-комментStage.ReasoningMaxTokens(pipeline.go:189-194) про «ignored … reasoning-off».
Статус: ФАЗА 2 ИСПОЛНЕНА, СДАНА НА ПРИЁМКУ. Сессия НЕ коммитила (дерево: 25 modified + 2 new + 2 staged-deleted;
git mvне применялся — удаления объявлены в §7). База:D39.26+ §«ФАЗА 2» промтаdocs/BACKEND_PACK17_CHANNEL_B_SESSION_PROMPT.md+ дизайн-отчётPACK17_DESIGN_2026-07-25.mdв редакции §14. Инварианты фазы держатся все: golden байт-идентичен БЕЗ пере-капчера · парити EXACT ·-raceзелёный · сквозняки через настоящий драйвер · цензы исполнением.
§0. Эхо-блок (что делал)
- Механизм: книга объявляет
content_labels(данные) × провайдер/модель —accepts_labels(данные) × ран-конфиг — УПОРЯДОЧЕННЫЙ реестрcontent_policy; движок только сравнивает множества. - Порядок: РЕЗОЛВ маршрутизации → ВАЛИДАЦИЯ → все читатели (валидация, eager-клиенты, рейт-гарды, префлайт ключей, снапшот, рантайм) видят РЕЗОЛВНУТОЕ.
- Хеши: лейблы не входят ни в один; их wire-судьба = резолвнутая модель стадии, пер-стадийно и пер-волново. Канон brief ЗАМОРОЖЕН.
- Эскалация под лейблом: ОДИН хоп =
chain[0],len>1— громко;budget_usd>0обязателен;escMuне тронут. - Гарантия: каждая достижимая модель несёт все лейблы книги — рубеж 1 (загрузка, за
forWrite) + рубеж 2 (рантайм-ассерт вrunAttemptДОReserve, черезclientFor(model, labels)). - Отставки одним диффом:
permissive·adult: true·Stage.Channel+enum+блок D4.1 ·CheckAdultChannel;failover.goудалён. - Данные владельца НЕ прибиты: вокабуляр лейблов и редактор под лейблом ждут ответа — fail-closed до него, ответ = строка данных.
- Не делал: L3 · live 18+ · реальный 18+ текст · мульти-хоп · авто-детекция · судья · вынесенные хвосты.
§1. Что построено (файл → что изменилось)
Данные (конфиг-слой)
internal/config/book.go—content_labels: []string(trim+lower+dedupe+sort на загрузке); отставнойadult:какLegacyContentFlag(громко только наtrue,false/отсутствие терпимо — ключ несут 13 файлов вне git); канонBriefHashзаморожен: 7-е поле рендерится константойretiredContentSlot=false, лейблов в каноне НЕТ.internal/config/models.go—accepts_labels *[]stringна провайдере и модели (nil = наследовать, указатель на пустой = «ни одного»); модельный набор только СУЖАЕТ (молчащий провайдер = «ничего», иначе одна строка на модели выдала бы разрешение, которого у эндпойнта нет); отставнойpermissive(громко при ЛЮБОМ значении);AcceptsLabels/MissingLabels/ProviderOf;ThinksOnWire(резолвнутая wire-форма);canonicalLabel— каждое АВТОРСКОЕ написание лейбла обязано быть каноническим (иначе fail-closed молча).internal/config/pipeline.go—content_policy []LabelPolicy(action: route|terminal,chain),Stage.label_models, резолвнутыеResolvedModel/ResolvedHop(yaml:"-"), отставнойchannel:;resolveContentRouting+checkContentRouting+ReachableModels(ЕДИНОЕ определение достижимости);ContentRoutingError(книго-зависимые отказы, применяются по пути).configs/models.yaml—permissive: true×3 УБРАН; вместо значений — комментарий, почему строка пуста (вокабуляр = решение владельца, D39.26 добор A) с двумя формами ответа.configs/pipeline-c1.yaml— закомментированный пример реестра на СИНТЕТИЧЕСКОМ значении + требования загрузки; шапка/цепочки/редактор-комменты переписаны.
Движок
runner.go—labelsFor()(книго-константа),clientFor(model, labels)(компиль-энфорс: клиента нельзя получить, не объявив контент),reachableModelsделегирует в конфиг, отказы заforWrite+ предупреждение на read-пути,sourceEchoExposure(эхо-половина добора B).stagerun.go— праймари =ResolvedModel; клиент резолвится ДОReserve(ассерт срабатывает раньше денег; ветка релиза резервации на этой ошибке больше не нужна).escalation.go— хоп =ResolvedHop(для книги без лейблов это ровноescalate_to).snapshot.go—stageSnap.Modelи хоп-фолд берут резолвнутые модели; ни одного нового поля (существующиеEscalate*сохранили имя/тег/позицию/правило заполнения).resume.go/waverun.go— атрибуция резолвнутой моделью.internal/store/ledger.go—EscalationHops+SpendByModel(read-only агрегаты, БЕЗ миграции).status.go/quality.go/cmd/tmctl/render.go— провенанс лейблов и денег (всёomitempty: книга без лейблов байт-идентична прошлому).- Удалено:
internal/llm/failover.go+failover_test.go.
§2. Три решения, которые я принимал сам (и аргументы)
(1) Форма закрытия добора B — гейт НЕЗАВИСИМО от off (вторая из двух ратифицированных форм). Первая форма (echoes_when_thinking_off: true у xai) требует снять у grok off-выключатель, иначе models.yaml вообще не грузится (echoMineViolation бьёт по control: effort), — а этот выключатель запинён инвариантом с ДЕНЕЖНЫМ обоснованием (echo_mine_test.go: без него grok думает по дефолту, additive). То есть форма 1 = отдать за эхо-гарантию деньги и сломать пин, чего сессия решать не вправе. Поэтому: аддитивный гейт больше не смотрит ни на строку reasoning, ни на провод — буфер обязан объявить ЛЮБАЯ стадия на аддитивном провайдере, и AdditiveReasoningTokens резервирует его так же (гейт и резервация не могут разойтись по одному вызову). Подавленный вызов лишь ПЕРЕ-резервирует: потолок ужимается, слепым не становится. Эхо-ось при этом тоже перестала молчать, но НЕ гейтом: sourceEchoExposure громко предупреждает, когда стадия отправляет ИСХОДНИК модели, чей провод гасит thinking (generic: без имён провайдеров и лейблов; на шиппинг-пути не стреляет, на glm-арме стреляет — ровно та неизмеренная экспозиция, которую отрицал устаревший коммент D30.1).
(2) Где живут резолвнутые поля и как лейблы доезжают до runAttempt. Поля — на стадии, yaml:"-" (конвенция PromptPath); лейблы приходят в LoadPipeline тем же швом, что pair (доктрина «все проблемы конфига одним списком»), а до провода едут ПАРАМЕТРОМ clientFor(model, labels) — единственный способ получить клиента. Ключевание ассерта на данных книги/чанка, а не на стадии, обязательно: ремонтный путь строит СИНТЕТИЧЕСКУЮ стадию и стадийное поле потерял бы.
(3) Объём B6. Шапка status/report (лейблы + таблица маршрутизации), EscalationHops (булев escalated хопы не считает), SpendByModel (GROUP BY model_requested). Ни миграций, ни синтетической роли (Role — ось RequestHash: новая роль = новый ПЛАТНЫЙ вызов).
§3. Отклонения и пинги оркестратору (норма 5 — не тихие девиации)
- Добор B закрыт ВТОРОЙ формой, не первой (§2.1). Цена: любая стадия на аддитивном провайдере теперь обязана объявить
reasoning_max_tokensдаже приoff— одна строка конфига, громко на загрузке. Пере-резервирование консервативно. Прошу зафиксировать выбор формы явно. - Семантика хопа под лейблом уточнена: цепочка лейбла подменяет
escalate_toТОЛЬКО на стадии, которую лейбл реально пере-маршрутизирует (label_models). Причина — ратифицированная D39.26 п.1: безусловная подмена двигала ДРАФТ-волновой снапшот (в нём фолдитсяescalate_to) при лейбле, который маршрутизирует только редактора, то есть пере-оплачивала оплаченную волну. Стадия, которую лейбл не трогает, сохраняет свой фолбэк — и если тот не вправе получить контент, прогон падает по ИМЕНИ модели, а не молча подменяет её. ТестTestEditorOnlyLabelLeavesTheDraftWaveUntouchedпинит именно ратифицированное обещание. - Хвосты общности (Detail-строки чекеров · множитель ×2) — ОТЛОЖЕНЫ, как позволял промт. Причина проверена: множитель живёт в чекере, версионируемом
CheapGateVersion, а тот фолдится в оба волновых снапшота безусловно; данные в langpack двигаютpack.Version(), который у СТЕНДОВЫХ книг присутствует ⇒--resnapshotи потеря $0-резюма драфта под замер остатка пака-16. Байт-нейтральной формы (пар-конфиг + фолд только при отличии от дефолта) в этом паке не строил — это отдельная работа с доказательством исполнением. labelsFor()«ассерт книго-константы» реализован ПОДПИСЬЮ, а не рантайм-проверкой: функция не принимает чанк, поэтому пер-главная маршрутизация физически невыразима; в комменте названо, что для неё пришлось бы снять фолд модели с пер-волнового снапшота. Если владелец хотел исполняемый ассерт — скажите, добавлю.internal/llm/llm.goне отформатированgofmt— это состояние HEAD, не моё (проверено:git show HEAD:… | gofmt -l). Не правил, чтобы не носить в диффе чужой шум.
§4. Ревью ИСПОЛНЕНИЕМ своего кода (мандат 12.07) — и что оно нашло
Прогнал 6 адверсариальных ревьюеров ПО ИСПОЛНЕНИЮ (компliance по 15 пунктам · байт-идентичность · гарантия маршрутизации · деньги · общность · честность тестов), с мутациями в копии дерева. Нашли 8 существенных дефектов; все исправлены, каждое исправление проверено мутацией (мутация возвращает дефект → тест краснеет):
| # | Найдено | Исправление | Мутация-доказательство |
|---|---|---|---|
| 1 | CRIT (тесты). Лейбл-фикстура была ТОЖДЕСТВЕННЫМ отображением (label-модель = конфиговая), поэтому откат ResolvedModel/ResolvedHop в stagerun/escalation не краснел — несущая половина пака была не покрыта |
фикстура маршрутизирует в ДРУГИЕ слаги (fake-label/fake-labelhop); ассерты на ТЕЛО запроса (мок канонизирует model в ответе, поэтому modelActual свидетелем быть не может) |
st.ResolvedModel→st.Model: 2 красных · ResolvedHop→EscalateTo: 3 красных |
| 2 | HIGH (D39.26 п.1). Безусловная подмена хопа двигала ДРАФТ-снапшот при лейбле «только редактор» ⇒ пере-оплата оплаченной волны | подмена только на пере-маршрутизированных стадиях (§3.2) | восстановление безусловной подмены → TestEditorOnlyLabelLeavesTheDraftWaveUntouched красный |
| 3 | HIGH (гарантия). Модель могла ВЫДАТЬ лейбл, которого нет у провайдера, если у провайдера набор не объявлен (nil) — а сегодня он не объявлен ни у одного | nil провайдера = «ничего»; правило «модельный ⊆ провайдерского» безусловно | && prov.AcceptsLabels != nil → TestAcceptsLabelsNarrowingAndWidening красный |
| 4 | HIGH (снапшот). Откат хоп-слота фолда не краснел | ассерт "escalate_to":"fake-labelhop" в payload |
ss.EscalateTo = st.EscalateTo → красный |
| 5 | MED. Два активных route-лейбла с РАЗНЫМИ головами цепочек — вторая никогда не выстрелит, тихое усечение | упорядоченное объединение голов + дедуп + громко при >1 | отключение проверки → TestContentRoutingRefusals красный |
| 6 | MED. Гард шва .Complete( не видел cmd/ и зависел от ИМЕНИ переменной ctx; исключал себя по basename |
корень = модуль, regexp \.Complete\(, needle собран из частей, stagerun.go исключён по ПУТИ пакета |
(позитивный контроль ревьюера: файл-обход ловится) |
| 7 | MED. EscalationHops/SpendByModel и провенанс в StatusReport — ноль тестов |
TestRoutingMoneyProvenanceIsDerived + ассерты в read-path-тесте |
обнуление агрегата: 8 красных · снятие блока провенанса: 1 красный |
| 8 | MED. Написание лейбла: книга нормализуется, а accepts_labels/политика/label_models сравнивались побайтно ⇒ fail-closed, но с враждебной диагностикой; плюс порядок сообщений зависел от итерации map |
canonicalLabel для всех авторских мест + сортировка ключей |
снятие проверки → TestLabelSpellingIsCanonicalEverywhere красный |
Плюс мелкое, тоже исправлено: тавтологический errors.Is(nil, …) удалён · basePipeline разведён (голова цепочки ≠ escalate_to, иначе «подменяет» непроверяемо) · переформулирована завышенная проза в TestShippingConfigsUnderLabels · os.Unsetenv→t.Setenv · тест перестал пинить ДАННОЕ значение chains.adult (ответ владельца по вокабуляру ломал бы CI) · добавлен TestNoPrivilegedLabelValue (тривью на «движок выучил значение»: книга с adult против реестра, который знает только синтетический лейбл, обязана быть отвергнута — привилегированное значение делает тест красным) · сняты 4 устаревших против D30.1 коммента «вход редактора — русский черновик» (репо + арм-конфиги) · retiredAdultSlot→retiredContentSlot, канон-поле Adult→RetiredSlot (тег json:"adult" заморожен — байт-нейтрально).
Что ревью НЕ опровергло: байт-идентичность (все четыре кейстоуна ре-деривированы независимо), единственность шва .Complete( в проде, отсутствие вокабуляра/редактора в конфигах, все скоуп-выносы чисты.
§5. Манифест «заявление = команда» (приёмка ре-ранит; все цифры — из моего исполнения)
| # | Заявление | Команда | Результат |
|---|---|---|---|
| M1 | сборка и vet чисты | go build ./... && go vet ./... |
без вывода |
| M2 | вся сьюта зелёная под -race |
go test -race -count=1 ./... |
12 пакетов ok, 0 FAIL |
| M3 | golden БАЙТ-ИДЕНТИЧЕН без пере-капчера | sha256sum internal/pipeline/testdata/golden/capture.golden |
f7641993e37f7ec619dfc5d2914a342d796a7cecd92080e33704f3974f0020f8 — ТОТ ЖЕ, что baseline пака-16 |
| M4 | фикстуры golden не тронуты | git status --porcelain backend/internal/pipeline/testdata/ |
пусто (0 строк) |
| M5 | парити EXACT | TM_MINER_PARITY=1 go test ./internal/miner/ -run Parity -count=1 -v |
PARITY: n=13618 catastrophe{方源:0 蛊:1 蛊师:2 古月:22} recall@proposed=0.9655 (56/58 GT) — тождественно пакам 15/16 |
| M6 | ценз тестов ИСПОЛНЕНИЕМ: было 433, стало 451 (+26 добавлено, −8 удалено) | grep -rn "^func Test" backend | wc -l · git grep -c "^func Test" HEAD -- 'backend/**/*_test.go' (сумма) · diff списков имён |
451 / 433; удалены 6 failover + TestCheckAdultChannel + TestRunnerChannelBRequiresPermissive |
| M7 | новые тесты не зависят от порядка | go test -count=1 -shuffle=on -run 'Content|Label|Retired|Shipping|Accepts|Routing|Privileged|Egress|Echo' ./internal/config/ ./internal/pipeline/ |
ok / ok |
| M8 | в движке не осталось ветвления по типу контента | grep -rn "CheckAdultChannel|providerPermissive|Stage.Channel|\.Channel\b" --include='*.go' backend |
только отставные Legacy*-поля и их сообщения |
| M9 | вокабуляр и редактор НЕ прибиты | grep -rnE "^\s*(accepts_labels|content_policy)" backend/configs/ |
ни одной раскомментированной строки |
| M10 | шов на провод один | grep -rn "\.Complete(" --include='*.go' backend | grep -v '/llm/' |
stagerun.go + live_conformance_test.go (именованный аллоулист) + сам гард-тест |
| M11 | мутации возвращают дефекты (ревью §4) | копия дерева в /tmp, 9 мутаций, go test ./internal/config/ ./internal/pipeline/ |
каждая даёт ≥1 красный (таблица §4) |
| M12 | сессия не коммитила | git status --porcelain | wc -l |
29 записей, HEAD не двигался |
§6. Открытые петли / на владельце
- Вокабуляр лейблов (единый
adultпротивviolence/sexually-explicit— D39.26 добор A): до ответаaccepts_labelsпусты, любая лейблованная книга падает громко, называя модель/провайдера/лейбл. Ответ = 1–3 строки данных вmodels.yaml+ запись реестра в ран-конфиге. - Редактор под лейблом (Q2): кандидаты только из способных; grok-4.3 интерим по аргументам §4 дизайна, но теперь ОБЯЗАН объявить
reasoning_max_tokens(следствие §2.1); glm-5 — одна строка данных ТОЛЬКО после сверки ToS/AUP Z.AI по первоисточнику. - Гранулярность = книга (Q1): лейблованная книга едет по цене permissive-тира целиком.
- D22.7 паком НЕ снят: пер-чанковый L3-скрин остаётся предусловием первой erotica-книги в проде;
action: terminal— book-level отказ ВХОДА по объявленным данным, механизм СЛАБЕЕ, и значение под него не заведено. - Форма закрытия добора B — прошу явной фиксации (§3.1).
- Хвосты общности — отложены с причиной (§3.3).
--accept-rebill(D20.2-Q2) по-прежнему отсутствует в коде — петля не пака-17.
§7. Удаления и переносы (объявляю)
git rm backend/internal/llm/failover.go backend/internal/llm/failover_test.go(ратифицировано D39.26 п.10: вызывающих не было, файл пересылал ТОТ ЖЕ запрос другому провайдеру — ровно то, что лейбл запрещает; nil-panic-дыра D4 п.1). Удалённые тесты объявлены в цензе M6.git mvне применялся.- Новые файлы:
backend/internal/config/content_labels_test.go,backend/internal/pipeline/content_routing_test.go.