textmachine/docs/architecture/06-memory-risk-registry.md

116 lines
41 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.

# Реестр рисков банка памяти (v1, 2026-07-04)
Выход сессии **«Валидация банка памяти»** (промт `docs/archive/prompts/MEMORY_RESEARCH_SESSION_PROMPT.md`). Это **вход для бэкенд-сессии перед фиксацией схемы store/глоссария Фазы 1** (шаг 4 «миграция памяти v2» держится до этого реестра — PROGRESS §Память, D7).
Метод: многоагентный ресёрч по 7 направлениям + независимая адверсариальная верификация каждой находки по первоисточникам + локальный эмпирический бенчмарк retrieval на реальном PD-корпусе (`eval/retrieval_bench.py`, `eval/data/retrieval_bench/`) + чтение реального кода `backend/`. Полный разбор с источниками и датами — `docs/research/13-memory-bank-validation.md`. Продуктовый код здесь не пишется — реестр описывает требования к нему.
**Главный вывод для чтения таблицы.** Опасение владельца («retrieval вернёт пусто/мусор → перевод деградирует тихо») уточнено литературой: **пустой retrieval — нормальное и безопасное состояние** детерминированного горячего пути (большинство чанков законно не содержат ключей глоссария); откат к базовому поведению модели. **Реальная тихая деградация — это ошибочная инъекция** (омограф/короткий алиас/спойлер-утечка/низкорелевантный эмбеддинг-сосед): подсунутая строка глоссария работает как мягкая инструкция, и сильная модель ей **послушно следует — уверенность растёт, а точность падает** (arXiv 2510.00829). Поэтому вся защита строится на двух принципах: **«лучше ничего, чем мусор»** (не добивать токен-бюджет соседями — arXiv 2401.14887, 2310.01558) и **точность важнее полноты на горячем пути**, плюс **обязательный детерминированный post-check** как главный (и почти бесплатный) детектор тихой деградации.
## Легенда
- **Слой защиты:** `DET` — детерминированный код (горячий путь или батч, без LLM); `LLM` — вызов модели (только батч/раз в главу, **никогда не в горячем пути инъекции**); `HUMAN` — ручное подтверждение/политика.
- **Вер./Тяж.:** вероятность (В/С/Н) и тяжесть (🔴 критич. / 🟠 высокая / 🟡 средняя). **🔇 = тихая деградация** (проходит без явного сигнала — приоритет владельца).
- **Фаза:** по плану MVP (1 = «перевод книги целиком», 2 = Python-сайдкар/эмбеддинги/судьи).
- **Статус:** ✅ закрыто дизайном v2 · 🔶 требует уточнения/новой работы в схеме или коде · ❌ дыра, действие обязательно до фиксации схемы.
---
## A. Горячий путь: robustness инъекции памяти
| # | Сценарий отказа (как ломает перевод) | Вер./Тяж. | Механизм защиты | Слой | Где в коде/схеме | Фаза | Статус |
|---|---|---|---|---|---|---|---|
| A1 | **Пустой/слабый retrieval трактуется как ошибка** → пайплайн «добивает» бюджет ближайшими эмбеддинг-соседями или фоновыми записями → near-miss дистрактор роняет качество (до 67%, 2401.14887; NLI-фильтр спасает, 2310.01558) | С / 🟠🔇 | «Лучше ничего, чем мусор»: на пустом ключевом матче инъектить **только** активные sticky-записи; бюджет **допустимо недозаполнить**; фильтр по точности **перед** сортировкой по приоритету (не только `approved>auto`) | DET | сборщик контекста (`render.Messages`, инъекция между system и user, `render.go:133-136`) | 1 | 🔶 добавить precision-gate; в research/05 §5.2 бюджет заполняется по приоритету, не по точности |
| A2 | **Ошибочная инъекция** (омограф/короткий алиас/низкорелевантный сосед) → модель послушно следует мягкой инструкции, уверенность растёт, точность падает (2510.00829) — **главный источник тихой деградации** | С / 🔴🔇 | Трёхстороннее явное решение **на каждую запись-кандидат**: `CONFIRMED` (точный ключ/лемма, `approved`, спойлер-валидна) → авторитетно; `AMBIGUOUS` (короткий/общий алиас, коллизия поверхности, `auto/draft`) → инъекция с явной пометкой «unverified — проверить» **+ принудительный post-check**; `REJECT` → выкинуть и залогировать. Паттерн CRAG (accept/verify/reject), но каждая ветка наблюдаема | DET | сборщик контекста + `disposition`-запись | 1 | ❌ спроектировать трёхсторонний disposition; сейчас инъекции нет вовсе |
| A3 | **CJK-подстрока срабатывает на чужом смысле** (阿Q, 送灶-обряд vs «送到灶», 炎=пламя vs имя 萧炎, 道/气/修 полисемия) → инъекция неверной записи | В / 🟠🔇 | Запрет одиночных Han/каны/латинской буквы как самостоятельного ключа (schema-констрейнт, не соглашение); longest-match; замена только целой сущности; условные `judgment word` (GalTransl post-dict). **Эмпирика (`retrieval_bench`): точный многосимвольный подстрочный матч — precision 1.0, 0/4 ложных срабатываний на этих ловушках; char-BM25/фаззи — все 4 ловушки в top-5** → фаззи/одиночные ключи на горячий путь не пускать | DET | матчер ключей; `glossary.min_key_len` per-язык; поле `sense` | 1 | 🔶 ключевая гигиена как констрейнт схемы (новое) |
| A4 | **Молчаливо-пустой матч из-за орфографии** (упрощ./трад. 鲁/魯, полу/полноширинные, кандзи↔кана 鈴木/すずき, ruby-чтения) → ключ есть в тексте, но не в той форме → запись не инъектируется | В / 🟠🔇 | Обязательный слой **нормализации перед матчем** (NFKC + OpenCC упрощ./трад. + kana-folding + захват ruby-чтений), симметрично к ключам/алиасам и тексту чанка; версионированный тестируемый артефакт | DET | pre-match нормализация (связать с pre-replace GalTransl); поле `aliases` | 1 | ❌ в research/05 §5.2 нормализация матчинга не описана — самый вероятный баг «тихо пусто» |
| A5 | **Местоименные чанки** (имя не встречается, только «он/她/彼») → нужная запись не активируется | В / 🟡 | Sticky scene-inertia (персонажи из предыдущих 12 чанков остаются); это состояние, а не retrieval — **не** решать эмбеддингами | DET | sticky-состояние сборщика | 1 | ✅ (в дизайне; см. World Info sticky) |
| A6 | **Lost-in-the-middle**: глоссарий закопан между стопкой резюме книги/арки/главы | Н / 🟡 | Авторитетный блок глоссария — рядом с чанком (конец промпта), не в середине; при наших размерах инъекции эффект мал, но избежать бесплатно | DET | раскладка `render.Messages` (согласовать с кэш-раскладкой Р5) | 1 | 🔶 |
| A7 | **Эмбеддинг-порог как тихий гейт** (Фаза 2): единый глобальный cosine-порог ведёт себя как эвристика entity-overlap и ломается на сдвиге распределения (репродукция CRAG, 2603.16169); zh/ja→ru литература — и есть сдвиг | С / 🟠🔇 | Эмбеддинг-слой держать **low-trust**: калибровать порог per-книга/язык на held-out чанках; использовать для генерации **кандидатов на ревью** (флешбеки, редкие термины), **не** для тихой инъекции в горячий путь | DET+HUMAN | Фаза-2 retrieval-слой | 2 | 🔶 research/05 подаёт эмбеддинги как безобидный «второй эшелон» без порога/воздержания |
## B. Корректность глоссария (реестр «термин → перевод»)
| # | Сценарий отказа | Вер./Тяж. | Механизм защиты | Слой | Где в коде/схеме | Фаза | Статус |
|---|---|---|---|---|---|---|---|
| B1 | **Устаревшая запись (stale)**: первый (наименее информированный) перевод имени лочится на 1000 глав. **DelTA Proper Noun Records = first-translation-wins без обновления и без детекции конфликтов → сам механизм ПОРОЖДАЕТ stale/contradiction/drift, а не защищает** (2410.08143; LTCR-1 меряет самосогласованность, не корректность — консистентно-неверное имя даёт 100%) | С / 🟠🔇 | Append-only журнал ревизий рендеринга (`src, old_dst, new_dst, editorial_ts`) **отдельной осью** от спойлер-окна; смена approved-рендеринга → LLM/HUMAN-подтверждение → точечный ре-перевод по ключу (ReTranslation-by-keyword, LinguaGacha); content-hash в snapshot принуждает перевыполнение | DET(журнал)+HUMAN | схема: ось «редакционное время» ≠ `since_ch/until_ch`; ключ TM (Р6) | 1 (поле) / 2 (ре-перевод) | ❌ DelTA-PNR наивно нельзя; в research/05 §4.2 подан как позитив |
| B2 | **Противоречивые записи**: один термин переведён двумя способами / два исходных → один перевод | С / 🟠 | `UNIQUE(src, sense, window)` на approved-строках + обратный `GROUP BY dst` (детект коллизии инъективности); LLM-адъюдикация конфликтов **раз в главу батчем** (паттерн write-time из Zep/Graphiti, но в SQLite без Neo4j); HUMAN решает | DET(констрейнт)+LLM(батч) | схема: индексы/констрейнты; тип `ranked-set` для рангов (v1.1) | 1 (констрейнт) / 2 (адъюдикация) | 🔶 |
| B3 | **Разрешение алиасов** (名/字/号/прозвища/титулы: «один персонаж = 36 людей») | С / 🟠🔇 | Alias-граф с типами (имя/цзы/хао/прозвище/титул) + name-tables (фамилия/имя раздельно); NER-гейт «новая форма имени-подобного токена = блок/флаг» | DET+LLM(экстракция)+HUMAN | схема: `aliases`→граф (D7 v1); NER-гейт | 12 | 🔶 (04-unhappy §1) |
| B4 | **Дрейф «термин → утверждённый перевод»** по ходу книги (approved-термин передан иначе) | С / 🟠🔇 | Post-check регэксп по **склонённым** формам (через `decl`/лемматизатор): approved `src`/алиас во входе → лемма `dst` в выходе? нет → флаг редактору; прономинализация не штрафуется, только `approved` (процедура Р7) | DET | гейт консистентности глоссария (Р7); поле `decl` | 1 | ✅ (Р7) — но см. E-post-check |
| B5 | **Западные имена через катакану/фонозапись** (约翰→«Юэхань» вместо «Джон», ヴァイオレット, васэй-эйго マンション=квартира) | С / 🟡 | Поле `translit_policy` для псевдоевропейских имён; словарь васэй-эйго (v1.1) | DET | схема: `translit_policy` (D7 v1) | 1 (поле) / 2 (словарь) | ✅ (04-unhappy §9, D7) |
| B6 | **Нонконформность стандарту транслитерации** (zh→ru — Палладий, ja→ru — Поливанов): имя может быть **внутренне консистентным, но систематически неверным** по стандарту — ось корректности, ортогональная самосогласованности | С / 🟠🔇 | Валидация `dst` имён собственных против таблиц Палладия/Поливанова на коммите термина; отклонение — флаг/политика проекта | DET+HUMAN | схема: правило на коммите `dst`; пресеты стандарта | 2 | 🔶 **эмпирически подтверждён (exp06):** локаль на zh даёт Палладий-мусор (阿Q→«Ах-Чу», 赵太爷→«Тяо Тай-Я»), рендер zh 0.26; даже облако-топ лишь 0.75 (часть zh→ru реалий без единого канона) → детерминированная таблица Палладия/Поливанова обязательна, «сильной моделью» не закрывается |
## C. Спойлеры и род (темпоральная ось)
| # | Сценарий отказа | Вер./Тяж. | Механизм защиты | Слой | Где в коде/схеме | Фаза | Статус |
|---|---|---|---|---|---|---|---|
| C1 | **Спойлер-утечка**: retrieval отдаёт факт, которого текущая глава знать не должна («учитель — предатель» из гл. 200 в гл. 5) | С / 🟠🔇 | `since_ch/until_ch`**жёсткий reject с логируемым событием** на ОБОИХ путях (ключевой И эмбеддинг), не UX-фича; это safety-гейт | DET | фильтр окна в сборщике; поля `since_ch/until_ch` | 1 | 🔶 research/05 трактует спойлер-окно как продуктовую фичу, не как safety |
| C2 | **Поздний гендер-твист / намеренная неоднозначность** (данмэй, 女扮男装, ta) → вмороженные главы закоммитили род | С / 🟠 | `gender=hidden(until_ch)` + «дисциплина уклонения» в промпте (безличные/настоящее время) + детерминированный блокер утечки рода; ретро-патч-протокол «с гл. N — так» с показом стоимости | DET+HUMAN | поле `gender=hidden`; протокол релиза твиста (D5.1) | 2 | 🔶 (04-unhappy §2, D7) |
| C3 | **Утечка рода через русский глагол прош. вр.** («сказал» vs «сказала») — `gender=hidden` защищает существительные/местоимения, но глагол согласуется с полом **без имени рядом** | С / 🟠🔇 | Морфо-гейт post-check должен сканировать **глагольные формы** прош. времени, не только родовые существительные | DET (Python-морфология) | морфо-гейт Фазы 2 (pymorphy/Natasha) | 2 | ❌ под-специфицировано (находка критика; 04-unhappy §2 говорит о согласовании, но не выделяет глагол-без-имени) |
| C4 | **Склонение транслитерированных CJK-имён**: какие имена склоняются в русском — правило-зависимо (многие ja на гласную несклоняемы, zh-моносиллабы варьируются) → `decl` для CJK-имён нетривиален → источник ложных флагов/форсов | С / 🟡 | Правила склоняемости per-пара при заполнении `decl`; при неуверенности — флаг, не форс | DET+LLM(заполнение)+HUMAN | заполнение `decl` (один дешёвый LLM-вызов на коммит термина, Р3) | 12 | 🔶 (находка критика) |
## D. Иерархическая память (резюме) — «вторая половина банка», почти не покрыта
> Строки D1/D2 ниже — ID секции D этого РЕЕСТРА (буква-секция + номер, как A1/E1/F1); НЕ путать с решениями D1D12 из `05-decisions-log.md`.
| # | Сценарий отказа | Вер./Тяж. | Механизм защиты | Слой | Где в коде/схеме | Фаза | Статус |
|---|---|---|---|---|---|---|---|
| D1 | **Пропагация ошибки резюме**: галлюцинированный/неверный факт в резюме главы инъектируется как авторитетный контекст и **компаундится по 1000 глав — post-check-аналога у резюме НЕТ** (у глоссария есть GalTransl layer-3, у резюме — ничего) | С / 🟠🔇 | Отдельный gate для резюме: сверка сущностей резюме против глоссария/coverage; батч-судья фактичности резюме раз в арку; резюме — не «авторитет по умолчанию» | DET+LLM(батч) | схема резюме; гейт резюме | 2 | ❌ **первоклассный пробел** — все направления ~на 90% про глоссарий (находка критика) |
| D2 | **Whole-book RAG по резюме — недоказанное расширение**: DelTA LTM = ограниченное онлайн-окно ~20 предложений уже-переведённого префикса + LLM-ретривер per-предложение, **не** whole-document RAG и **не** детерминированный | С / 🟡 | Держать как гипотезу к проверке, не как доказанное; whole-book семантический слой — Фаза 2, low-trust (см. A7) | DET+HUMAN | Фаза-2 LTM | 2 | 🔶 research/05 §4.2 гласит «retrieval по всему документу» — сверх того, что DelTA показал |
## E. Детерминизм и принуждение (Р3 трёхслойная защита)
| # | Сценарий отказа | Вер./Тяж. | Механизм защиты | Слой | Где в коде/схеме | Фаза | Статус |
|---|---|---|---|---|---|---|---|
| E1 | **Мягкий глоссарий молча не сработал**: LLM оставляет **1736% терминов неиспользованными** (2310.05824) — soft following выигрывает по качеству (DuTerm en-ru, 2511.07461; WMT23 COMET-QE), но проигрывает по adherence | В / 🟠🔇 | **Post-check обязателен и это post-CHECK, а не слепой post-replace**: для каждого approved-термина с `src` в чанке — проверить, что склонённая форма `dst` есть в выходе; нет → флаг/точечный ре-перевод. Это главный (и почти бесплатный) детектор тихой деградации: ловит и «модель проигнорировала», и «мы инъектили неверное» | DET | post-check гейт (Р7); поле `decl` | 1 | 🔶 повысить приоритет: не «nice-to-have флаггер», а **необязательный→обязательный** гейт наблюдаемости |
| E2 | **Слепой post-replace в русский выход** ломает согласование (принудительная словарная форма в косвенный падеж; 46% ошибок constrained-моделей в en-cs — согласование, 2106.12398) | С / 🟠 | **Запретить** слепой регэксп-replace форм глоссария в ru-выход; форс только когда термин морфологически инвариантен ИЛИ есть полный `decl` И падеж резолвится; иначе флаг. Модели дают **лемму** в мягком глоссарии, склоняет она сама (+47.7 п.п. term accuracy в латышский — Bergmanis&Pinnis) | DET | post-check; политика форса; `decl` | 1 | 🔶 `decl` — не «фича ru-рынка», а safety-механизм грамматичности |
| E3 | **Жёсткий constrained decoding как «нельзя»** — переоценка: наивный (Grid Beam Search) стоит ~7 BLEU, но plug-and-play (Cascaded Beam Search, 2305.14538) даёт ~0 BLEU-потерь и EMA 0.925 на en-ru. Реальная причина не использовать — **недоступен через закрытые frontier-API** (нужен доступ к beam/декодеру), а не «портит качество» | — / — | Оставить soft-в-промпте дефолтом (доступно, качество подтверждено); формулировку research/05 conclusion #4 поправить | DET | — | 1 | 🔶 research/05 §5.3 «constrained decoding портит качество» — overstated |
## F. Инфраструктура и состояние (код `backend/`)
| # | Сценарий отказа | Вер./Тяж. | Механизм защиты | Слой | Где в коде/схеме | Фаза | Статус |
|---|---|---|---|---|---|---|---|
| F1 | **`snapshotID` не покрывает память → тихий расходящийся ре-пэй (D5.2)**. Подтверждено кодом: `snapshotID` (`runner.go:145-163`) хэширует brief/chunker/план-стадий/сэмплинг, но **не** версию approved-глоссария и **не** параметры сборки контекста; `RequestHash` (`render.go:158`) включает контент `msgs`; `render.go:23-29` декларирует рендер «чистой функцией snapshot». Как только Фаза 1 инъектит память в `msgs` (`render.go:133-136`): изменённый глоссарий/STM меняет `request_hash`, но не `snapshotID` → resnapshot-гейт (`runner.go:260`) молчит → ре-ран старой главы промахивается мимо чекпоинта и **переоплачивает расходящимся переводом**, ломая инвариант Р6 | В / 🔴🔇 | **Свернуть в snapshot ОБА:** (1) состояние памяти (**content-hash материализованного** approved-глоссария + резюме/series-bible на момент старта джобы — рендер читает **замороженную** версию, не live; **D8: content-hash, НЕ версия-счётчик** — счётчик молча дрейфует, если забыли бампнуть) и (2) context-assembly-конфиг (`glossary_injection`, `stm_depth`, `overlap`). Тогда рендер снова чист по snapshot. **✅ УЖЕ СДЕЛАНО (commit 636919c): snapshot содержит `context_assembly` (`GlossaryInjection`/`GlossaryTokenBudget`) + `MemoryVersion` — материализует память → хеш (выбран мой рекомендованный «материализующий» вариант); `Capability` тоже свёрнут. `memoryVersion()` пока хеширует константу-заглушку `tm-memory-v1\x00`, т.к. банк памяти v2 ещё не приземлился (комментарий в коде явно фиксирует инвариант).** | DET | `runner.snapshotID()`/`memoryVersion()`/`contextSnap` | сделано; активируется с v2 | ✅ структурно закрыто (636919c). **ОСТАЁТСЯ узко: при приземлении глоссария заменить константу в `memoryVersion()` на хеш ORDER BY-стабильных материализованных строк (замороженных, не live) — иначе F1 тихо регрессирует.** |
| F2 | **Токен-бюджет: eviction не определён**. При превышении бюджета (matched + sticky > лимит) **какая запись выпадает — не задано**; выпавшая нужная запись = тихая деградация | С / 🟡🔇 | Явная политика приоритета/вытеснения (World Info inclusion-groups: approved>auto; персонажи чанка>фоновые) **+ логировать факт вытеснения** (не молча) | DET | сборщик; `retrieval-state` запись | 1 | ❌ политика и лог вытеснения не специфицированы (находка критика) |
| F3 | **Конкурентность/порядок в SQLite**: параллельный перевод чанков + запись auto-записей ломает `first-translation-wins` и sticky (оба предполагают последовательность); modernc — один писатель | С / 🟡 | Явное правило: auto-экстракция и коммит терминов — только на **границе джоб** (impl-notes §3.2 это уже фиксирует); sticky/порядок считаются от детерминированной пересборки чекпоинтов, не от wall-clock порядка | DET | граница джоб; write-пул store | 1 | 🔶 назвать явно (находка критика) |
| F4 | **Один SQLite-файл на книгу = SPOF**: повреждение файла молча теряет весь глоссарий+резюме | Н / 🟠🔇 | Бэкап/интеграл-чек/версионирование файла книги; экспорт TMX/TBX как побочный бэкап | DET | store; экспорт | 2 | 🔶 нет строки бэкапа/интеграла (находка критика) |
| F5 | **Коррелированный отказ судьи-экстрактора**: адъюдикация конфликтов и подтверждение кандидатов — одна модель на одних и тех же трудных кейсах (редкие имена, неоднозначный смысл) → систематически-неверный судья тихо промоутит плохие approved-ключи → retrieval-state lock-in | С / 🟠🔇 | Независимая проверка (судья чужого семейства / человек) на промоушене approved-ключа; first-encounter имя не промоутится в approved до батч-подтверждения раз в главу | LLM(2-й)+HUMAN | пайплайн подтверждения терминов | 2 | 🔶 (находка критика; смыкается с A2/B1) |
## G. Популяция глоссария и человеческая ёмкость (корень и петля)
| # | Сценарий отказа | Вер./Тяж. | Механизм защиты | Слой | Где в коде/схеме | Фаза | Статус |
|---|---|---|---|---|---|---|---|
| G1 | **Качество автоэкстракции (bootstrap)**: все направления валидируют *retrieval*, но не *популяцию*. Мусор/пропуск главного персонажа на входе → горячий путь верно инъектит мусор / молча опускает — **корневой отказ выше всего измеренного** | С / 🟠🔇 | **Разделить задачу экстракции на две (индикативно замерено, `eval/extract_bench.py`): (а) СПОТ кандидатов-спанов — локаль годится** (qwen3-abliterated:8b recall 1.0 на zh+ja, 0 галлюцинаций, ~15с/чанк — дёшево на стенде); **(б) РЕНДЕРинг dst — локаль НЕ годится** (0.00.5: 阿Q→«А Цзы», 羅生門→«Россомаха», 秀才→«шоуцай» — как exp03), dst даёт сильная/облачная модель + человек. First-encounter unknown → `status=auto/unconfirmed`, гейт (батч-судья/человек) **до** промоушена в approved; alarm «имя-подобный токен без записи → неизвестная сущность» (DelTA-паттерн). ⚠ deepseek-v4-flash молча вернул **пустой** ответ на части чанков (祝福 воспроизводимо) — это тот empty-paid-response, что ловят D2/coverage-гейт | DET(алярм)+LLM(спот=локаль / рендер=облако)+HUMAN | pre-пасс NER; статус-машина термина | 12 | 🔶 верифицировано (exp06, 13 моделей × 12 кейсов zh/ja/en): спот локальный OK (recall ~0.98 все языки), рендер локали язык-зависим (en 0.82 / ja 0.53 / **zh 0.26**) → спот=8b, рендер dst=облако(gemini-flash value)/человек, на zh + таблица Палладия B6 |
| G2 | **Человеческая ёмкость — недоказанная петля всей safety-аргументации**: почти каждая защита кончается «флаг → редактору/человеку». Объём флагов на главу никто не оценил; при высоком flag-rate «route to human» пусто → деградация тихая на практике | С / 🟠🔇 | Оценить flag-volume на главу на пилоте; alarm-пороги и aggregate «memory-coverage»-сигнал; безлюдный режим: auto→approved с записью в журнал (Р7), но с явной пометкой качества | DET(телеметрия)+HUMAN | `retrieval-state` запись; отчёт «паспорт качества главы» | 12 | ❌ flag-rate × throughput не смоделирован (находка критика) |
---
## Контракт горячего пути (снять противоречие до «где в коде»)
Направления разошлись в описании несущего компонента; **резолюция**:
1. **Горячий путь — НЕ FTS5.** impl-notes §3.6 (строки 123-124) уже решает: «FTS5 в горячий путь v1 не тащить (unicode61 не сегментирует CJK; селективной инъекции хватает точных ключей/алиасов по обычным индексам)». То есть горячий путь — **детерминированный multi-pattern матч** (Aho-Corasick/double-array trie по `src`+алиасам над нормализованным чанком; для ru/en — по леммам/`decl`). FTS5/BM25 на горячем пути для zh/ja **молча вернёт ничего** (дефолтный токенайзер не сегментирует CJK) — их туда не ставить. Эмбеддинги на горячем пути — тоже нет (Р3). Это подтверждено эмпирикой (A3).
2. **sqlite-vec: причину отказа в Р3/research/05 поправить.** «pure-Go не загрузит C-расширение» верно **только для `modernc.org/sqlite`**, не как свойство pure-Go: `ncruces/go-sqlite3` (WASM) + `asg017/sqlite-vec-go-bindings/ncruces` — официально поддерживаемый CGO-free путь к SIMD-brute-force sqlite-vec. **Решение (brute-force KNN в MVP) остаётся верным** (эмпирика Q2: <20 мс на 100k×1024-d в чистом numpy; sqlite-vec-scalar 81108 мс на 1M на порядки выше нужд одной книги), но **рациональ переписать**: реальная причина «держим modernc единым драйвером / избегаем WASM-миграции», а не «pure-Go не может». ncruces+sqlite-vec самый дешёвый триггер миграции, если вектора станут узким местом.
## Гейты для бэкенда перед фиксацией схемы store/глоссария v2
Обязательно закрыть/учесть до заморозки схемы (шаг 4):
1. **F1 (snapshotID под память + context-assembly) — до инъекции Фазы 1.** Класс бага «тихий расходящийся ре-пэй». Payload snapshot: версия-счётчик approved-глоссария + резюме/series-bible + `glossary_injection`/`stm_depth`/`overlap`; рендер читает pinned-версию.
2. **Схема v1 (подтверждаю D7) + добавить из этого реестра:** поле `sense` (A3/B-полисемия); ось «редакционное время» отдельно от `since_ch/until_ch` (B1); констрейнты `UNIQUE(src,sense,window)` + обратная проверка `dst`-коллизий (B2); `glossary.min_key_len` per-язык + запрет одиночных ключей (A3); нормализационный артефакт NFKC/OpenCC/kana/ruby (A4).
3. **Post-check из E1/E2 — обязательный гейт, не опциональный флаггер;** политика форса только при разрешимом падеже + `decl`.
4. **Трёхсторонний disposition инъекции (A2)** + `retrieval-state` per-chunk запись (A1/F2/G2: `n_exact_hits, n_sticky, n_ambiguous_flagged, n_spoiler_blocked, embedding_tier_used`) наблюдаемость деградации с первого дня.
5. **Отложено в v1.1/Фазу 2 (не блокирует v1-схему, но заложить поля/место):** транслит-стандарты Палладий/Поливанов (B6), гейт фактичности резюме (D1), whole-book эмбеддинг-слой low-trust (A7/D2), бэкап/интеграл файла книги (F4), морфо-гейт глагольного рода (C3), измерение экстракции и flag-volume (G1/G2).
### Ruby-seed контракт (вход шага 4 из шага 3a — подтверждён оркестратором 05.07)
Шаг 3a захватил ruby/фуригану в `ruby_readings(book_id, base, reading, first_chapter, occurrences)` (capture-only, не в промпте/hash). Маппинг в глоссарий v2 **подтверждён, форма верна**, с 3 уточнениями:
- `base` (кандзи) `src`; `first_chapter` `since_ch`; `occurrences` **приор уверенности** (не прямой approved).
- `reading` (кана) **НЕ dst напрямую, а мост к правильному dst**: это авторитетное произношение, по которому валидируется Поливанов-транслитерация. **Ruby прямо усиливает пробел B6** reading есть ground-truth для транслит-стандарта (без него B6 нерешаем; с ним механически проверяем).
- **Уточнение 1 (обязательно): различать имя-чтение vs авторское двойное чтение (фуригана-игра).** Ruby двойствен (04-unhappy §4): 鈴木すずき (имя name-lock, Поливанов) против 強敵とも (база и чтение значат РАЗНОЕ стилистический приём). Блайнд-лок второго (強敵とも→«друг») = mistranslation. Классифицировать: фонетическая согласованность / паттерн собственного имени name-lock; несвязанное чтение **переводческая сноска, не лок**. Неоднозначное человеку.
- **Уточнение 2: ruby СИД в auto-кандидатный пул с приором `occurrences`, НЕ прямо в approved** (статус-машина G1: высоко-частотное чтение auto-кандидат; батч-судья/человек промоутит в approved до распространения на будущие главы).
- **Уточнение 3: `decl`/`gender` ruby НЕ даёт** они с term-commit LLM-пасса (decl) и контекста (gender). Ruby сидит src+readingdst, остальное достраивается.
> ⚠ **Перед реализацией прочитай `research/13` §«Честные слабые места рекомендаций».** Два пункта прямо влияют на этот реестр: **F1** подан как решённый фикс, но там реальная развилка (snapshot материализует байты инъекции vs store версионирует глоссарий — я склоняюсь к «байты», но не доказал); **E1 post-check** — несущий гейт, но его надёжность в русской морфологии сама невалидирована (склонение-осознанный матч шумит → ложные флаги → редакторы игнорируют → safety рушится). Померить пост-check на русском ДО того, как доверять ему как гейту. Исполняемый эталон механизма (не продукт) — `eval/memory_hotpath.py` (T1T10 → Go-юниттесты).
## In-house eval — обязателен (числа литературы не переносятся)
**Ни один процитированный источник не меряет zh/ja→ru** (DelTA/TransAgents/Karpinska/WMT25 En-центричны или ru только как *источник*; вендорские memory-числа про chat-память о пользователе, при честной методологии их бьёт ванильный RAG). Поэтому перед заморозкой ставки нужен **свой замер на реальном стеке и направлении** детали, acceptance-критерии и протокол (bank-vs-long-context baseline, «random-terminology» адверсариальный arm из WMT25) в `docs/research/13-memory-bank-validation.md` §Вердикт. Реестр рисков не заменяет этот замер он делает провалы **явными**, что и есть ответ на опасение владельца: **тихую деградацию мы конвертируем в громкую (post-check + disposition-лог + retrieval-state), а не устраняем её обещанием.**