Ratify pack nineteen design as D39.55 with corrections: bank record types with conditional fold, reveal scope cut to a leak flagger, nine build invariants from verification probes

This commit is contained in:
Claude (backend session) 2026-07-30 23:14:27 +03:00
parent e8846fbb31
commit 53a1ddcacf
3 changed files with 263 additions and 0 deletions

View file

@ -99,6 +99,10 @@
| 69 | Gemini API «под-18» обязательство на конечный продукт (независимо от лейблов) | владелец | когда-нибудь | Ф3 / отдельное решение | D39.32, D39.34(7) |
| 70 | Action-security gate перед выдачей tools/webfetch (D25 п.5) | бэкенд | когда-нибудь | Ф3 | D39.34(7) |
| 71 | Планы research/22: epub-tag-rewrite · Q7-леджер | бэкенд | когда-нибудь | Ф3 | D39.34(7) |
## Оркестратор №9 — ДИЗАЙН ПАКА-19 РАТИФИЦИРОВАН (D39.55), стройка ждёт «идём» владельца, 30.07
Фаза 1 принята: 82 клейма — 71 CONFIRMED / 9 PARTIAL / 2 REFUTED (полнота, не факты). Ратифицировано: типы записей банка + условный фолд в `memory_version` · CONFIRMED-only голос/обращения · свой гейт `gates.voice` без фолда · reveal сокращён до флаггера утечки (амендмент D21 п.3) · слот инъекции не выбран (амендмент D21 п.1г, решит полигон) · D21 п.8 model-bound. Девять новых «ровно так» фазы 2 — в D39.55. Отчёт: `archive/reports/PACK19_DESIGN_2026-07-30.md`.
## Оркестратор №9 + бэкенд — МАЛАЯ ПАЧКА 14а+14б ПРИНЯТА И ЗАЛЕНДЕНА (D39.54), 30.07
Бэкенд-сессия (та же, что несёт пак-19) закрыла обе строки до дизайна: ingest грязных епабов на `x/net/html` (4 класса жёстких падений импортируются; ТРИ регрессии — CDATA, self-closed raw-text, несбалансированный ruby — пойманы адверсариальным ревью сессии до лендинга и запинены байт-эталоном старого ридера, мутации 17/17) + вырезание промпт-комментариев с провода (SHA по канонической форме; 9/10 файлов байт-неподвижны; снапшоты книг не двигаются, терминолог перекупает батчи один раз — центы). Приёмка исполнением оркестратора: независимый дампер old-vs-new — 205 документов трёх епабов стенда, дифф глав и ruby ПУСТ; сьют `-race` 14/14; 4/4 спот-мутации красные; посылки записки перемерены (126 itemref, не 958 · 8/8 книг стенда txt · 2/252 сущности — независимым стеком). chunkerVersion НЕ поднят — ратифицировано D39.54 (экспозиция — пустое множество; строгая буква — одна строка за подписью владельца). Отчёт с ревью-шапкой: `archive/reports/SMALLPACK_INGEST_PROMPTS_2026-07-30.md`. Бэклог: 14а/14б сняты, добавлена 34а (`<meta charset>`-дыра, до-паковая). Пак-19 фаза 1 (дизайн) — следующий шаг той же сессии; жду `PACK19_DESIGN_*` и эхо-блок релеем.

View file

@ -1172,3 +1172,7 @@ API-529-долг закрыт: 8-осевой refute-by-default воркфлоу
## D39.54 — МАЛАЯ ПАЧКА 14а+14б ПРИНЯТА И ЗАЛЕНДЕНА ($0, +559/94): ingest грязных епабов на токенайзере x/net/html (4 класса жёстких падений теперь импортируются; ТРИ регрессии — CDATA, self-closed raw-text, несбалансированный ruby — пойманы адверсариальным ревью сессии ДО лендинга и запинены байт-эталоном старого ридера, мутационная таблица сессии 17/17) · комментарии промпт-файлов вырезаются С ПРОВОДА до сплита, SHA по канонической форме (9/10 файлов байт-неподвижны, снапшоты книг не двигаются, терминолог перекупает батчи один раз — центы) · chunkerVersion НЕ поднят РАТИФИЦИРОВАННО (30.07, оркестратор №9). ✅
**Приёмка исполнением на неподвижных копиях:** независимый дампер оркестратора old-vs-new по трём епабам стенда — 205 документов, дифф глав и ruby ПУСТ (3.29 МБ на сторону); сьют `-race -count=1` 14/14 зелёный, vet/gofmt чисты; 4/4 спот-мутации вне порядка сессии (SHA-по-сырому · нейтрализация вырезания · raw-text везде · flushRuby в no-op) красные, восстановление зелёное. Единственная ось сверх паритета — ЛУЧШЕ старого и вне провода: чтение ruby при пропущенном `</rp>` теперь захватывается (старый терял; ruby питает только сид глоссария, не `ch.Text`). **Поправки посылок записки (замер сессии, перемерено оркестратором):** Empire of the Dawn = 126 itemref, не 958; ingest переисполняется КАЖДЫЙ прогон (`bookrun.go:112`, `status.go:190`) — «существующие БД не тронуты» держится на другом факте: все 8 книг стенда txt, `extractXHTML` достижим только из `ingestEPUB`. **chunkerVersion — осознанная девиация от буквы дока (`render.go:29`):** остаточная экспозиция (епаб-БД с legacy-сущностью без `;` либо `&lang;`/`&rang;` — единственные 2/252 расходящиеся имени, кросс-чек независимым стеком WHATWG↔`xml.HTMLEntity`) есть ПУСТОЕ МНОЖЕСТВО и возникнуть не может — новые епаб-книги создаются уже новым кодом, а подъём двигал бы снапшот всех книг (`snapshot.go:414`) = полная переоплата за изменение, не касающееся их байтов; строгая буква — одна строка за подписью владельца. `mimetype zip.Store` — верность фикстуры OCF, НЕ фикс поведения (ingest файл не читает). Хвосты: фикс-лист (1) — комментарий поля `SHA256` в `render.go:89` остался «hash of the raw file», одна строка на ближайшее касание; новая строка бэклога 34а — экран `<meta charset>` без XML-объявления (до-паковая дыра, §8 отчёта). Отчёт с ревью-шапкой: `archive/reports/SMALLPACK_INGEST_PROMPTS_2026-07-30.md`.
## D39.55 — ДИЗАЙН ПАКА-19 «голос/состояние» РАТИФИЦИРОВАН С ПОПРАВКАМИ (фаза 1, $0, стройка после «идём» владельца): три сущности = ТИПЫ ЗАПИСЕЙ БАНКА с УСЛОВНЫМ фолдом в `memory_version` (наследование точечной ре-редактуры подтверждено исполнением) · голос/обращения CONFIRMED-only, порядок инъекции «обязывающее перед необязывающим» · флаггер = свой гейт `gates.voice` БЕЗ фолда (версия логируется с прогоном) · оси AC полноценно, ось D (регистр пары) отдельным счётчиком-индикатором · reveal-скоуп СОКРАЩЁН до флаггера спойлер-утечки без `reveal_ch` (амендмент ФОРМЫ D21 п.3, интент сохранён `spoilerBlocked`) · D21 п.1г амендирован: слот инъекции НЕ выбран, оба провода, решит полигон-эксперимент · D21 п.8 помечен model-bound (действующий порядок: драфт ~$0.04, редактор ~$0.070.10) (30.07, оркестратор №9). ✅
Приёмка: воркфлоу 7 верификаторов, 82 клейма = 71 CONFIRMED / 9 PARTIAL / 2 REFUTED (оба — полнота отчёта; поправки фактов — в ревью-шапке отчёта). Диспозиции пингов: (1) сокращение reveal ратифицировано; (2) `glossary.speech` → deprecated (журнал-строки вместо статической матрицы), `first_person` → переиспользуется полем профиля; (3) цифра D21 п.8 контрактно привязана к модели; (4) «драфт-слот дефолт» снят; (5) структурная неполнота оси D зафиксирована ДО фазы 2. **Новые «РОВНО ТАК» фазы 2 (из проб верификации):** (а) бюджет инъекции входит в денежный потолок/reserve — дозакрыть в отчёте стройки; (б) критик полноты в отчёте фазы 2 обязателен; (в) `Materialize` получает фильтр типа записи — строки голоса/обращений НЕ попадают в AC-матчер/глоссарную инъекцию как обычные строки (`memory.go:264-270` матчит всё с непустым Dst); (г) условный voice-тег копирует порядок «исключение скоупом ДО подсчёта условия» (`memory.go:352-357`); (д) `voiceSnap`/отдельный ключ снапшота — ЗАПРЕЩЁН (ломает `repin.go:72`); (е) `address_pair` хранится по стабильному ключу (src+sense), не по autoincrement `glossary.id`; (ж) sticky-carry, отклонённый окном, добавляется в `Selection.Rejected` (`memory.go:495-497` — сейчас молчаливый дроп); (з) ВКЛЮЧЕНИЕ инъекции = полный `--resnapshot` книги через consent-гейт, bank-only-класс на самом флипе не ожидается (omitempty защищает только добавление поля, не флип 0→300); (и) v1 переиспользует `GlossaryTokenBudget` в сигнатуре отбора — отдельный voice-бюджет потребовал бы зеркала в `repin.go:186`. Опционально фазе 2: линт «gap в окнах одного src» (сид грузится молча с дырой). Отчёт: `archive/reports/PACK19_DESIGN_2026-07-30.md` (ревью-шапка).

View file

@ -0,0 +1,255 @@
# ПАК-19 «голос и состояние» — ФАЗА 1 (дизайн). Отчёт бэкенд-сессии, 2026-07-30
**Статус:** дизайн, кода нет. Дерево чистое по моей зоне (проверено `git status`). Аддендумы получены: дельта D39.54 (пачка 14а+14б залендена; фикс-строка `pipeline/render.go` комментарий поля `SHA256` — «hash of the raw file» → каноническая форма — взята в фазу 2). **СТОП после этого отчёта.**
> **Ревью-шапка (оркестратор №9, 30.07): ДИЗАЙН ПРИНЯТ С ПОПРАВКАМИ, D39.55; стройка — после «идём» владельца.**
> Верификация (7 адверсариальных верификаторов, приёмка дизайн-класса): 82 клейма — 71 CONFIRMED / 9 PARTIAL /
> 2 REFUTED, фабрикаций ноль. Оба REFUTED — полнота, не факты: (1) «ровно так» п.7 промта (бюджет инъекции
> входит в денежный потолок/reserve) в отчёте не закрыт → дозакрыть абзацем в отчёте фазы 2; (2) критика
> полноты нет (5 линз §4 — другой мандат) → обязателен в отчёте стройки. Несущие клеймы (repin/фолды/мёртвые
> колонки/reveal-механика/цены) подтверждены исполнением. Поправки фактов: пин «две строки одного src» живёт
> в `memseed_test.go:125-133` и `:167-174`, а не в `:427-437` (там handoff ruby-чтения двух РАЗНЫХ src);
> «ru-формальность» — research/15 §1.5 (`:90`), не §2.1; «рычаг как авторитет» — глосса (в §1.2 дословно
> только «формат важнее наличия»; опора «в минус» — §3.2/T2); комментариев `verdictSnapshotID`/`guard_hash`
> семь, не четыре (вывод «0 кодовых вхождений» устоял). Слепые классы флаггера утечки: source-anchored
> (identity-twist без срабатывания ключа) и sticky-carry (`memory.go:495-497` дропает без `Rejected` — фикс
> одной строкой в фазу 2); скоуп флаггера ратифицирован как «утечка при СРАБОТАВШЕМ ключе». Диапазон
> «$0.0650.1» склеен из двух методов и недооценивает юниты (1.0/главу против 1.4 мини-прогона), но верх
> покрывает ($0.0910.098 < 0.1) принят как порядок величины. Новые «ровно так» фазы 2 в D39.55.
---
## 1. Эхо-блок
1. Фаза 1 = только дизайн; деливерабл — этот файл → СТОП → ратификация.
2. Строю безусловную половину D21: схема трёх сущностей · детерминированный ты/вы-флаггер · reveal-окна.
3. Инъекция проектируется, в прод не проводится (D21 п.2 + `store/migrate.go:349`).
4. ШАГ 0 первым: синк против новой реальности банка (двухсекционная инъекция, `ComputeVersionScoped`, `repin.go`, `TerminologyGate`).
5. Ни одного пар/языкового литерала в Go; `address_pair` = материализация журнала, не второе хранилище; voice-профиль заморожен per-job, пока не доказано обратное.
6. Любая ось, входящая в снапшот или в `CheapGateVersion`, двигает пере-оплату — называется отдельной строкой.
7. НЕ делаю: Annotator · слой-2 · ru-target долг · метрики пилота · масштаб · платные прогоны · коммиты.
8. Расхождение промта с кодом = пинг, не тихая интерпретация.
9. Право сказать «этого делать не надо» использую — см. §6.
10. Отчёт меряется плотностью: улики — `file:line`, всё неулико́ванное помечено «мнение».
---
## 2. ШАГ 0 — обязательный синк «источник ↔ код ↔ вердикт»
| # | Источник | Код | Вердикт |
|---|---|---|---|
| 1 | D21 п.1а: профиль ЗАМОРОЖЕН per-job **до лендинга D15.2** | `verdictSnapshotID`/`guard_hash`**0 вхождений** вне комментариев (`snapshot.go:90`, `cheapgates.go:45`, `sanitizer.go:89`, `migrate.go:361` — все «moves to … with D15.2»); этап А залендён (D33), Б/В сознательно не строились (D39.31, бэклог 49) | **совпадает; заморозка ОСТАЁТСЯ.** Предусловие не наступило — см. §3.V1 |
| 2 | D21 п.2: `address_pair` = материализация журнала ты/вы (D7-Правки-1) | журнала нет; есть статическая колонка `glossary.speech` (`migrate.go:184`), заполняется из сида (`memseed.go:96`), пишется/читается (`store/glossary.go:154,207) | **не построено** + находка Н1 |
| 3 | D21 п.1б: приоритет `CONFIRMED-глоссарий > address > voice > reveal` | инъекция редактора ДВУХСЕКЦИОННАЯ (`memory.go:659678`), порядок бюджета — `priorityRank` (`memory.go:564`) по осям confirmed/sticky/approved | **расходится с формулировкой** → пере-подтверждено в §3.V3 |
| 4 | D39.42 п.3: ВСЕ строки банка фолдятся в версию | `ComputeVersionScoped` (`memory.go:341374`) фолдит для каждой строки Status/Src/Dst/Sense/Type/Gender/Decl/Since/Until/AllowShort/Aliases | **совпадает** + находка Н2 (что НЕ фолдится) |
| 5 | D39.42 п.5: точечная ре-редактура по ключу | `classifySnapshotMove` (`repin.go:6191`): `moveBankOnly` ⟺ единственный различающийся ключ payload = `memory_version` (`repin.go:72`) | **совпадает** + находка Н3 (несущее следствие для схемы) |
| 6 | Прецедент «гейт-конфиг вне снапшота» | `TerminologyGate` (`config/pipeline.go:266270`) — не фолдится даже включённым, обоснование: производит ФАЙЛ, на провод попадает только через `mined_delta → memory_version` | **совпадает; прецедент применим к флаггеру** |
| 7 | D39.53: языковой экран в гейте, не в langpack | `config/pipeline.go:284292` — байты langpack двигают `LangpackVersion` (`snapshot.go:376`) ⇒ пере-покупка обеих волн всех книг пары | **совпадает; прямо применимо к T/V-данным** |
| 8 | D39.47: жанровый словарь отменён как класс | `lang/langpack.go:260264` — отсутствие зафиксировано решением | **совпадает; аргумент переносится на реестр обращений** (§3.V5) |
| 9 | README/D39: «новый Ф2-consumer role = данные + один рендерер» | реестр `roleInjectionRenderers` (`chunkrun.go:97`) есть; но `MessagesWithInjection` (`render.go:245`) принимает ОДНУ строку инъекции | **совпадает частично** → голос/обращения обязаны быть СЕКЦИЯМИ одной инъекции |
| 10 | research/15 §2.3: флаггеру нужна атрибуция речи | целевые данные `speech_verb`/`inner_marker` (`lang/data/target-ru.txt`, `checks/checkers.go:3844`) уже есть и уже используются | **частично построено** — целевая половина есть, исходной нет (§3.V1-бис) |
| 11 | Бэклог-15: гендер-аннотация НАРРАТИВНАЯ (since_ch-смены + перспектива) | `gender` — строка на строку банка + окно `since/until`; директива `genderConstraintNote` (`memory.go:718`) male/female/hidden | **частично**: смена по главам выражается двумя строками, «перспектива» — нет (§3.V2) |
| 12 | D21 п.3: reveal-окна + pre/post-алиасы | `spoilerBlocked` (`memory.go:533`) + непересекающиеся окна на одном src — **уже поддержаны и запинены** (`memseed.go:247` + `memseed_test.go:427437` «a spoiler handoff of the same reading»); у `glossary_aliases` (`migrate.go:206`) окна НЕТ, но оно и не нужно (алиасы наследуют окно строки) | **построено на ~90%** → §6, предложение сократить скоуп |
### Находки синка (4)
**Н1 — колонка `speech` и `first_person` мертвы, но живут в сиде.** `seed.Term.Speech`/`FirstPerson` (`seed/seed.go:30,33`) → `store.GlossaryEntry` (`memseed.go:9697`) → БД (`glossary.go:154`) → читаются обратно (`glossary.go:207`) — и **не потребляются ничем**: `grep Speech|FirstPerson` вне `store`/`seed` даёт 0 потребителей, поля нет даже в `entry` матчера (`memory.go:134155`). То есть D7-«series-bible-lite» есть как хранилище и отсутствует как механизм. Для пака-19 это не косметика: `first_person` — это ровно `self_ref` voice-профиля, а `speech` — ровно та статическая матрица, которую D21 п.2 объявляет superseded журналом. **Решение дизайна: не расширять `speech`, а объявить её deprecated и перенести смысл в типизированные строки (§3.V2); `first_person` — переиспользовать как поле профиля.**
**Н2 — что НЕ фолдится в `memory_version`.** Фолд-цикл (`memory.go:359373`) пишет 10 полей + алиасы. Вне фолда остались: `Speech`, `FirstPerson`, `TranslitPolicy`, `NicknameTranslation`, `Note`, `RubyReading/RubyClass/Confidence`, `Source` (используется как фильтр, не как байты). Пока они не на проводе — безвредно. **В момент, когда любое из них попадает в инъекцию, оно ОБЯЗАНО войти в фолд тем же диффом**, иначе воспроизводится ровно класс тихой пере-оплаты, закрытый D39.42 п.3 (`memory.go:342350`: правка меняет `content_hash` и resume, но не снапшот, а `projectRebill` проецирует по снапшоту ⇒ $0 в смете при реальной пере-оплате). Это несущий инвариант всего пака.
**Н3 — `repin` диктует ХРАНЕНИЕ, а не только цену.** `classifySnapshotMove` признаёт сдвиг re-pinnable, только если единственный изменившийся ключ payload — `memory_version`; любой новый ключ или любой другой изменившийся ключ = `moveOther` (`repin.go:7880,84`). ⇒ если голос/обращения получают СВОЙ компонент снапшота, то подпись voice-профиля перестаёт быть bank-only-сдвигом и стоит **всей редакторской волны** вместо затронутых юнитов — механизм D39.42 п.5 молча теряется для новой оси. Если же их содержимое входит в `memory_version`, точечная ре-редактура наследуется бесплатно и без единой правки `repin.go`. **Это решает V2.**
**Н4 — две ловушки безусловного фолда.** `RenderFormatVersion` (`snapshot.go:427`) и `ContextAssembly` (`snapshot.go:421425`, у полей `contextSnap` нет `omitempty`) фолдятся БЕЗУСЛОВНО. Наивная реализация — «бампнуть `RenderFormatVersion` под новый блок» и «добавить `voice_token_budget` в `contextSnap`» — пере-покупает **каждую книгу на свете**, включая те, у которых голоса нет. Тот же аргумент уже записан в коде (`memory.go:380384`). Обход существует и запинен прецедентами: условный тег внутри `ComputeVersionScoped` (`memory.go:385387`, `editor-unverified-section-v1`) и `omitempty` на нулевом значении.
---
## 3. Ответы по V1V6
### V1. Скоуп и порядок
**Рекомендация (порядок фазы 2):** (1) схема трёх сущностей + загрузка из сида + условный фолд; (2) reveal — только флаггер утечки, без новой схемы (§6); (3) детерминированный флаггер обращений/голоса как отдельный гейт. Инъекция — раздел §3.V3, кода за ним нет.
**Заморозка профиля per-job — снимать НЕ предлагаю, и это замер, а не осторожность.** Условие D21 п.1а — «до лендинга D15.2». В коде `verdictSnapshotID` и `guard_hash` не существуют (0 вхождений); четыре живых комментария (`snapshot.go:90`, `cheapgates.go:45`, `sanitizer.go:89`, `migrate.go:361`) описывают их как будущее. Пока их нет, mid-run append памяти двигает `memory_version` → снапшот → пере-оплата книги (D8/D15.1); ни одного «дешёвого» пути нет. **Аргумента за снятие у меня нет — приношу отсутствие аргумента, а не послабление.**
**Границу ратификации держу дословно:** строится схема + флаггер + reveal; инъекция проектируется. Основание — D21 п.2 (`05-decisions-log.md:254`) и `store/migrate.go:349` («gated on the pilot and on D15.2 landing»).
### V1-бис. Вход, которого пока нет — честный ответ
**Атрибуции прямой речи в движке НЕТ.** Что есть:
- целевая половина: `chevronSpeechShape` (`cheapgates.go:250`) + `isSpokenChevronLine` (`checkers.go:467`) отвечают «эта строка — произнесённая реплика», по данным `speech_verb`/`inner_marker`. Это **не** «кто говорит»;
- банк даёт множество персонажей, чьи имена сработали в чанке (AC-матчер);
- исходной половины нет вовсе: `grep 说|道|quote` по `internal/miner/` — 0 попаданий.
**Что флаггер НЕ поймает без Аннотатора** (числа — research/15 §2.3, не мои):
- имплицитные реплики без атрибуции — концентрат ошибок (en 7278% → ~53%);
- **адресата** — он на 1015 пп хуже спикера даже у LLM (70.379.9%), а явные кии его почти никогда не называют: 说 даёт говорящего, адресат приходит из 对X说 или вокатива;
- спикера-местоимения (他说) — нужна кореференция;
- чередование говорящих в многоходовом диалоге.
**Несущее следствие, которое меняет форму деливерабла.** Проверка «ты/вы против реестра ПАР» требует обоих концов пары. Один конец (спикер) детерминированно достижим, второй — нет. Поэтому безусловный флаггер пака-19 должен стоять на трёх осях, которым адресат не нужен, и на одной, которой нужен, — с раздельным учётом:
| Ось | Что нужно на входе | Достижимо сегодня |
|---|---|---|
| A. Внутреннее противоречие ты/вы в ОДНОЙ реплике | ничего, кроме границ реплики | да |
| B. Самоназвание против `self_ref` профиля | только спикер | да (явный кий) |
| C. Вхождение `ng_lexicon` спикера | только спикер | да |
| D. Регистр пары против реестра | спикер **и** адресат | только эвристикой «в сцене ровно двое» |
Оси AC — безусловный урожай пака. Ось D строится, но **отчитывается отдельным счётчиком с явной оговоркой точности** и не смешивается с AC в одном числе. Мнение: смешать их — это и есть «намерить сходимость сильнее данных»; счётчик D без Аннотатора честнее читать как индикатор, а не как метрику.
**Гендер-аннотация нарративная (бэклог-15).** Сегодня `gender` — одно значение на строку банка с одним окном. Смена по ходу сюжета выражается уже сейчас — двумя строками с непересекающимися окнами (запинено: `memseed_test.go:427437`). Чего НЕТ и что действительно требуется бэклогом-15 — **перспективы**: «пол скрыт от читателя, но известен персонажу X». Это второй ортогональный признак, и он не выражается ни окном, ни `gender`. Дизайн-ответ: перспектива — свойство READER-состояния, то есть та же ось, что reveal; её носитель — `reveal_ch` строки, а не новое поле пола. Директива `hidden` (`memory.go:718`, `GenderHidden`) уже реализует «избегать родовых форм до раскрытия» — то есть перспектива читателя уже закодирована, просто называется другим словом.
### V2. Схема и хранение
**Рекомендация: три сущности живут как ТИПЫ ЗАПИСЕЙ БАНКА, а их содержимое фолдится в `memory_version`.** Форма таблиц вторична (отдельные таблицы допустимы), фолд — нет.
Обоснование — Н3: единственный компонент снапшота, который `repin.go:72` умеет прощать, — `memory_version`. Собственный компонент снапшота = потеря точечной ре-редактуры для новой оси. Записка `migrate.go:355375` предполагает «likely a `voice_profiles` table» — таблица допустима, **но её строки обязаны идти в тот же хеш**; иначе подпись одного профиля стоит редакторской волны.
**Авторская поверхность — сид.** `seed.File` (`seed/seed.go:18`) — единственный артефакт, который и майнер пишет, и банк читает; voice/address курируются владельцем (D21 п.1д) и не майнятся вовсе. ⇒ две новые секции YAML рядом с `terms:`; контракт round-trip сохраняется.
**`address_pair` как журнал, а не второе хранилище.** Требование D21 п.2 выполняется без новой машинерии: строка `(speaker, addressee, register, since_ch, until_ch)` **уже есть журнальная запись**, а «состояние на главе N» — проекция ровно того же вида, что `spoilerBlocked` (`memory.go:533`). Переход ты↔вы = вторая строка с непересекающимся окном. Отдельная таблица «текущего состояния» не нужна и была бы вторым источником истины, запрещённым D21 п.2.
**Durable / производное / пере-оплачивается:**
| | Что | Где |
|---|---|---|
| Durable | сид (подпись владельца) → строки банка, идемпотентный REPLACE | файл + `glossary`-семейство |
| Производное | проекция регистра на главу, per-chunk отбор, счётчики флаггера | `retrieval_state`, пересчитывается каждый прогон (`waverun.go:390` — вызов безусловен) |
| Пере-оплачивается | ТОЛЬКО если инъекция включена — и тогда только юниты, чьи инъектированные байты реально изменились (`repin.go`) | — |
**Политика append в середине книги: запрещена (наследуется от D8/D15.1).** Профиль заморожен per-job; добавление строки между волнами = сдвиг `memory_version` = обычная громкая пере-оплата с согласием Р6 (`rebill.go`), а не тихий append.
**Условный фолд — механизм, не дисциплина.** Строки голоса/обращений входят в `ComputeVersionScoped` **только когда инъекция включена**, по образцу `hasUnverified` (`memory.go:385387`). Тогда: авторство профилей при выключенной инъекции — байт-идентичный хеш и $0; включение инъекции — громкий `--resnapshot` для этой книги; правка профиля при включённой инъекции — громкий сдвиг + точечная ре-редактура. Книга без профилей хешируется как раньше, побайтно.
### V3. Инъекция (проектируется; кода не будет)
**Форма.** Одна инъекция на роль (ограничение `render.go:245` — один аргумент-строка), секциями. Порядок — пере-подтверждение D21 против двухсекционной реальности D39.45:
```
1. КАНОН (CONFIRMED-глоссарий) — обязывающее
2. ОБРАЩЕНИЯ (регистр пары) — обязывающее
3. ГОЛОС (профиль спикера чанка) — обязывающее
4. ⟨проверить⟩ (неподписанный банк) — НЕ обязывающее, всегда последним
```
**Аргумент, а не умолчание.** D21 говорил «CONFIRMED-глоссарий > address > voice > reveal» до того, как глоссарий раскололся надвое. Правильный инвариант — не «после глоссария», а **«обязывающее перед необязывающим»**: заголовок `EditorUnverifiedHeader` (`lang/embedded.go:321325`) вводит «дальше — непроверенное», и всё, поставленное после него, наследует это чтение. Вставить обращения/голос между секциями — значит подвесить подписанный закон под непроверенную шапку.
**Идёт ли неподписанное голосовое в помеченную секцию — НЕТ, и это аргумент.** Вторая секция существует потому, что терминолог МАШИННО производит рендеринги в масштабе книги (D39.42) и выбрасывать их целиком расточительно. У voice-профиля в этом паке производителя нет вовсе: Аннотатор вне скоупа, exemplars заморожены, строки пишет владелец. ⇒ «неподписанный профиль» — категория без источника. Заводить её значит выдать модели необязывающую стилевую инструкцию, что хуже её отсутствия (research/15 §1.2: экземпляры — рычаг именно как авторитет; формат важнее наличия). **Голос и обращения — CONFIRMED-only; неподписанной строки этих типов не существует.**
**Позиция и кэш.** Инъекция — отдельное system-сообщение ПОСЛЕ кэш-границы (`render.go:249262`), то есть не кэшируется. Идея D21 п.1е «топ-23 героя в кэшируемый префикс» требует вложить блок в `msgs[0]` до границы, то есть сделать стабильный префикс зависимым от состояния книги. Считаю по прайсу: 300 ток./чанк × ~1000 чанков (500 глав × 2, каденс мини-прогона) = 300k входных токенов; в хвосте — 300k × $0.14/M = **$0.042**, в кэшируемом префиксе — × $0.0028/M = **$0.00084** (`models.yaml:149`). Экономия ≈ $0.04 на книге при COGS $0.85. **Рекомендация: не трогать префикс.** Мнение: 5% экономии не окупают превращение `msgs[0]` в книго-зависимый байт (окна профиля мигрируют — D21 п.1е сам это оговаривает).
**Бюджет.** Сегодня `glossary_token_budget: 800` (`pipeline-c1.yaml:26`) — общий на чанк. Новые оси получают собственные knobs в `context`, **обязательно с `omitempty` и нулевым значением по умолчанию** (Н4): у существующих полей `contextSnap` (`snapshot.go:2024`) `omitempty` нет, и добавление поля без него меняет payload каждой книги. Кап голоса ≤300 ток./чанк (D21 п.1б) принимается как есть.
**Пер-ролевая разница.** Реестр `roleInjectionRenderers` (`chunkrun.go:97`) уже позволяет развести: переводчику — голос (D21: голос выигрывается на драфте), редактору — обращения + канон. **Честно: посылка «голос выигрывается на драфте» СЕГОДНЯ НЕ ИЗМЕРЕНА** — баннер ревизии D31 в `research/15` (шапка файла) фиксирует, что P3 снималась на ПАССИВНОМ моно-grok-редакторе, который вернул черновик дословно 3/4; после флипа D1 редактор билингвальный и активный. ⇒ выбор слота — вопрос полигон-эксперимента, не дизайна; в отчёт кладу оба провода, а не рекомендацию.
**Что фолдится (и чего фолдить нельзя):**
- содержимое строк → внутрь `memory_version`, условно (V2);
- ФОРМАТ блока → **условный тег внутри `ComputeVersionScoped`, НЕ бамп `RenderFormatVersion`** (Н4: `snapshot.go:427` безусловен; книга без голоса не должна платить);
- флаг «инъекция включена» → он и есть условие тега; отдельного компонента не нужно.
Блок `context_assembly` получает новую ось только в виде `omitempty`-нулей — то есть присутствует, но байт-нейтрален до включения. Это и есть осознанный ответ на требование «новая ось обязана быть в нём или получить осознанный отказ».
### V4. Детерминированный детектор отклонения голоса (слой 1, $0)
**Где живёт:** `internal/checks` — это пакет чистых функций без стора и часов (шапка `checks/cheapgates.go:110`), новый флаггер туда садится без исключений.
**Как отчитывается:** счётчиками в `retrieval_state` через тот же путь, что стилевые флаггеры (`persistRetrievalState`, `chunkrun.go:106`), и агрегатом в `QualityReport` (`pipeline/quality.go:4146`) — отдельными полями с `omitempty`, чтобы книга без голоса сериализовалась байт-идентично (прецедент: `UnsignedBankTerms`, `quality.go:112114`).
**Версионирование — рекомендация: СВОЙ гейт `gates.voice`, своя версия-константа, НЕ фолдить.**
- Против включения в семью cheap-gates: `CheapGateVersion` (`cheapgates.go:94`) фолдится БЕЗУСЛОВНО (`snapshot.go:432`) ⇒ бамп = пере-оплата каждой книги каждой пары за механизм, которым она не пользуется. Это ровно то, что отказался делать пак-20 (`langpack.go:245247`).
- За «не фолдить вообще»: флаггер — observability, никогда не диспозиция (как все чекеры, `cheapgates.go:2426`), а его счётчики пересчитываются каждый прогон и самозалечиваются (`waverun.go:390``persistRetrievalState` вызывается безусловно, не только при свежем вызове). Прямой прецедент — `TerminologyGate` (`config/pipeline.go:266270`), которая не фолдится даже включённой и вместо этого логирует свою версию алгоритма с прогоном (`terminologist.go:5355`).
- **Принятая цена:** правка правила молча сдвигает записанные счётчики между прогонами под одним снапшотом. Смягчение — то же, что у терминолога: версия алгоритма логируется с прогоном и попадает в отчёт. Альтернатива (фолдить условно, как banknote — `snapshot.go:113130`) остаётся дешёвым апгрейдом, если владелец решит, что счётчики должны быть воспроизводимы под снапшотом.
**Слой 2 (эмбеддинги) — не в этом паке**, подтверждаю.
### V5. Общность (несущее)
Три разных вида данных, три разных дома — и это не вкусовщина, а три разных ответа на вопрос «о чём этот факт»:
| Данные | Пример | Дом | Прецедент |
|---|---|---|---|
| Различение T/V, формы 2 л. | «ты/вы», императивы | **ЦЕЛЕВЫЕ**, `lang/data/target-<tgt>.txt` | `speech_verb`/`inner_marker` (`checkers.go:3844`, `embedded.go:25`) |
| Кии прямой речи | 说/道/问 | **ИСХОДНЫЕ**, `configs/langpacks/<src>/speech-cue.txt`, ОПЦИОНАЛЬНЫЙ файл | `terminology.txt` (`langpack.go:244258`) |
| Кто кому «вы» | 方源→李兄 = вы | **КНИЖНЫЕ**, сид | D39.47 (`langpack.go:260264`) |
Третья строка — прямой перенос аргумента отмены жанрового словаря: пар-широкий файл «как персонажи обращаются друг к другу» прописал бы одну социальную модель всем книгам пары, а решение о регистре — подпись владельца на банк КНИГИ.
**Мысленный тест (обязателен) — пара, которой в репо НЕТ. Беру en→ru.**
| Шаг | Что нужно дописать | Правка Go? |
|---|---|---|
| Различение ты/вы | ничего — цель та же (`target-ru.txt` уже есть) | нет |
| Кии речи | `configs/langpacks/en/speech-cue.txt`: said, asked, replied… | нет (опциональный файл, `readOptional``langpack.go:537`) |
| Реестр обращений | секции сида книги | нет |
| Алфавит кавычек | `"…"` вместо `「…」`**обязан прийти данными** | **да, если алгоритм зашьёт CJK-смежность** |
**Проектное ограничение, которое надо записать в фазу 2:** сопоставление кия ведётся через нормализацию + **алфавит кавычек как данные** (прецедент — `lang.Terminators`, `embedded.go:137146`, где два класса терминаторов уже данные). Если этого не сделать, «имя + кий вплотную к кавычке» окажется правилом про китайский, зашитым в Go, — то есть ровно утечкой §0.
**Пара БЕЗ T/V (→en, →ja):** целевой файл не несёт строк `tv_*` → флаггер инертен, 0 срабатываний, ноль правок Go. Прецедент поведения — `TargetChecks.HasData()` (`embedded.go:37`) и «пара, не включившаяся в механизм, никогда не флагается» (`langpack.go:229231`).
**Явная граница скоупа (и защита от углубления ru-target долга, бэклог 32):** v1 флаггера смотрит **только поверхности местоимений и императивов из целевых данных**. Никакой лемматизации, никакого согласования глагола, никакого `TokenizeCyrillic`-расширения. research/15 §2.1 говорит, что ru-формальность = местоимение 2 л. + согласование; беру только первую половину — она бинарна и надёжна на одной реплике, а вторая тянет морфологию, то есть слой 7 и чужой пак.
### V6. Деньги и измеримость
**Флаггер: $0.** Детерминированный, в горячем пути, без вызовов.
**Инъекция, если её когда-нибудь включат** (пересчёт «другим способом» — по действующему прайсу, а не по цифре D21):
| Слот | Токенов | Цена входа | На книгу-500 |
|---|---|---|---|
| Драфт (~1000 чанков) | 300/чанк | $0.14/M (`models.yaml:149`) | **$0.042** |
| Редактор (~500 юнитов) | 300/юнит | $0.435/M (`models.yaml:161`) | **$0.065** |
**D21 п.8 «верхняя граница editor-side +$0.20.3» устарела по фактуре, а не по методу.** Она считалась при grok-редакторе ($1.25/M входа — research/15 §1.5); действующий интерим-редактор deepseek-v4-pro втрое дешевле по входу, и потолок падает до ~$0.0650.1. Драфт-слот воспроизводит ратифицированную цифру ($0.020.05) точно. Развилки «дороже — повод для решения» не возникает ни в одном слоте.
**Что мерить и чем** (нормы D39.46/47 — арм без фактора обязателен):
- точность флаггера — на экспорте ХОЛОДНОГО мини-прогона, руками по срабатываниям; бэклог-24 уже держит строку «срабатывания голос-флаггера»;
- **детерминированный скорер > судьи:** счёт флипов ты/вы на книгу — сам по себе метрика, судья не нужен;
- арм без фактора: тот же прогон с `gates.voice.enabled: false` даёт нулевую линию бесплатно (флаггер $0, прогон один и тот же);
- **не мерить здесь:** blind speaker attribution — сторона полигона, в пак не входит (промт это фиксирует, подтверждаю).
---
## 4. Адверсариальный селф-ревью (5 линз)
**Линза «деньги и пере-оплата».** Три места, где наивная реализация пере-покупает ВСЕ книги: бамп `RenderFormatVersion` (`snapshot.go:427`), поле без `omitempty` в `contextSnap` (`snapshot.go:2024`), бамп `CheapGateVersion` (`snapshot.go:432`). Все три обойдены механизмом, не дисциплиной (условный тег / нулевой `omitempty` / отдельный гейт). Четвёртое, менее очевидное: `LangpackVersion` (`snapshot.go:376`) — поэтому кии речи идут ОПЦИОНАЛЬНЫМ файлом; пара, которая его не шлёт, сохраняет байт-стабильную версию пакета. **Остаточный риск:** если фазу 2 будут строить «по образцу banknote», соблазн добавить `voiceSnap` отдельным ключом велик — и он тихо ломает `repin` (Н3). Это надо вынести в «ровно так» фазы 2.
**Линза «durable-схема и миграции».** Миграции append-only (`migrate.go:911`); новые строки — аддитивные колонки/таблицы с дефолтами, как v11/v12 (`migrate.go:306,328`), то есть прошлые строки и провод не двигаются. Опасность иная: `ReplaceGlossary` заменяет весь глоссарий книги из детерминированных входов; новые типы записей обязаны попасть в ту же транзакцию замены, иначе появится второй жизненный цикл и рассинхрон с `term_id` (логическая связь без FK — `migrate.go:209`). **Остаточный риск:** `address_pair` ссылается на ДВА `term_id`, а `glossary.id` — свежий autoincrement на каждую замену (`migrate.go:173`); значит пара обязана храниться по СТАБИЛЬНОМУ ключу (src+sense), а не по `id`. Это надо явно записать, иначе реестр протухнет после первой же пере-засевки.
**Линза «общность §0».** Ревью-вопрос «заработает ли на паре, которой нет»: см. таблицу V5 — заработает при одном условии (алфавит кавычек данными). Самая слабая точка честно названа: `speech-cue` формулируется как «кий вплотную к кавычке», и «вплотную» для zh (без пробелов) и для en (с пробелами и атрибуцией ПОСЛЕ реплики) — разные правила. Мнение: v1 должен принимать оба порядка (кий до / кий после) как данные-флаг в файле пары, а не как две ветки Go.
**Линза «байт-точность и снапшот».** Единственная ось, которую пак сознательно двигает, — `memory_version`, и только для книги, где инъекция включена. Ожидание для golden: маскированный структурный дифф **пуст**, потому что фикстура голоса не имеет; ни одна ось не сдвигается. Если фаза 2 обнаружит непустой дифф — это дефект реализации, а не смена поведения. Отдельная строка на будущее: `banknote`-подобный `omitempty`-указатель для voice в снапшот **не заводить** (Н3).
**Линза «граница с ru-target долгом».** Флаггер целиком питается целевыми данными и не добавляет ни одного `isRuTarget` (сейчас 11 сайтов, бэклог 32) и ни одного вызова `TokenizeCyrillic`. Точка риска: соблазн проверить согласование глагола. Явно запрещено в V5. **Остаточно:** `checks/repair.go:178184` уже фиксирует «Go-level target assumption, которое не переопределяется данными» — новый флаггер не должен добавить второй такой.
---
## 5. Что предлагаю НЕ делать (право D39.46/47)
**Reveal-окна как НОВЫЙ механизм строить не надо — он на ~90% уже построен, и это измерено, а не предположено.**
- Пара «до раскрытия / после раскрытия» выражается двумя строками банка с непересекающимися окнами: `UNIQUE(book_id, src, sense, since_ch, until_ch)` (`migrate.go:202`) их различает, `windowsOverlap` (`memseed.go:247`) их пропускает (`[1,N]` vs `[N+1,∞]`), `spoilerBlocked` (`memory.go:533`) выбирает ровно одну, и это **уже запинено тестом** как «a spoiler handoff of the same reading» (`memseed_test.go:427437`).
- Алиасам собственное окно не нужно: алиас наследует окно своей строки (`memory.go:264280`), а pre/post-алиасы принадлежат РАЗНЫМ строкам.
- Единственное, чего действительно нет, — **флаггер утечки**: «dst строки, отклонённой спойлер-окном, появился в выходе». И он почти бесплатен: отклонённые строки уже собираются (`memory.go:472`, `Selection.Rejected`), а проверка присутствия dst с учётом склонений уже написана (`dstFormPresent`, `mempostcheck.go:144`). Это ~15 строк переиспользования и **ноль схемных изменений**.
**Предлагаю переопределить деливерабл «reveal-окна» как «флаггер спойлер-утечки + документированный паттерн двух строк», без колонки `reveal_ch` и без фазы на алиасах.** Колонка `reveal_ch`, предположенная запиской `migrate.go:372375`, была бы третьим способом выразить то, что уже выражается двумя, — и каждый лишний способ означает ещё одну ветку в `spoilerBlocked`.
---
## 6. Пинги оркестратору (расхождения промта/записей с кодом)
1. **`migrate.go:372375` (записка D21.10) предполагает колонку `reveal_ch` и фазу на алиасах — обе избыточны** (§5). Записка написана до того, как непересекающиеся окна были запинены тестом. Прошу ратифицировать сокращение, а не строить третий способ.
2. **`glossary.speech` и `glossary.first_person` — мёртвый код данных** (Н1): пишутся из сида, хранятся, не читаются никем. Либо они становятся носителями новых сущностей, либо их надо объявить deprecated. Тихо оставить нельзя: сид владельца выглядит работающим и не работает.
3. **D21 п.8 «+$0.20.3 editor-side» считалась при grok-редакторе**; при действующем интериме потолок ~$0.0650.1 (§V6). Цифру в контракте стоит пометить как привязанную к модели.
4. **Посылка «голос выигрывается на драфте» (D21 п.1г/P3) не измерена после флипа D1** — это уже зафиксировано баннером ревизии D31 в `research/15`, но промт пака-19 подаёт драфт-слот как «дефолт». Прошу читать как «слот не выбран», иначе полигон получит арм с предрешённым исходом.
5. **Ось D флаггера (регистр пары) без Аннотатора неполна структурно, а не количественно** (§V1-бис): адресат детерминированно недостижим. Если ратификация ожидает от «безусловного флаггера» именно проверку ПАР, то безусловная половина D21 меньше, чем звучит, и это стоит зафиксировать до фазы 2, а не после.
---
## 7. Приёмка фазы 1
- Эхо-блок — §1 · ШАГ 0 с таблицей и 4 находками — §2 · V1V6 с вариантами/рекомендацией/рисками — §3 · адверсариальный селф-ревью по 5 линзам — §4 · право «не делать» использовано — §5 · пинги — §6.
- Кода не тронуто; коммитов нет; `git status` по моей зоне чист.
- Аддендумы получены и учтены (шапка).
**СТОП — жду ратификации.**