Accept memory-bank validation: GO on schema and deterministic gates, bet gated by in-house eval running parallel; apply pings to R3/R7

This commit is contained in:
Claude (backend session) 2026-07-04 20:26:00 +03:00
parent 8fac2b8968
commit dee6efa282
4 changed files with 407 additions and 12 deletions

View file

@ -44,7 +44,8 @@
- [ ] Фаза 01 бэкенда — **передана отдельной сессии «Бэкенд»** (промт: `BACKEND_SESSION_PROMPT.md`; шаг 0 — перевалидация решений, финал фаз — агентное селфревью). Оркестратор делает внешнее ревью её кода.
- [x] Параллельная сессия «Полигон» запущена (эксперименты 0103 идут).
- [ ] Сессия «Редакция» (промпты ролей) — после фиксации формата конфига в Фазе 0.
- [ ] Пилот 4 рук для выбора ядра пайплайна (Фаза 2.5).
- [x] Сессия «Валидация банка памяти» — завершена (вердикт GO на схему+гейты; реестр `architecture/06`; ставка гейтится in-house eval'ом в пилоте Ф2.5).
- [ ] Пилот 4 рук для выбора ядра пайплайна + **in-house memory-eval** (Фаза 2.5).
- Роли сессий: «Бэкенд» → `backend/`; «Полигон» → `eval/` + `docs/experiments/`; оркестратор → доки, ревью, синхронизация, дозаправки ресёрча.
- [x] **Дозаправка ресёрча выполнена (04.07, оркестратор)**: 5 документов `research/12-*.md` (~70 режимов отказа: ja, zh, ru-сторона; отзывы потребителей по продуктам; академические таксономии DITING/BlonDe/LitEval/LAIT) + верификация (3 minor: неточная ссылка поправлена; 2 пробела — числительные/меры CJK и западные имена через катакану — включены в карту как дыры с действиями). Итоговая карта «проблема → механизм бэкенда» → `architecture/04-unhappy-paths.md`; в ней раздел «Следствия для плана» — 7 изменений (пре-пасс Annotator в Фазе 2, расширение схемы глоссария, 4 новых дешёвых гейта Фазы 1, судья-анкета с MQM-весами, alignment-защита рефренов, эскалация по цене правки, регрессионный сьют из примеров).
@ -288,3 +289,52 @@ keep-alive (Ф12), инъекция глоссария (`selective`), пор
> 4. Проброс cache-hit/batch RU-агрегаторами (уже в бэклоге) гейтит достоверность всего премиум-бюджета — приоритет поднять.
> 5. **Ключевой набор расширен:** теперь живы и `OPENAI_API_KEY`, и `GEMINI_API_KEY`, и `XAI_API_KEY` — Gemini-судья и Grok-канал-B можно гонять в евалах сразу.
> 6. **xAI/Grok НЕ выведен** (уточнение владельца): ретайрнулся только дешёвый Grok 4.1 Fast, сам провайдер жив. Роли Grok — переводчик канала B **и судья 18+** (не отказывается оценивать explicit). У xAI **промо $150 за data-sharing** — бери на выделенный NSFW-аккаунт: этим разблокируется висящая 18+ часть refusal-бенчмарка (эксп.02) и 18+ плечо пилота. Гони на промо-кредитах: (а) 18+ refusal/excision на Grok/DeepSeek/локальной abliterated; (б) Grok в роли 18+-судьи — качество оценки explicit-сцен. Data-sharing только PD/тест-корпус, реальные книги через него не гнать. Grok в евалах — thinking OFF (reasoning аддитивный).
## Память
(секция сессии «Валидация банка памяти» — записи добавлять сюда)
### 2026-07-04 — Сессия «Валидация банка памяти» завершена
Метод: многоагентный ресёрч по 7 направлениям + независимая адверсариальная верификация по первоисточникам + completeness-критик (15 агентов) + локальный эмпирический бенчмарк retrieval на реальном PD-корпусе + чтение кода `backend/`. **Выходы:**
- [research/13-memory-bank-validation.md](research/13-memory-bank-validation.md) — вердикт (что менять, обоснование, источники).
- [architecture/06-memory-risk-registry.md](architecture/06-memory-risk-registry.md) — **реестр рисков (вход бэкенду перед фиксацией схемы store/глоссария Фазы 1)**.
- Эмпирика: `eval/retrieval_bench.py`, `eval/data/retrieval_bench/` (реюз PD-корпуса полигона, не дублируя инфру).
**Вердикт (кратко):** архитектура ДЕРЖИТ, но **go/no-go на саму ставку сейчас нельзя** — центральный детерминированный no-LLM горячий путь **академически нов** (DelTA — LLM-в-цикле per-предложение), сравнение «банк vs длинный контекст» на реальном zh/ja→ru стеке **не проведено**, acceptance-критериев/baseline/cost-модели нет.
**Находки (12 строки):**
- **Q1 Робастность:** опасение владельца уточнено — «пусто» безопасно (норма горячего пути), тихую деградацию порождает **ошибочная инъекция** (модель послушно следует неверной строке, уверенность↑ точность↓ — 2510.00829). Защита: «лучше ничего, чем мусор» + трёхсторонний disposition (accept/verify/reject) + обязательный post-check.
- **Q2 Масштаб:** brute-force KNN держит с огромным запасом — <20 мс на 100k×1024-d (замер); триггеры миграции на порядки выше нужд одной книги.
- **Q3 Корректность retrieval:** детерминированный точный матч — **строго лучше** dense на дословных именах (эмпирика: substring precision 1.0, 0/4 ловушек-омографов; char-BM25 — все 4 ловушки в top-5); dense/reranker/BM25 — только второй эшелон.
- **Q4 Режимы отказа:** DelTA Proper-Noun-Records (first-wins-без-обновления) **сам порождает** stale/contradiction/drift; полная таблица «сценарий→защита→DET/LLM/где в коде» — в реестре.
- **Q5 Детерминизм:** soft>hard по качеству (в т.ч. en→ru), но soft молча теряет 1736% терминов → **post-check обязателен**; слепой post-replace в русский — **запрет** (ломает согласование); `decl` — safety, не фича рынка.
- **Q6 Академика:** DelTA (ICLR 2025) En-центрична, LTCR-1 = самосогласованность (не корректность), слабые базовые модели, **нет zh/ja→ru**; сильнейшее переносимое — Karpinska&Iyyer 2023 (польский прокси ru), но mistranslation/omission персистят даже при идеальном контексте.
- **Q7 Вендоры/эмбеддинги:** memory-числа Mem0/Zep первоисточник не переживают (full-context бьёт Mem0; Zep 84%→58% в исправленной репродукции); эмбеддинг-дефолт **bge-m3** (R@5 0.974 vs Qwen3-0.6B 0.891 vs LaBSE 0.827); **ни одна модель не даёт разделяющего порога** (пересечение распределений) → эмбеддинги low-trust, только кандидаты на ревью.
- **Инвариант D5.2 — подтверждён кодом:** `snapshotID` (`runner.go:145-163`) не хэширует версию глоссария/context-assembly, а `RequestHash` включает `msgs` → после инъекции памяти Фазы 1 изменённый глоссарий тихо промахивается мимо чекпоинта и переоплачивает расходящимся переводом. **Общий гейт Фазы 1 (F1 реестра), закрыть ДО инъекции памяти.**
### [ПИНГИ ОРКЕСТРАТОРУ] расхождения с research/05 / Р3 / Р7
Эта сессия архдоки не правит (кроме своих 13/06) — расхождения выношу пингами:
1. **Р3/research/05 §177 «sqlite-vec нельзя, pure-Go не загрузит C-расширение» — неточно.** Верно только для `modernc.org/sqlite`; CGO-free sqlite-vec **существует** (ncruces WASM). Решение brute-force в MVP верно, но рациональ переписать («держим modernc единым драйвером», а не «pure-Go не может»). → правка формулировки Р3.
2. **Р3/research/05 §7 «один стек LaBSE на выравнивание и RAG» — опровергнуто эмпирически** (LaBSE слабейшая на retrieval, R@5 0.827). LaBSE — только Bertalign; RAG — bge-m3 (дефолт) + Qwen3-Embedding-0.6B (challenger, добавить в стек Р4). → правка Р3/Р4.
3. **research/05 §4.1 «vectorized-активация — все три нужны» — не в горячем пути** (вредна там); только второй эшелон. §5.2 «желательно без эмбеддингов» → **строго без** для дословных терминов.
4. **research/05 §4.2 DelTA / §1.2 Zep / §5.3 constrained decoding — числа/атрибуции поправить** (см. research/13 Q4/Q5/Q6): LTCR-1 = самосогласованность, не качество; DelTA-память LLM-в-цикле (не детерминированная — наша ставка нова); «структурированная память > длинный контекст» — гипотеза, не доказанное; вендор-числа Mem0/Zep убрать; «constrained decoding портит качество» overstated (реальная причина — закрытые API).
5. **Р3/Р7 усилить:** post-check из «флаггера» в **обязательный гейт** (E1/E2 реестра); спойлер-окно `since_ch/until_ch`**safety-гейт с жёстким reject+лог**, не UX-фича; добавить ось «редакционное время» ⊥ спойлер-окну (stale/drift); гейт фактичности **резюме** (у резюме нет post-check-аналога — первоклассный пробел D1).
6. **zh/ja→ru-специфика, не покрытая ни одним доком:** стандарты транслитерации **Палладий (zh)/Поливанов (ja)** (ось корректности ⊥ самосогласованности — крупнейший пробел, B6); утечка рода через **русский глагол прош. вр.** мимо `gender=hidden` (морфо-гейт Ф2 должен сканировать глаголы — C3).
### [БЭКЕНДУ] шаг «миграция памяти v2» разблокирован — с гейтами
Реестр вышел → шаг 4 можно фиксировать, **соблюдая `architecture/06` §Гейты**: (1) **F1 snapshotID под память+context-assembly ДО инъекции** (общий гейт, держит D1/D5); (2) схема D7 + `sense`, ось редакционного времени, `UNIQUE(src,sense,window)`+обратная dst-проверка, `min_key_len`+запрет одиночных ключей, нормализация NFKC/OpenCC/kana/ruby; (3) post-check обязателен, слепой post-replace в ru запрещён; (4) трёхсторонний disposition + per-chunk retrieval-state запись. **Горячий путь = НЕ FTS5** (impl-notes §3.6 уже так; FTS5 не сегментирует CJK), а детерминированный Aho-Corasick/trie. **Ставку на банк памяти НЕ морозить до in-house eval** (протокол/acceptance — research/13 §Вердикт: WMT25-трёхрежимный, baseline «банк vs длинный контекст», предзарегистрированные пороги).
### [ВЕРДИКТ ОРКЕСТРАТОРА] банк памяти — GO на схему+гейты, ставка гейтится eval'ом (параллельно, не блокер)
Валидацию принял (15 агентов + верификация + свой retrieval-бенчмарк — самый провалидированный артефакт проекта). Пинги применены в Р3/Р7 (`01-decisions.md`). Разбор надвое:
- **Схема + детерминированный горячий путь + 5 гейтов реестра — GO, low-regret.** Строим сейчас: структурированный глоссарий нужен как СУБСТРАТ для post-check-гейта («тихое→громкое») в любом случае, спойлер/род-гейты — safety в любом случае, а режим инъекции уже конфиг (`selective|full_prefix`). Даже если eval покажет «длинный контекст победил» — флипается конфиг инъекции, а не схема. Поэтому схема не гейтится eval'ом.
- **Ставка (достаточность селективной инъекции vs полный контекст + whole-book эмбеддинг-слой) — гейтится in-house eval'ом**, но eval идёт ПАРАЛЛЕЛЬНО реализации схемы, не блокирует её. Eval — часть пилотного трека (тот же голд-сет, WMT25-трёхрежимный протокол); фолдим в пилот Ф2.5, не отдельная сессия.
- Ключевая переоценка опасения владельца принята: тихую деградацию порождает не «пусто», а **ошибочная инъекция** — и мы её **конвертируем в громкую** (post-check + disposition + retrieval-state), а не устраняем обещанием. Это и есть ответ на твой страх.
- Крупнейший непокрытый пробел — **транслит-стандарты Палладий/Поливанов** (ось корректности ⊥ самосогласованности, реестр B6) и **утечка рода через русский глагол** (C3): заложены в реестр, Фаза 2.
- **Сессия «Валидация банка памяти» — задача выполнена, закрывается.** In-house eval — уже не её мандат, а пилотного трека.
> **[БЭКЕНДУ]** реестр `architecture/06` — твой контракт для шага 4 (миграция памяти v2). Порядок: **F1 (snapshotID под память+context-assembly) — ПЕРВЫМ** (общий гейт, держит D1/D5, независим от memory-вопроса — код отстаёт от impl-notes §3.2, там уже предписан версия-счётчик); затем схема v2 = D7 + добавления реестра (`sense`, ось редакционного времени, `UNIQUE(src,sense,window)`+обратная dst-проверка, `min_key_len`+запрет одиночных ключей, артефакт нормализации NFKC/OpenCC/kana/ruby); горячий путь = **Aho-Corasick/trie, НЕ FTS5** (impl-notes §3.6 уже так); post-check обязателен, слепой post-replace в ru запрещён; трёхсторонний disposition + retrieval-state запись. Режим инъекции держи конфигом (`selective|full_prefix`) — eval потом решит дефолт. Отложенное в v1.1/Ф2 (заложить поля/место, не реализовывать): Палладий/Поливанов B6, гейт фактичности резюме D1, эмбеддинг-слой A7, бэкап файла F4, морфо-гейт глагольного рода C3.
> **[ПОЛИГОНУ/ПИЛОТУ]** in-house memory-eval — дециждающий эксперимент, фолдится в пилот Ф2.5: WMT25-трёхрежимный (no / correct / **random** terminology — random-рука ловит тихую деградацию) на реальных zh/ja→ru чанках; **обязательный baseline «банк ON vs OFF» и «банк vs длинный контекст frontier-модели»** на том же стеке (DelTA этого не делала — это пробел, который решает go/no-go на ставку); мерить две оси раздельно (самосогласованность approved-форм + source-fidelity/omission через CometKiwi + LLM-судья-против-источника); ru-специфика (согласование рода вкл. глагол, склонение dst); предзарегистрировать пороги + допустимый flag-volume на главу. Эмбеддинг-бенчмарк уже сделан этой сессией (bge-m3 дефолт, `eval/retrieval_bench.py`) — переиспользуй.

View file

@ -27,18 +27,22 @@
- Приоритет силы моделей: **редактор > черновик** (вывод arXiv:2605.13368 — refinement проецирует текст в распределение редактора).
- Пилот 4 рук: ~2535 глав zh/ja/en→ru; решающий критерий — человеческие попарные сравнения (протокол LAIT; для двух систем это pairwise, не BWS — BWS только с ≥3 объектами, третьим якорем берём человеческий перевод) + панель LLM-судей чужих семейств. Правило: если C2/C3 лучше C1 менее чем на 5 п.п. win rate — берём C1 (в 23 раза дешевле). Бюджет: <$50 API + внешние редакторы 2040 ч; **elapsed 23 недели, отдельная фаза** (см. план MVP v2) — подготовка корпуса стартует параллельно с Фазы 0 (работа владельца + сессия «Полигон»).
## Р3. Банк памяти книги — собственный модуль на SQLite. **Принято**
## Р3. Банк памяти книги — собственный модуль на SQLite. **Принято (схема+гейты) / ставка — гейтится in-house eval**
Из [05-memory-glossary.md](../research/05-memory-glossary.md):
Из [05-memory-glossary.md](../research/05-memory-glossary.md), провалидировано сессией «Валидация банка памяти» ([research/13](../research/13-memory-bank-validation.md), реестр рисков [architecture/06](06-memory-risk-registry.md)) — **правки ниже внесены по её пингам**:
- **MemPalace существует, но форкать нельзя**: независимый разбор показал, что заявленные метрики измеряли дефолтные эмбеддинги ChromaDB, «contradiction detection» в коде отсутствует. Generic agent-memory (Letta/Mem0/Zep/LangMem) заточены под чат-память о пользователе и противоположны нужному детерминированному реестру «термин → утверждённый перевод». Банк памяти — ядро продукта, его пишем сами.
- Хранилище: **SQLite (один файл на книгу) + FTS5**. Драйвер — modernc.org/sqlite (CGO-free). **sqlite-vec в MVP не используется** (разрешение конфликта из ревью: это C-расширение, pure-Go драйвер его не загрузит): на масштабе одной книги (тысячи эмбеддингов) достаточно brute-force KNN по BLOB-таблице прямо в Go; если вектора станут узким местом — ncruces/go-sqlite3 (WASM) или cgo, решение отложено. Эмбеддинги (bge-m3 локально) — **второй эшелон, Фаза 2**, и не в горячем пути инъекции.
- Спецификация модуля = **SillyTavern World Info / NovelAI lorebook** (ключи/алиасы, вторичные ключи, токен-бюджет, sticky-инерция сцены, inclusion groups) + **4-уровневая память DelTA** (Proper Noun Records, двуязычные резюме глава/арка/книга, LTM/STM; +4.58 п.п. консистентности имён, +3.16 COMET).
- Схема записи глоссария: `src, dst, type, aliases, gender, speech, decl (склонения для ru), since_ch/until_ch (спойлер-окно), status (approved/auto), note`. Поле `decl` заполняется автоматически при коммите термина (один дешёвый LLM-вызов на термин) — без него метрика консистентности по склоняемому русскому тексту неизмерима.
- Инъекция в промпт — **селективная и детерминированная** (v1: только ключи/алиасы/леммы + sticky, без эмбеддингов; ~100800 токенов), отбор без LLM в горячем пути. Раскладка промпта под кэш — см. Р5.
- Трёхслойная защита консистентности (паттерн GalTransl): pre-replace нормализация → мягкий глоссарий в промпте → post-check + флаг редактору. Жёсткий constrained decoding не применять (WMT: мягкое следование даёт качество выше).
- Series bible — first-class сущность: персонажи, матрица ты/вы с версионированием по главам, речевые профили; в MVP входит **series-bible-lite** (gender + матрица ты/вы) — без него не проверяется главная жалоба ru-читателей MTL (род, регистр обращения).
- Экспорт/импорт TMX/TBX + tab-глоссарии OmegaT — дешёвая фича с высокой ценностью для проф. аудитории.
- **MemPalace существует, но форкать нельзя**: заявленные метрики измеряли дефолтные эмбеддинги ChromaDB, «contradiction detection» в коде отсутствует. Generic agent-memory (Letta/Mem0/Zep/LangMem) — про чат-память о пользователе; при честной методологии их бьёт ванильный RAG (Mem0: full-context точнее по первоисточнику; Zep 84%→58% в исправленной репродукции). Вендор-числа как доказательство для глоссария не использовать. Банк памяти пишем сами.
- Хранилище: **SQLite (один файл на книгу) + FTS5** (FTS5 — НЕ в горячем пути: unicode61 не сегментирует CJK → молча вернёт ничего). Драйвер — modernc.org/sqlite. **sqlite-vec в MVP не используется — но причина в том, что держим modernc единым драйвером и избегаем WASM-миграции, НЕ «pure-Go не может»** (правка формулировки: CGO-free sqlite-vec существует через ncruces/go-sqlite3 WASM). Brute-force KNN подтверждён эмпирически: <20 мс на 100k×1024-d на порядки внутри нужд одной книги. Эмбеддинги **второй эшелон, Фаза 2, low-trust** (см. ниже).
- **Горячий путь — детерминированный multi-pattern матч** (Aho-Corasick / double-array trie по `src`+алиасам над нормализованным чанком; ru/en — по леммам/`decl`), НЕ FTS5, НЕ эмбеддинги. Эмпирика сессии: точный многосимвольный подстрочный матч — **precision 1.0, 0/4 ложных срабатываний на омографах-ловушках** (送灶/修/炎/气); char-BM25/фаззи — все 4 в top-5. Значит одиночные Han/кана как ключ — запрет схемы, longest-match, замена целой сущности.
- Спецификация модуля = **SillyTavern World Info / NovelAI lorebook** + иерархические резюме. ⚠ **DelTA (2410.08143, ICLR 2025) валидирует концепт многоуровневой памяти, но НЕ нашу ставку:** её память — LLM-в-цикле per-предложение, а не детерминированный no-LLM горячий путь; LTCR-1 = самосогласованность (не корректность); нет zh/ja→ru. Её Proper-Noun-Records (first-wins-без-обновления) сам порождает stale/contradiction/drift → наивно нельзя. **Наш детерминированный горячий путь академически нов → гейтится in-house eval (ниже).**
- Схема записи глоссария (D7 v1 + добавления реестра): `src, dst, type, aliases (граф с типами), gender (+hidden until_ch), speech, decl, since_ch/until_ch, status, note, translit_policy` **+ новое из валидации: `sense`** (полисемия/омографы), **ось «редакционное время»** отдельно от спойлер-окна (против DelTA first-wins-навсегда), констрейнты `UNIQUE(src,sense,window)` + обратная проверка dst-коллизий, `min_key_len` per-язык + запрет одиночных ключей, **артефакт нормализации** (NFKC + OpenCC упрощ./трад. + kana-folding + ruby-захват — симметрично к ключам и тексту; «самый вероятный баг тихо-пусто»). `decl`**safety-механизм грамматичности** (не фича рынка): без него форс формы в косвенный падеж ломает согласование.
- Инъекция — **селективная детерминированная** (v1: ключи/алиасы/леммы + sticky, ~100800 токенов). **Принцип «лучше ничего, чем мусор»**: пустой матч — норма, не тревога; бюджет допустимо недозаполнить; фильтр по точности ПЕРЕД приоритетом. Реальная тихая деградация — не пустота, а **ошибочная инъекция** (модель послушно следует неверной строке, уверенность↑ точность↓) → **трёхстороннее решение на каждую запись** (CONFIRMED / AMBIGUOUS+обязательный post-check / REJECT+лог) + per-chunk `retrieval-state` запись (наблюдаемость с первого дня).
- Трёхслойная защита (GalTransl): pre-replace нормализация (только на источнике) → мягкий глоссарий в промпте → **обязательный post-CHECK** (не опциональный флаггер: soft молча теряет 1736% терминов). **Слепой post-replace форм в русский выход — ЗАПРЕТ** (ломает согласование); форс только при инвариантном термине или полном `decl` с разрешимым падежом, иначе флаг. Спойлер-окно `since_ch/until_ch`**safety-гейт с жёстким reject+лог**, не UX-фича.
- Series bible — first-class; series-bible-lite (gender + ты/вы-журнал событий) в MVP.
- Эмбеддинги (Фаза 2): **дефолт bge-m3** (R@5 0.974), challenger **Qwen3-Embedding-0.6B** (переключать только если выиграет на своём held-out zh/ja→ru); **LaBSE — только для Bertalign-выравнивания, НЕ для RAG** (слабейшая, R@5 0.827). ⚠ **ни одна модель не даёт разделяющего порога** (пересечение распределений) → эмбеддинг-слой low-trust, per-книга калибровка, только кандидаты на ревью (флешбеки/редкие термины), не тихая инъекция.
- **Резюме (иерархическая память) — первоклассный пробел:** у резюме нет post-check-аналога, галлюцинация факта компаундится по 1000 глав → отдельный гейт фактичности резюме (Фаза 2, реестр D1).
- **Ставка на банк памяти НЕ морозить до in-house eval:** WMT25-трёхрежимный протокол (no / correct / **random** terminology) на реальных zh/ja→ru + **baseline «банк vs длинный контекст» frontier-модели** (DelTA этого не делала) + предзарегистрированные acceptance-пороги. Eval идёт параллельно реализации схемы (схема low-regret — при «long-context победил» флипается только режим инъекции `selective→full`, а не схема; post-check-гейт нужен в любом случае).
- Экспорт/импорт TMX/TBX + tab-глоссарии OmegaT.
## Р4. Модельный стек и контент-роутинг. **Принято (стартовый состав) / цены и составы — в конфиге**
@ -92,7 +96,7 @@ COGS (прямые ключи, из [experiments/01](../experiments/01-token-cal
- Деньги: дисциплина vojo целиком — биллинг по usage из ответа API (включая reasoning-токены), reserve-before-call/settle-after, потолок $ на книгу/день, request_log per-агент/per-глава/per-стадия, trace_id насквозь.
- Качество онлайн: **CometKiwi** (reference-free, локальная, не умеет отказывать — валидировать на 18+ ru-корпусе до доверия) + периодический GEMBA-MQM-подобный LLM-судья; **сырой BLEU/COMET как KPI художественности запрещён** (LitEval: автометрики отличают человеческий перевод максимум в 20% случаев).
- **Не-LLM гейты по фазам**: Фаза 1 (реализуемо в Go без морфологии) — детектор CJK-артефактов, глоссарная консистентность, coverage-гейт v1 (счёт предложений регэксп-сегментацией `。!?/.!?`, соотношение длин ±порог, blacklist refusal-паттернов; сам гейт проверяется мини-набором из 2030 фрагментов с искусственно выпиленными предложениями), правило «в TM коммитится только вывод, прошедший все гейты». Фаза 2 (Python-сайдкар) — морфодетектор канцелярита по чек-листу Галь (pymorphy/Natasha), MTLD/distinct-n, дисперсия длин, доля пассива, порт BlonDe-подобных дискурсивных проверок на русскую морфологию.
- Метрика глоссарной консистентности — с процедурой: знаменатель = вхождения `src`/алиасов в исходных чанках; числитель = наличие леммы `dst` (по полю `decl`) в соответствующем выводе; прономинализация («Ли Вэй → он») не штрафуется, гейт считает только `approved`-термины; в безлюдном режиме auto→approved автоматом с записью в журнал.
- Метрика глоссарной консистентности — с процедурой: знаменатель = вхождения `src`/алиасов в исходных чанках; числитель = наличие леммы `dst` (по полю `decl`) в соответствующем выводе; прономинализация («Ли Вэй → он») не штрафуется, гейт считает только `approved`-термины; в безлюдном режиме auto→approved автоматом с записью в журнал. **Это post-CHECK и он ОБЯЗАТЕЛЕН** (не опциональный флаггер — soft-глоссарий молча теряет 1736% терминов; правка валидации Р3): главный почти-бесплатный детектор тихой деградации, ловит и «модель проигнорировала термин», и «мы инъектили неверное». **Слепой post-replace форм в русский выход — запрещён** (ломает согласование).
- Оффлайн: паттерн routedecide/routereval из vojo → **golden-set реплей** решений об эскалации и вердиктов судьи со свипом порогов. Золотой набор — приватно из лицензионных изданий; GuoFeng Webnovel V2 — только dev (non-commercial, референсы MTPE-качества).
## Р8. Юридическая и продуктовая позиция. **Принято (переориентировано на EU после коррекции контура)**

View file

@ -0,0 +1,103 @@
# Реестр рисков банка памяти (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 (персонажи из предыдущих 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 | ❌ **не покрыто ни одним доком** — крупнейший zh/ja→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 | **Пропагация ошибки резюме**: галлюцинированный/неверный факт в резюме главы инъектируется как авторитетный контекст и **компаундится по 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) состояние памяти (версия-счётчик approved-глоссария + резюме/series-bible на момент старта джобы — рендер читает **замороженную** версию, не live) и (2) context-assembly-конфиг (`glossary_injection`, `stm_depth`, `overlap`). Тогда рендер снова чист по snapshot, а изменение памяти громко триггерит resnapshot-гейт. impl-notes §3.2 это уже **предписывает** («зафиксированное состояние approved-глоссария (версия-счётчик)») — код отстаёт | DET | `runner.snapshotID()` struct; snapshot payload; сборщик читает pinned-версию | **до инъекции Фазы 1** | ❌ **общий гейт Фазы 1**, держит D1 и D5; закрыть ДО инъекции памяти |
| 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*, но не *популяцию*. Мусор/пропуск главного персонажа на входе → горячий путь верно инъектит мусор / молча опускает — **корневой отказ выше всего измеренного** | С / 🟠🔇 | Мерить precision/recall экстракции на своём голд-сете; first-encounter unknown имя → `status=auto/unconfirmed`, гейт (батч-судья/человек раз в главу) **до** промоушена в approved-ключ, распространяемый на будущие главы; alarm «имя-подобный токен без записи → неизвестная сущность» (DelTA Proper-Noun-Record паттерн) | DET(алярм)+LLM(экстракция)+HUMAN | pre-пасс NER; статус-машина термина | 12 | ❌ популяция не измерена (находка критика) |
| 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).
## 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), а не устраняем её обещанием.**

View file

@ -0,0 +1,238 @@
# Валидация банка памяти книги: вердикт, что менять, обоснование и источники
Дата: 2026-07-04. Сессия **«Валидация банка памяти»** (промт `docs/MEMORY_RESEARCH_SESSION_PROMPT.md`). Предмет — решение **Р3 «Банк памяти книги»** (`architecture/01-decisions.md`) и исследовательская база `research/05-memory-glossary.md`. Мандат: **адверсариально провалидировать** подход до заморозки в коде — не поверить, а попытаться сломать.
Метод: многоагентный ресёрч по 7 направлениям (Q1Q7) + независимая адверсариальная верификация каждой находки по первоисточникам + завершающий completeness-критик + **локальный эмпирический бенчмарк retrieval** на реальном PD-корпусе (`eval/retrieval_bench.py`) + чтение реального кода `backend/`. Все вендорские/бенчмарк-числа перепроверены независимо (как MemPalace в research/05); процитированные корректировки — от верификаторов. Реестр рисков (таблица «сценарий → защита → слой → где в коде») — отдельный файл `architecture/06-memory-risk-registry.md` (единственный, что этой сессии разрешено создать в `architecture/`), он же — вход для бэкенда перед фиксацией схемы.
Все URL проверены на дату исследования. Версии моделей/бенчмарков зафиксированы.
---
## TL;DR — вердикт
**Подход ДЕРЖИТ архитектурно, но с обязательными условиями, и почти ни одно из процитированных research/05 чисел не переносится на zh/ja→ru.**
1. **Опасение владельца уточнено, а не подтверждено буквально.** «Пусто» — не главная угроза: пустой retrieval — **нормальное и безопасное** состояние детерминированного горячего пути (откат к базовому поведению модели). Тихую деградацию порождает **ошибочная инъекция** — модель послушно следует подсунутой неверной строке глоссария как мягкой инструкции, **уверенность растёт, а точность падает** (2510.00829). Дизайн надо переориентировать с «не пусто ли» на «точно ли» и на **обязательный post-check** как детектор.
2. **Горячий путь верен и его надо усилить, а не смягчить.** Детерминированный точный матч ключей — не «дешёвое приближение» dense, а **строго лучший** инструмент для дословных имён собственных (Sciavolino EntityQuestions, 2109.08535). Эмпирика (наш бенчмарк): точный подстрочный матч — **precision 1.0, recall 0.571, 0/4 ложных срабатываний** на ловушках-омографах (送灶/修/炎/气); char-BM25 — все 4 ловушки в top-5. Эмбеддинги/reranker/BM25 на горячем пути — **нет**.
3. **Эмбеддинг-слой — low-trust, только Фаза 2, только кандидаты на ревью.** Эмпирически подтверждено: даже лучшая модель (bge-m3) **не даёт разделяющего порога** — распределения релевантных и мусорных cosine пересекаются (rel_min 0.374 < irr_p95 0.447), при precision0.9 recall падает до 0.54. Единый глобальный порог тихо гейтить не может (совпадает с репродукцией CRAG, 2603.16169). Эмбеддинги генератор кандидатов для человека (флешбеки, редкие термины), не тихая инъекция.
4. **Академический якорь честнее, чем в research/05.** DelTA (2410.08143, **ICLR 2025**) реален, но: (а) полностью En-центричен — **нет русского, нет ja/zh→ru**; (б) флагманская метрика LTCR-1 меряет **самосогласованность, а не корректность** — консистентно-неверное имя даёт 100%; (в) прирост COMET +3.16 — на слабых/средних базовых моделях (GPT-3.5/4o-mini/Qwen2); (г) память DelTA **LLM-в-цикле per-предложение**, а не детерминированная. То есть DelTA валидирует *концепт* многоуровневой памяти, но **не** нашу ставку «детерминированный no-LLM горячий путь» — она **академически нова и не подтверждена**. Это первая строка любого go/no-go.
5. **Детерминизм: soft>hard по качеству подтверждён (в т.ч. в русский), но soft молча теряет 1736% терминов** (2310.05824) → **post-CHECK обязателен**, а слепой post-replace в русский выход **запрещён** (ломает согласование). Поле `decl` — не «фича ru-рынка», а safety-механизм грамматичности.
6. **Стек по масштабу — в разы с запасом.** Brute-force KNN: <20 мс на 100k×1024-d (наш замер); триггеры миграции на порядки выше нужд одной книги. Одна поправка: sqlite-vec **доступен** CGO-free через ncruces (причину отказа в Р3 переписать).
7. **Итог: строить свой банк памяти — верно** (все вендорские agent-memory числа при честной методологии бьёт ванильный RAG; домен чужой). Но **числа не переносятся** → перед заморозкой ставки нужен **свой замер zh/ja→ru с acceptance-критериями и baseline «банк vs длинный контекст»** (§Вердикт). Реестр рисков делает провалы **явными** — это и есть ответ на опасение владельца: тихую деградацию мы **конвертируем в громкую**, а не устраняем обещанием.
---
## Q1. Робастность retrieval — центральный вопрос
**Что делают зрелые системы при пустом/слабом/нерелевантном retrieval и как сделать деградацию явной и безопасной.**
**Находка 1 — near-miss дистрактор вреднее пустоты.** «The Power of Noise» (SIGIR 2024, 2401.14887) и Yoran et al. (ICLR 2024, 2310.01558) показывают: семантически близкий, но неответный документ роняет точность существенно (в отдельных случаях до 67%), тогда как пустой контекст лишь возвращает модель к базовому поведению; NLI-фильтр устраняет просадку ценой части релевантных. RGB-бенчмарк (Chen et al., AAAI 2024, 2309.01431) прямо операционализирует нужные нам оси — **Noise Robustness** и **Negative Rejection** (поведение при пустом/полностью нерелевантном retrieval) — и имеет **китайский** тестбед (ближе к нам, чем англоязычные работы). → Принцип **«лучше ничего, чем мусор»**: не добивать токен-бюджет соседями; бюджет допустимо недозаполнить; фильтр по точности **перед** сортировкой по приоритету.
> ⚠ Поправка верификатора: контринтуитивный результат Power of Noise «случайный шум помогает (+35%)» специфичен для extractive-QA и **не** оправдывает набивку глоссария низкорелевантными записями — строка глоссария это директива, а не нейтральный филлер.
**Находка 2 — пороги уверенности хрупки.** Независимая репродукция CRAG (2603.16169) показала: оценщик релевантности ведёт себя как детектор **пересечения сущностей**, а не семантической релевантности; высокая уверенность ≠ корректность; пороги **не переносятся** между наборами (на OOD-данных 88.3% классифицированы как «Ambiguous» на нестроенных порогах). zh/ja→ru литература — и есть сдвиг распределения. **Наша эмпирика это подтверждает напрямую** (см. §Q2/Q3, таблица): у лучшей модели bge-m3 ROC-AUC=0.970, но релевантные (rel_mean 0.555, min 0.374) и мусорные (irr_mean 0.357, p95 0.447) cosine **пересекаются**; чтобы держать precision≥0.9, порог cos≥0.547 срезает recall до 0.54. Единого глобального порога, тихо отделяющего сигнал от мусора, **нет**.
**Находка 3 — безопасный паттерн: явное трёхстороннее решение + post-check.** CRAG (2401.15884), Self-RAG, Adaptive-RAG сходятся на явной диспозиции **accept / verify / reject** с разными действиями по ветке. Переносимый вклад — не конкретные пороги, а **машинерия диспозиции** (логировать/маршрутизировать/отклонять). Плюс дешёвый пост-хок-чек фактичности (RAGAS-триада; у нас — GalTransl layer-3 / post-check).
**Рекомендованное поведение пайплайна (явная безопасная деградация):**
- Пустой ключевой матч → инъектить **только** активные sticky-записи; ничего не добавлять. «Пусто» — норма, не тревога.
- Трёхстороннее решение **на каждую запись-кандидат**: `CONFIRMED` (точный ключ/лемма, `approved`, спойлер-валидна) → авторитетно; `AMBIGUOUS` (короткий/общий алиас, коллизия поверхности, `auto/draft`, эмбеддинг в серой зоне) → инъекция **с пометкой «unverified — проверить» + принудительный post-check**; `REJECT` (спойлер-нарушение / ниже планки) → выкинуть и **залогировать**.
- **Реальная тревога** — не пустота, а: чанк содержит имя-подобные/CJK-name-pattern токены, не совпавшие **ни с одной** записью и **ни с одним** sticky → неизвестная сущность → авто-экстракция + подтверждение (DelTA Proper-Noun-Record паттерн). Никогда не «перевести и забыть» новое имя молча.
- Спойлер-окно `since_ch/until_ch`**жёсткий reject с логируемым событием** на обоих путях (не UX-фича, а safety-гейт).
- Per-chunk `retrieval-state` запись (`n_exact_hits, n_sticky, n_ambiguous_flagged, n_spoiler_blocked, embedding_tier_used`) — наблюдаемость деградации с первого дня.
> **Расхождение с research/05:** эмбеддинг-«второй эшелон» подан как безобидный, без порога/воздержания; доказательства сильнее — он должен быть явно **low-trust, per-книга калиброван, только кандидаты на ревью**. «Лучше ничего, чем мусор» не сформулирован как принцип. Спойлер-окно — safety-гейт, а не продуктовая фича.
> ⚠ Честная граница: весь корпус доказательств Q1 — англоязычный QA/MT; **никто не мерил вред неверной строки глоссария на zh/ja→ru прозе**. Магнитуду даёт только свой замер (§Вердикт).
## Q2. Адекватность стека под масштаб
**Держит ли SQLite + brute-force KNN + bge-m3 вебновеллу в тысячи записей × сотни глав; где триггеры миграции.**
**Держит с большим запасом.** Эмпирика (`retrieval_bench`, чистый numpy, single query, top-10, медиана):
| dim | N=1k | N=10k | N=50k | N=100k | N=200k |
|---|---|---|---|---|---|
| 768 (LaBSE) | 0.02 мс | 0.26 мс | 6.1 мс | 12.3 мс | 25.3 мс |
| 1024 (bge-m3/Qwen3) | 0.02 мс | 2.0 мс | 9.3 мс | **19.6 мс** | 33.2 мс |
Одна книга — единицы тысяч записей глоссария + десятки тысяч векторов чанков — **на порядки внутри зоны «brute-force норм»**. Первоисточник-якорь: C/SIMD sqlite-vec делает 100k×1024-d <75 мс; его **скалярный** вариант (аналог рукописного Go-цикла) 81 мс на 1M×128-d, 108 мс на 500k×960-d то есть даже без SIMD на нашем N это низкие десятки мс.
**Триггеры миграции (конкретно):**
- **T1 (самый дешёвый, остаться CGO-free + один файл):** per-query N → ~100k при 1024-d или p95 скалярного скана > ~150200 мс → перейти на SIMD-brute-force sqlite-vec через **ncruces/go-sqlite3 + asg017/sqlite-vec-go-bindings/ncruces** (pure-Go WASM, официально, stable v0.35.1). ×310 без сервера. Реальная цена — миграция всего драйвера с modernc (FTS5+хранилище), оценивать заранее.
- **T2 (квантизация до индекса):** перед ANN — int8/binary векторы (sqlite-vec: 100k×3072-d bit ~11 мс, Hamming, ×32 меньше памяти) как грубый проход + re-rank top-k float. Скорее всего снимает нужду в ANN на нашем масштабе вовсе.
- **T3 (ANN, всё ещё embedded):** per-query N > ~500k1M или интерактивный SLO <20 мс p99 sqlite-vec ANN (планируется) / hnswlib. Актуально только если скоуп вырастет далеко за одну книгу.
- **T4 (реальный вектор-DB/сервер):** только когда продукт — единый library-wide индекс на много книг (агрегат миллионы векторов) или нужны конкурентные писатели / высокий QPS при 99%+ recall → pgvector+pgvectorscale (если уже Postgres) / Qdrant (50M+). **Явно жертвует гарантией «один SQLite-файл на книгу»** — не пересекать ради per-book масштаба.
- **T5 (память, не латентность):** float32 1024-d = 4 КБ/вектор → 100k резидентных = ~400 МБ RAM на открытую книгу; при многих открытых книгах это упрётся раньше латентности → mmap/on-disk или int8.
**Важная поправка (расхождение с research/05 и Р3):** «sqlite-vec нельзя, pure-Go не загрузит C-расширение» — **верно только для `modernc.org/sqlite`**, не как свойство pure-Go. CGO-free sqlite-vec **существует** (ncruces WASM). **Решение (brute-force в MVP) остаётся верным**, но рациональ переписать: реальная причина — «держим modernc единым драйвером / избегаем WASM-миграции», а не «pure-Go не может». bge-m3 (~560600M, XLM-R-large, 1024-d, ~2.22.5 ГБ FP32) влезает в 8 ГБ GTX 1070, но **эмбеддинг держать строго офлайн/батч** (Pascal без быстрого FP16; наш замер — bge-m3 ~78 мс/текст на CPU, Qwen3-0.6b **~800 мс/текст**) — на горячем пути недопустимо.
> Позитивный факт: весь Q2 — чистая инфраструктура (латентность/драйвер/VRAM), **без zh/ja→ru зависимости** — «англо-доказательства не переносятся» здесь не применимо. Единственное языко-чувствительное — **качество** retrieval bge-m3 (Q3/Q7).
## Q3. Правильность задачи retrieval — dense vs гибрид vs детерминизм
**Чистый dense — оптимум, или нужен гибрид (BM25/FTS + dense + reranker)?**
**Разделение дизайна верно, и доказательства велят сделать его резче.**
**Горячий путь (дословный термин в чанке): dense строго ХУЖЕ детерминизма.** Dense молча промахивается мимо редких имён собственных (ровно тот тихий провал, которого боится владелец); Sciavolino «Simple Entity-Centric Questions Challenge Dense Retrievers» (EMNLP 2021, 2109.08535) — канонический первоисточник: exact/lexical бьёт dense именно на **редких, отличительных** строках (=наш случай имён собственных). BM25/FTS5 туда тоже не годятся: **дефолтный токенайзер FTS5 (unicode61) не сегментирует CJK → молча возвращает ничего**. Reranker — тоже нет. Это **не «дешевле», а корректнее и с более высоким recall**. Наша эмпирика (13 gold-запросов, 4 ловушки):
| Метод | R@1 | R@3 | R@5 | MRR | nDCG@5 | ловушки в результате | точность |
|---|---|---|---|---|---|---|---|
| **substring-exact** (горячий путь Р3) | — | — | — | — | — | **0/4 срабатываний** | **precision 1.0, recall 0.571** |
| bm25-char (лексика) | 0.40 | 0.73 | 0.92 | 0.95 | 0.87 | **4/4 в top-5** | — |
| **bge-m3** (dense) | 0.40 | 0.86 | **0.97** | 0.96 | **0.94** | 0/4 инъектир. при t* | AUC 0.970 |
| Qwen3-Embedding-0.6B | 0.40 | 0.87 | 0.89 | 0.96 | 0.88 | 0/4 при t* | AUC 0.953 |
| LaBSE | 0.36 | 0.72 | 0.83 | 0.91 | 0.81 | 0/4 при t* | AUC 0.931 |
Читается так: точный подстрочный матч **никогда не ошибается** (precision 1.0) и **не ловится на омографы** (0/4), но **пропускает** семантику/кросс-язык/резюме (recall 0.571) — это его роль. char-BM25/фаззи-ключи ловят **все 4 ловушки** (送灶-обряд на «送到灶», 修为 на «修好», 萧炎 на «炎势», 斗气 на «雾气») — почему одиночные/фаззи ключи на горячий путь пускать нельзя. Dense **не** инъектит ловушки при пороге precision≥0.9 — но **только потому, что порог высок** (сырые cosine ловушек 0.390.54 лежат **внутри** диапазона релевантных, rel_min 0.374); dense избегает ловушки ценой recall, а substring — бесплатно. → горячий путь остаётся детерминированным.
**Второй эшелон (семантика: перефраз, алиасы, «где это было», флешбеки): гибрид + опциональный reranker.** Чистый dense суб-оптимален; hybrid (лексика + dense, RRF k=60 — Cormack et al. SIGIR 2009) + опциональный bge-reranker-v2-m3 на top-3050 — продакшн-стандарт. Прирост гибрида над dense на мультиязычной семантике реален, но **скромен** (bge-m3 на MIRACL: dense 67.8 → dense+sparse 68.9 → all-three 70.0 nDCG@10). Хитрость: bge-m3 отдаёт dense+sparse+ColBERT за **один forward** — гибридную лексическую руку второго эшелона можно взять из его же sparse-весов, минуя отдельный CJK-токенизированный BM25.
> **Расхождения с research/05:** (1) §4.1 «constant/keyed/**vectorized** — все три нам нужны» — vectorized-активация **не** нужна (и вредна) в горячем пути, только во втором эшелоне; scope-ить по слоям. (2) §5.2 «желательно без эмбеддингов» → **строго без** для дословных терминов (substring не приближение dense, а лучший метод). (3) FTS5-ловушка CJK-токенизации в research/05 не помечена — при опоре на FTS5 для «полнотекст/ключи» это тихий провал; нужен bigram/jieba токенайзер, если FTS5 всё же используется.
> ⚠ Поправки верификатора: ни один процитированный retrieval-бенчмарк не меряет **русский** (MIRACL zh/ja без ru; T2-RAGBench английский) — ru-сторона (bilingual dst, ru-резюме) числами не покрыта. Плюс ja-специфика: чистый substring молча промахивается по орфо-вариантам (кандзи/кана/ромадзи 鈴木/すずき/スズキ, полу/полноширина) — нужна NFKC-нормализация + алиасы (см. реестр A4); а zh-substring рискует ложным вложением короткого термина в длинное слово (нет границ слов) — нужен longest-match/Aho-Corasick, «горячий путь не так тривиально точен».
## Q4. Специфические для перевода режимы отказа
Полная таблица «сценарий → защита → DET/LLM/HUMAN → где в коде/схеме» — в **`architecture/06-memory-risk-registry.md`** (разделы BD). Ключевые находки этой сессии:
**Самая опасная — академический якорь сам порождает отказы.** DelTA **Proper Noun Records = first-translation-wins без обновления и без детекции конфликтов**; первое (наименее информированное) вхождение имени лочит рендеринг на 1000 глав. То есть механизм — **генератор** режимов **stale / contradictory / drift**, а не защита от них. И решающий довод: LTCR-1 меряет **консистентность, не корректность** — консистентно-неверное имя даёт 100%, метрика **структурно не может** оштрафовать stale-рендеринг, который сама же и производит.
По каждому режиму (кратко; детерминированный слой vs LLM):
- **Stale/drift:** append-only журнал ревизий рендеринга **отдельной осью** от спойлер-окна (Zep bi-temporal как **паттерн** в SQLite, без Neo4j) + content-hash в snapshot + ReTranslation-by-keyword. `DET`+`HUMAN`.
- **Contradictory:** `UNIQUE(src,sense,window)` + обратный `GROUP BY dst`; LLM-адъюдикация **раз в главу батчем**, не в горячем пути. `DET`+`LLM(батч)`.
- **Alias:** alias-граф с типами + name-tables + NER-гейт на новую форму имени. `DET`+`LLM`+`HUMAN`.
- **Wrong-sense (омографы/полисемия 阿Q, 送灶, 炎, 道/气/修):** запрет одиночных ключей как schema-констрейнт + longest-match + замена целой сущности + условные judgment-words (GalTransl post-dict). Эмпирически подтверждено (Q3). `DET`.
- **Спойлер:** жёсткий reject с логом на обоих путях. `DET`.
- **Молчаливо-пустой матч из-за орфографии** (упрощ./трад., полу/полноширина, кана, ruby) — **самый вероятный баг «тихо пусто»**: обязательный слой нормализации (NFKC + OpenCC + kana-folding + ruby-захват) симметрично к ключам и тексту. `DET`.
**LLM — строго вне горячего пути инъекции.** Роли LLM: авто-экстракция кандидатов, батч-адъюдикация конфликтов, остаточный sense-check, ревью гендер-твиста — всё **раз в главу**, никогда per-chunk.
> **Расхождения с research/05:** §4.2 подаёт DelTA-PNR как позитив для автопополняемого глоссария, не отмечая, что это first-wins-без-обновления (генератор stale/contradiction/drift). §1.2 приписывает Zep «63.8% LongMemEval vs 49.0% Mem0» — первоисточник Zep (2501.13956) даёт 71.2% vs full-context 60.2% и **не** сравнивает с Mem0; цифра из вторички. Ценное у Zep — не тяжёлый стек, а **лёгкий паттерн** (invalidate-not-delete bi-temporal + write-time LLM-чек), реализуемый в SQLite.
> ⚠ zh error-таксономия, на которую опирается дизайн (DITING, 2510.09116), — zh→**En** only; её вывод «китайские модели бьют западные» — англо-таргетный, для ru-таргета сигнала ноль. Разрыв переноса шире, чем «нет русского в DelTA».
## Q5. Гарантия детерминизма реестра
**Трёхслойная защита GalTransl (pre-replace → мягкий глоссарий → post-check) против литературы constrained decoding / soft-following.**
**Ставка держит, но research/05 формулирует её слишком резко.**
1. **Soft > hard по КАЧЕСТВУ — подтверждено, в т.ч. в русский.** DuTerm (arXiv 2511.07461, «It Takes Two: A Dual Stage Approach for Terminology-Aware Translation», **en→ru**) и WMT23 (COMET-QE: LLM-refine > constrained decoding на всех парах). НО:
2. **Soft ПРОИГРЫВАЕТ по adherence:** промптинг оставляет **1736% терминов молча неиспользованными** (2310.05824) — **поэтому post-check обязателен, а не опционален**. Формулировка research/05 §5.3 «soft лучше по обоим (accuracy И fluency)» **не поддержана**: soft меняет adherence на fluency, и post-check — именно это чинит.
3. **Самая важная адверсариальная находка, отсутствующая в research/05, и точно бьющая в опасение владельца:** LLM **сверх-доверяет** инъектированному термину — при неверной/шумной записи внимание уходит с источника, а **уверенность модели растёт при падении точности** (2510.00829). → горячий путь переориентировать на **precision-first** отбор и **громкий флаг** подозрительных инъекций.
4. **Русская морфо-опасность к СОБСТВЕННОМУ post-replace-слою:** форс словарной формы в косвенный падеж ломает согласование (**46% ошибок constrained-моделей в en-cs — согласование**, 2106.12398; лемма+самостоятельное склонение даёт **+47.7 п.п.** term accuracy в латышский — Bergmanis&Pinnis). → **`decl`+`gender` — не фича ru-рынка, а safety-механизм грамматичности**; слепой регэксп-replace форм в русский выход **запретить**; форс только при инвариантном термине ИЛИ полном `decl` И разрешимом падеже, иначе флаг.
5. **«Жёсткий constrained decoding = деградация» — overstated.** ~7 BLEU теряет **наивный** Grid Beam Search; plug-and-play (Cascaded Beam Search, 2305.14538) даёт ~0 BLEU-потерь и EMA **0.925 на en-ru** (vs baseline 0.753). Реальная причина не использовать — **недоступен через закрытые frontier-API** (нужен доступ к beam/декодеру), а **не** «портит качество».
**Грань «жёстко нельзя / мягко можно»:** hard find-replace — **только на источнике** (нормализация к каноническому ключу, CJK без пробелов надёжно); в **русском выходе — только soft + post-check + флаг**; hard-форс формы в выход — лишь для морфологически инвариантного термина или при полном `decl` с разрешимым падежом. Для критических «никогда-не-варьировать» имён — не hard-декодинг, а **демонстрация** (короткое прежнее переведённое предложение с именем в нужном падеже — 2503.05010: demonstrations > bare terminology, хотя это de-en).
> ⚠ Поправки цитирования (верификатор): research/05 §5.3 ссылается на 2503.05010 как «soft>hard» — та работа про retrieval-vs-generation (de-en), не про soft-vs-hard; заменить на 2310.05824 + 2511.07461. «Все три сходятся» (2024.wmt-1.51 / 2503.05010 / 2511.07461) — overclaim: сравнение soft-vs-hard делает только 2511.07461. **Языковой перенос — крупнейшая оговорка всего Q5:** все результаты — с латинским/английским ИСТОЧНИКОМ (en→ru, en→lv, en→cs); **ни один не оценивает zh/ja источник и zh/ja→ru**. Ставка унаследована по аналогии; уверенность в zh/ja→ru снизить.
## Q6. Академическое заземление
**DelTA воспроизводима? На чём мерено? Переносима на zh/ja→ru? Литература document-level MT / RAG-failure / long-context. Свежие 20252026.**
**DelTA (2410.08143, ICLR 2025):** реальна, код+данные выпущены. Но:
- **8 направлений, все X↔English; китайский и японский — только в паре с English; русского нет нигде.** Заявленное «+4.58 п.п. LTCR-1» — это **Xx→En среднее** по 4 backbone-моделям (En→Xx среднее +4.36; до +6.17 на отдельной паре), под рамкой «up to … on average».
- **LTCR-1 = самосогласованность, не корректность** (по формуле; выровнена с механизмом Proper-Noun-Records почти по построению). Независимый сигнал качества — только COMET **+~3.1**, и то на **слабых/средних** моделях (GPT-3.5-turbo, GPT-4o-mini, Qwen2), не frontier.
- **Память DelTA — LLM-в-цикле:** LTM-ретривер (n=2 из последних l=20 предложений) + двуязычное резюме (обновляется каждые m=20) — **вызовы модели per-предложение**, не детерминированный no-LLM горячий путь. Дорого/медленно на книжном масштабе.
- **Валидировано на коротких документах** (IWSLT2017-talks, одиночные главы Guofeng), **никогда на 1000-главном сериальном каноне** — «first-wins на 1000 глав» — непроверенная экстраполяция масштаба. «структурированная память > длинный контекст» **DelTA не A/B-тестировала против long-context frontier-модели** — это гипотеза, не установленный факт (research/05 подаёт как доказанное).
- Лицензию Apache-2.0 из репозитория подтвердить **не удалось** — перепроверить перед реюзом.
**TransAgents (2405.11804):** «предпочитают GPT-4 и человеческим референсам» — технически верно, но слабо: человеческое предпочтение **маргинально**; сильные 66-vs-30 — от **GPT-4-как-судьи** (self-preferential); d-BLEU **катастрофически низкий** (25 vs 4352 — большой дрейф от источника); авторы **сами предупреждают**, что человеческие референсы могли быть Google Translate, а оценщики — не реальные читатели вебновелл; **zh→En only**. Реюзать только проверенный вклад — **подготовительную стадию «translation guidelines / story bible»**; не позиционировать продукт как «за пределами человека» (NAACL 2025: LLM всё ещё проигрывают проф. литпереводчикам 80100% под BWS).
**Сильнейшее переносимое доказательство ставки на память — не DelTA, а Karpinska & Iyyer (2023):** контекст уровня абзаца предпочитают ~7178% и он режет ошибки инконсистентности **25→2**, мерено на **ja/zh-источнике и морфологически богатых японском/польском таргетах** (**польский — лучший доступный прокси для русского** по роду/падежу). НО та же работа: **mistranslation и omission ПЕРСИСТЯТ даже при идеальном контексте** — прямая валидация опасения владельца: **память необходима, но недостаточна**, post-check/редактор сохранять.
> ⚠ Уточнение: в Karpinska&Iyyer русский — только **источник** (ru-en/ru-ja/ru-pl), никогда таргет; переводчик — GPT-3.5 (2023). Даже сильнейшее переносимое доказательство содержит **ноль ru-таргет-оценок**.
**Свежее (20252026):** **WMT25 Terminology** («Terminology is Useful Especially for Good MTs», aclanthology 2025.wmt-1.30) — sentence-level решён, **document-level (наш кейс) НЕ решён даже с глоссарием**; Track 1 включает **En→Ru** (ближайшее к русской терминологии во всём корпусе), Track 2 — doc-level En↔Trad.Chinese с one-to-many словарём (аналог нашего). Ключевой нюанс: **глоссарий помогает СИЛЬНЫМ системам больше** — payoff памяти условен на качестве базовой модели (важно для выбора стека). Их **трёхрежимный протокол (no / correct / RANDOM terminology)** — готовый шаблон адверсариального замера «помогает ли инъекция vs вредит», с «random»-рукой как проба тихой деградации.
> **Расхождения с research/05** (сведены): «+4.58 п.п.» подать с точной атрибуцией (Xx→En среднее); LTCR-1 назвать самосогласованностью, не качеством; «LTM = retrieval по всему документу» → ограниченное онлайн-окно; TransAgents-предпочтение смягчить; «структурированная память > длинный контекст» — гипотеза, не доказанное; DelTA-память **не детерминированная** (LLM per-предложение) — наша no-LLM ставка академически нова.
## Q7. Скептицизм к вендорам и эмбеддингам
**Независимая перепроверка вендорских memory-чисел и выбор эмбеддинг-модели для zh/ja/ru.**
**Вендорские «memory»-числа не переживают первоисточник и меряют не то.** Они про recall фактов о **пользователе** между чат-сессиями, не про детерминированный реестр «термин→перевод»:
- **Mem0** (ECAI 2025, 2504.19413): бенчмарк — **LOCOMO, не LongMemEval**; в собственной таблице **full-context (72.9% J) БЬЁТ Mem0 (66.88%)** по точности — выигрыш Mem0 только в токенах/латентности. «94.4% LongMemEval @ ~7k токенов» — отдельный пост-пейпер-маркетинг.
- **Zep** (2501.13956): DMR 94.8 vs MemGPT 93.4 — реально, но Zep **сам** называет DMR неадекватной; независимая исправленная репродукция LOCOMO **обрушила** число Zep (getzep issue #5: 84%→58.44%, методологическая критика — single-run vs 10-run, first-4 категории, system_prompt).
- Вывод: **не класть ни одно вендор-число в доки как доказательство для глоссария**; при стандартизованной методологии их бьёт ванильный RAG. Цитировать только как «оценено, отвергнуто — чужой домен». (Свежий вход: LoCoMo — быстро-обновляемая гонка самозаявленных чисел, любая одна цифра одноразова.)
**Эмбеддинги для zh/ja/ru (мандат сессии) — эмпирический замер, `retrieval_bench` (CPU, реальный корпус):**
| Модель | dim | R@5 | nDCG@5 | ROC-AUC | recall@prec≥0.9 | encode CPU | вердикт |
|---|---|---|---|---|---|---|---|
| **bge-m3** | 1024 | **0.974** | **0.941** | **0.970** | 0.54 | ~78 мс/текст | **дефолт Фазы 2** |
| Qwen3-Embedding-0.6B | 1024 | 0.891 | 0.881 | 0.953 | 0.14 | ~800 мс/текст | challenger |
| LaBSE | 768 | 0.827 | 0.806 | 0.931 | 0.20 | ~27 мс/текст | **только alignment** |
- **bge-m3 — дефолт:** лучший R@5/nDCG/AUC; sparse-голова делает точный матч имён собственных, которую dense-only модели упускают; 8k контекст под резюме; 1024-d держит brute-force BLOB дёшево; опубликован MIRACL zh/ja/ru dense 67.8.
- **Qwen3-Embedding-0.6B** (2506.05176, MMTEB 64.33 — **highest open-weight**, Gemini Embedding 68.37 выше) — держать **challenger'ом**: у нас MRR/ранняя точность близки, но **recall@prec≥0.9 = 0.14** (в режиме высокой точности заметно хуже bge-m3), dense-only (нет sparse), тренирован на ~150M **синтетических** парах от самого Qwen3 (риск MMTEB-оверфита/self-distillation). Переключаться только если выиграет на **своём** held-out zh/ja→ru сете. На CPU ×10 медленнее bge-m3.
- **LaBSE — только для Bertalign-выравнивания, НЕ для RAG** (эмпирически слабейшая: R@5 0.827; документирована слабость на асимметричном short-query→long-passage). → расхождение с research/05 §7 «один стек на выравнивание и RAG».
- **Общий вывод по Q1/Q3:** **ни один** эмбеддинг не даёт разделяющего порога на нашей задаче (пересечение распределений; при prec≥0.9 recall 0.140.54) → эмбеддинг-слой low-trust, per-книга калибровка, кандидаты на ревью; спор «какая модель» **вторичен** для MVP (горячий путь детерминирован).
> **Расхождения с research/05:** убрать вендор-числа (Mem0 «94.4%/~6787 ток.», Zep «63.8% vs 49.0%») — не поддержаны первоисточником; LaBSE не реюзать для RAG; добавить Qwen3-Embedding-0.6B как основного challenger'а bge-m3.
> ⚠ Честная граница: MIRACL/CMTEB/MMTEB включают русский, но как **внутриязыковой** retrieval (ru→ru), **не** кросс-язык zh/ja-запрос→ru-глоссарий — истинную задачу дизайна **ни один публичный бенчмарк не меряет**; свой кросс-язык-замер обязателен.
---
## Cross-cutting: пробелы, найденные completeness-критиком (не покрыты отдельными Q)
Все — первоклассные строки реестра рисков (`architecture/06`, разделы D/F/G):
1. **Пропагация ошибки резюме** — вторая половина банка почти не рассмотрена; у резюме **нет post-check-аналога**, галлюцинация факта компаундится по 1000 глав (реестр D1).
2. **Качество автоэкстракции (bootstrap)** — валидировали retrieval, не популяцию; мусор/пропуск на входе — корневой отказ выше всего измеренного (G1).
3. **Token-budget eviction** — какая запись выпадает при превышении бюджета не задано; выпавшая нужная = тихая деградация (F2).
4. **Человеческая ёмкость** — почти каждая защита кончается «флаг → человеку»; объём флагов на главу не оценён; при высоком flag-rate «route to human» пусто (G2).
5. **Конкурентность/порядок в SQLite** — параллельный перевод ломает first-wins/sticky (последовательные по предположению) (F3).
6. **Коррелированный отказ судьи-экстрактора** — одна модель на одних трудных кейсах → тихо промоутит плохие approved-ключи (F5).
7. **SPOF одного файла** — повреждение теряет весь банк молча (F4).
8. **zh/ja→ru-специфика:** стандарты транслитерации **Палладий (zh) / Поливанов (ja)** — имя может быть внутренне-консистентным, но систематически неверным по стандарту (ось корректности ⊥ самосогласованности) — **крупнейший непокрытый пробел** (B6); утечка рода через **русский глагол прош. вр.** («сказал/сказала») мимо `gender=hidden` (C3); склонение транслит-CJK-имён нетривиально (C4); гоноративы/регистр ты/вы (реестр).
9. **Противоречие «горячий путь = FTS5 vs trie»** — разрешено: **не FTS5** (impl-notes §3.6 уже так решает; FTS5 не сегментирует CJK), а детерминированный Aho-Corasick/trie матч (реестр §Контракт горячего пути).
## Инвариант робастности D5.2 — подтверждён кодом (критично)
Промт требовал провалидировать инвариант «`snapshotID` покрывает состояние памяти + сборку контекста». **Подтверждаю против реального кода — дыра реальна:**
- `snapshotID` (`backend/internal/pipeline/runner.go:145-163`) хэширует brief_hash, chunker/estimator-версии, pipeline_core, defaults (max_output_ratio/min_max_tokens) и per-stage план (model, prompt_version, PromptSHA256, temperature, reasoning, ExtraBody, provider-оверрайды) — **но НЕ версию approved-глоссария и НЕ параметры сборки контекста** (`glossary_injection`/`stm_depth`/`overlap`).
- `RequestHash` (`render.go:158`) включает контент `msgs`; `render.go:23-29` декларирует рендер **чистой функцией snapshot**; `Messages` (`render.go:133-136`) прямо говорит: инъекция глоссария/STM встанет между system и user в Фазе 1.
- Следствие: как только память попадёт в `msgs`, изменённый глоссарий/STM меняет `request_hash`, но **не** `snapshotID` → resnapshot-гейт (`runner.go:260`) молчит → ре-ран старой главы промахивается мимо чекпоинта и **переоплачивает расходящимся переводом**, ломая инвариант Р6, на который D5 опирается. impl-notes §3.2 это уже **предписывает** («зафиксированное состояние approved-глоссария (версия-счётчик)») — **код отстаёт от доков**.
- **Фикс (реестр F1):** свернуть в snapshot **оба** — (1) версию-счётчик approved-глоссария + резюме/series-bible (рендер читает **замороженную** версию, не live) и (2) context-assembly-конфиг. Общий гейт Фазы 1, держит D1 и D5; закрыть **до** инъекции памяти.
---
## Вердикт: держит с условиями. Что менять.
**GO на архитектуру** (lorebook-активация + иерархические резюме + ограниченное окно STM + 3-слойная защита) — она peer-reviewed (DelTA ICLR 2025) и подтверждена растущей линией 20252026 (Karpinska, WMT25). **Но go/no-go на саму ставку нельзя выдать сейчас** — центральный **детерминированный no-LLM горячий путь академически нов и не валидирован** (DelTA — LLM-в-цикле), единственное сравнение, что имеет значение (**банк vs длинный контекст на реальном zh/ja→ru стеке**), **не проведено**, acceptance-критериев/baseline/cost-модели нет.
**Обязательно до заморозки схемы (входит в реестр `architecture/06` §Гейты):**
1. **F1 snapshotID под память + context-assembly — до инъекции Фазы 1** (тихий расходящийся ре-пэй).
2. **Post-check из E1/E2 — обязательный гейт**, не опциональный флаггер; слепой post-replace в русский — запрет; `decl` как safety.
3. **Трёхсторонний disposition инъекции + per-chunk retrieval-state** запись (наблюдаемость).
4. **Схема v1 (D7) + добавить:** `sense`; ось «редакционное время» ⊥ спойлер-окну; `UNIQUE(src,sense,window)`+обратная dst-проверка; `min_key_len`+запрет одиночных ключей; нормализационный артефакт NFKC/OpenCC/kana/ruby.
5. **Рациональ sqlite-vec/modernc переписать** (не «pure-Go не может»); brute-force в MVP оставить.
**Обязательный in-house eval перед фиксацией ставки (acceptance-критерии — предзарегистрировать):**
- **Протокол:** WMT25-трёхрежимный (no / correct / **random** terminology) на реальных zh/ja→ru чанках, измерять **две оси раздельно**: (а) самосогласованность approved-форм по главам, (б) source-fidelity/omission (reference-free QE — CometKiwi/MetricX, + LLM-судья-против-источника, охотящийся на пропуски/mistranslation). Плюс ru-специфика: согласование рода (вкл. глагол прош. вр.) и склонение `dst`.
- **Baseline, без которого сборка может быть не оправдана:** **банк памяти ON vs OFF** и **банк vs длинный контекст** frontier-модели на **том же** стеке и направлении (DelTA этого не делала).
- **Acceptance (предзарегистрировать до прогона):** мин. Δ качества (напр. CometKiwi/win-rate), макс. omission-rate, макс. name-inconsistency-rate, **допустимый flag-volume на главу** (иначе «route to human» пусто). Голд-сет: размер, квалификация аннотаторов (ru-литчитатели), IAA, BWS/MQM (не сырые автометрики — NAACL 2025).
- **Cost:** посчитать батч-LLM-операции (экстракция, адъюдикация, обновление резюме) на 1000 глав против экономического контура Р5.
**Резюме одной фразой:** банк памяти строить своим — верно; но **тихую деградацию мы не устраняем, а конвертируем в громкую** (post-check + disposition-лог + retrieval-state + жёсткие спойлер/род-гейты), и **числа литературы для zh/ja→ru не годятся** — их заменяет свой замер с предзарегистрированными критериями и baseline «банк vs длинный контекст».
---
## Источники (проверены 2026-07-04; с корректировками верификаторов)
**Retrieval-robustness / RAG-failure:** Power of Noise (SIGIR 2024, [2401.14887](https://arxiv.org/abs/2401.14887)); Yoran et al. robust-to-irrelevant (ICLR 2024, [2310.01558](https://arxiv.org/abs/2310.01558)); RGB / Benchmarking LLMs in RAG — noise robustness + negative rejection, **есть китайский тестбед** (AAAI 2024, [2309.01431](https://arxiv.org/abs/2309.01431)); CRAG ([2401.15884](https://arxiv.org/abs/2401.15884)) + независимая репродукция/объяснимость ([2603.16169](https://arxiv.org/html/2603.16169)); over-trust инъектированных терминов ([2510.00829](https://arxiv.org/abs/2510.00829)).
**Стек/масштаб:** sqlite-vec ([github.com/asg017/sqlite-vec](https://github.com/asg017/sqlite-vec)); ncruces/go-sqlite3 (pure-Go WASM) + sqlite-vec-go-bindings/ncruces; RRF (Cormack, Clarke & Buettcher, SIGIR 2009).
**Retrieval-корректность / reranking:** Sciavolino «Simple Entity-Centric Questions Challenge Dense Retrievers» (EMNLP 2021, [2109.08535](https://arxiv.org/abs/2109.08535)); BGE-M3 ([2402.03216](https://arxiv.org/abs/2402.03216), MIRACL zh/ja/ru); bge-reranker-v2-m3.
**Детерминизм / терминология:** silent term-misses 1736% ([2310.05824](https://arxiv.org/abs/2310.05824)); DuTerm en→ru «It Takes Two: A Dual Stage Approach» ([2511.07461](https://arxiv.org/abs/2511.07461)); Cascaded Beam Search en-ru EMA 0.925 / ~0 BLEU ([2305.14538](https://arxiv.org/abs/2305.14538)); agreement 46% при форсе surface-форм en-cs ([2106.12398](https://arxiv.org/abs/2106.12398)); Bergmanis & Pinnis lemma+inflect +47.7 п.п. (Latvian); retrieval-vs-generation de-en ([2503.05010](https://arxiv.org/abs/2503.05010)); WMT24 terminology ([2024.wmt-1.51](https://aclanthology.org/2024.wmt-1.51/)).
**Академическое заземление:** DelTA (ICLR 2025, [2410.08143](https://arxiv.org/abs/2410.08143), код DocMTAgent); TransAgents (TACL, [2405.11804](https://arxiv.org/abs/2405.11804)); Karpinska & Iyyer «Large Language Models Effectively Leverage Document-level Context» (EMNLP 2023, [2304.03245](https://arxiv.org/abs/2304.03245)); WMT25 Terminology «Terminology is Useful Especially for Good MTs» ([2025.wmt-1.30](https://aclanthology.org/2025.wmt-1.30/)); DITING zh→En ([2510.09116](https://arxiv.org/abs/2510.09116)); NAACL 2025 литперевод BWS.
**Вендоры/эмбеддинги:** Mem0 (ECAI 2025, [2504.19413](https://arxiv.org/abs/2504.19413), LOCOMO); Zep/Graphiti ([2501.13956](https://arxiv.org/abs/2501.13956)) + getzep issue #5 (исправленная репродукция); Qwen3-Embedding ([2506.05176](https://arxiv.org/abs/2506.05176)); MMTEB ([2502.13595](https://arxiv.org/abs/2502.13595)); LaBSE.
**Эмпирика (эта сессия):** `eval/retrieval_bench.py`, корпус `eval/data/retrieval_bench/corpus.json` (реальные PD: 阿Q正传/祝福/羅生門/走れメロス + синтетические сянься-ловушки), результаты `eval/data/retrieval_bench/results.json`. Оговорка: микро-бенчмарк (24 записи, 18 запросов, 13 gold, 4 ловушки) — **индикативный**, не финальный eval; разделяющие/пороговые выводы (AUC, пересечение распределений) устойчивы как распределительное свойство, конкретные R@kс поправкой на малый N.