105 lines
39 KiB
Markdown
105 lines
39 KiB
Markdown
# Реестр рисков банка памяти (v1, 2026-07-04)
|
||
|
||
Выход сессии **«Валидация банка памяти»** (промт `docs/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 (персонажи из предыдущих 1–2 чанков остаются); это состояние, а не 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 | **Разрешение алиасов** (名/字/号/прозвища/титулы: «один персонаж = 3–6 людей») | С / 🟠🔇 | Alias-граф с типами (имя/цзы/хао/прозвище/титул) + name-tables (фамилия/имя раздельно); NER-гейт «новая форма имени-подобного токена = блок/флаг» | DET+LLM(экстракция)+HUMAN | схема: `aliases`→граф (D7 v1); NER-гейт | 1–2 | 🔶 (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) | 1–2 | 🔶 (находка критика) |
|
||
|
||
## D. Иерархическая память (резюме) — «вторая половина банка», почти не покрыта
|
||
|
||
| # | Сценарий отказа | Вер./Тяж. | Механизм защиты | Слой | Где в коде/схеме | Фаза | Статус |
|
||
|---|---|---|---|---|---|---|---|
|
||
| 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 оставляет **17–36% терминов неиспользованными** (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) состояние памяти (версия-счётчик approved-глоссария + резюме/series-bible на момент старта джобы — рендер читает **замороженную** версию, не live) и (2) context-assembly-конфиг (`glossary_injection`, `stm_depth`, `overlap`). Тогда рендер снова чист по snapshot. **✅ УЖЕ СДЕЛАНО (commit 5eaf2d9): snapshot содержит `context_assembly` (`GlossaryInjection`/`GlossaryTokenBudget`) + `MemoryVersion` — материализует память → хеш (выбран мой рекомендованный «материализующий» вариант); `Capability` тоже свёрнут. `memoryVersion()` пока хеширует константу-заглушку `tm-memory-v1\x00`, т.к. банк памяти v2 ещё не приземлился (комментарий в коде явно фиксирует инвариант).** | DET | `runner.snapshotID()`/`memoryVersion()`/`contextSnap` | сделано; активируется с v2 | ✅ структурно закрыто (5eaf2d9). **ОСТАЁТСЯ узко: при приземлении глоссария заменить константу в `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.0–0.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; статус-машина термина | 1–2 | 🔶 верифицировано (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` запись; отчёт «паспорт качества главы» | 1–2 | ❌ 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 81–108 мс на 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).
|
||
|
||
> ⚠ **Перед реализацией прочитай `research/13` §«Честные слабые места рекомендаций».** Два пункта прямо влияют на этот реестр: **F1** подан как решённый фикс, но там реальная развилка (snapshot материализует байты инъекции vs store версионирует глоссарий — я склоняюсь к «байты», но не доказал); **E1 post-check** — несущий гейт, но его надёжность в русской морфологии сама невалидирована (склонение-осознанный матч шумит → ложные флаги → редакторы игнорируют → safety рушится). Померить пост-check на русском ДО того, как доверять ему как гейту. Исполняемый эталон механизма (не продукт) — `eval/memory_hotpath.py` (T1–T10 → 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), а не устраняем её обещанием.**
|