textmachine/docs/archive/reports/PACK20_BANKNOTE_DESIGN_2026-07-26.md

67 KiB
Raw Blame History

Пак-20, продолжение: ДИЗАЙН по замерам пакета-8 — дисциплина банкноты и дыра языка ответа

База: D39.50 (пакет-8 принят, 8/8 CONFIRMED), D39.47/48, POLYGON_PREMEASURE_2026-07-26.md. Зона: backend/ (эта сессия — только ЧТЕНИЕ; ни одной строки продового кода не изменено). Статус: фаза ДИЗАЙНА. Деньги сессии $0 (ни одного платного вызова). Сессия НЕ коммитит. Дальше: СТОП → ратификация владельца → стройка.

Ревью-шапка (оркестратор №8, 26.07): ДИЗАЙН РАТИФИЦИРОВАН, D39.51 — стройка пп.17 §8 санкционирована. Спот-проверка оркестратора: AllowShort в хеше строки банка (memory.go:368) и место эхо-гейта (terminology.go:882-885) — сошлись; четыре опоры сессии проверены ЕЮ исполнением, полная проверка — мутациями приёмки стройки. Все три расхождения с записками оркестратора ПРИНЯТЫ как правильные: allow_short = перепокупка книги (не «строка данных») → на холодный прогон; пар-файл kwic для zh-ru не шипуем (значение = фолбэк, LangpackVersion не трогаем); судью не строить — не из-за денег, а потому что его положительное решение отнимает подпись у владельца (принцип D39.47) + популяция неизвестна до холодного старта. Ответ на §10 п.6: сырьё 147 вызовов ЖИВО (скретчпад полигон-сессии, raw2/raw3/raw3b + raw_anchor побайтовая копия; верификатор приёмки D39.50 пересчитывал по нему деньги) — приёмка предиката §2.5 п.5 на настоящем сырье ОБЯЗАТЕЛЬНА. Дефолты по решениям §10 (вето владельца одной строкой): судья ОТЛОЖЕН до P1 + холодного старта · мини-прогон D39.40 идёт В ХОЛОДНОЙ конфигурации (отдельная БД — операционное условие §6 в силе) · allow_short для 蛊/转 — данными на нём же. Полигону — два замера (§8 пп.89, ≈$0.009 + $0).

Посылка «расширить эмиссию майнера» закрыта замером отрицательно, и хорошо, что до стройки: §2 моей дельты (PACK20_DELTA_2026-07-26.md) устарела целиком, кроме одного — оси «что банкуется без подписи ≠ что кладётся на подпись». Она пережила пакет-8 и стала в этом дизайне несущей.


§0 Эхо-блок (что я услышал, ДО работы)

  1. Не проектировать: подъём emitRankCap, снятие фильтра типа, частотный экран для банкноты, подъём kwic-дефолтов. Всё четыре закрыты замером — механизмом, а не порогом.
  2. Предмет: (1) дыра языка ответа — закрыть ДО любого расширения канала; (2) дисциплина банкноты (кавычки + агрегация по прогонам; семантический отбор — судья или владелец, посчитать деньгами); (3) kwic_width из Go-константы в пар-данные, значение НЕ менять; (4) allow_short — упомянуть; (5) холодный старт — не мерить самому, предложить, как его снимет мини-прогон.
  3. NormalizeSourceKey — ключевая нормализация; оси объявить явно.
  4. Нормы D39.46/47: арм без фактора · сравнение на расходящихся позициях · декой · критик полноты на фазу · право сказать «этого делать не надо».
  5. Алгоритмы не подгонять под язык/книгу. Ревью допустимо, скоуп-крип — нет.
  6. Отчёт → СТОП. Судья в прод-провод — только с отдельной санкцией.

§1 Синк-таблица: источник ↔ код ↔ вердикт

Вердикт «подтверждаю исполнением» означает, что я выполнил проверку в этой сессии (временный тест, удалён сразу после снятия чисел); «подтверждаю чтением» — что грунтовал по file:line, но не запускал.

# Утверждение полигона Адрес в коде Вердикт
1 Роль отвечает по-английски, и конвейер это банкует (§3.4) terminology.go:842-892 ParseReply, :576-588 wellFormedLemma Подтверждаю ИСПОЛНЕНИЕМ. Боевой ParseReply на строках 修行→cultivation, 元海→Primordial Sea, 转→realm, 是为→is: принято 5 из 5, bad_lines = 0. Ни один счётчик не шевельнулся
2 Якорь ⟦TM-CANON⟧ — де-факто якорь целевого ЯЗЫКА, и он пуст на холодном старте terminologist.go:307-316, terminology.go:714-765 CanonFor Подтверждаю чтением. CanonFor берёт только APPROVED-строки; их ноль до первой подписи. Пустой якорь → renderPairs даёт "" → сообщение не отправляется вовсе
3 Эхо-гейт не ловит английский terminology.go:885 Подтверждаю чтением. Проверка normalize(dst) == key ловит эхо ИСХОДНИКА; английское слово исходником не является
4 《咏梅》 и 咏梅 — две разные строки карты подписи norm.go:136-156, banknote.go:288 Подтверждаю ИСПОЛНЕНИЕМ. key(《咏梅》)="《咏梅》" против key(咏梅)="咏梅", не равны. NFKC книжные кавычки не снимает — они не совместимостные
5 / подписаны и не доезжают ни одним каналом memory.go:276, norm.go:252-263, miner_emit.go:27,33 Подтверждаю ИСПОЛНЕНИЕМ. SignificantLen(蛊)=1, SignificantLen(转)=1, SignificantLen(蛊师)=2 — ровно порог minKeyLenHan=2
6 Банкнота — носитель покрытия (5/6 кандидатов) mining.go:87-120, terminology.go:128-214 Merge Подтверждаю чтением и УТОЧНЯЮ: в живом слое это ещё и означает, что ~84% кандидатов несут строку drafts: — то есть целевой язык уже стоит в запросе. Замер §3 его снял («черновых вариантов нет») и потому меряет риск языка в его ХУДШЕЙ точке. Дыру это не отменяет (см. §2.0)
7 Частотного экрана для банкноты не существует (§5.1) miner_emit.go:27 emitMinFreq Подтверждаю и присоединяюсь. Порога, разделяющего 少年 (1149) и 光阴之河 (15), нет. Своё предложение частотного порога полигон снял сам — это образцовая работа, и повторять её я не буду
8 kwic-дефолт 3×40 достаточен terminologist.go:57-61 Принимаю вердикт, оспариваю его перенос. Достаточность измерена на ЛЁГКОЙ выборке без строки drafts: (§3.3 сам называет три различия). Практический вывод — «поднимать нечем» — от этого не страдает, и я не предлагаю поднимать
9 kwic_width в рунах — утечка пары в общий слой (§6.3) terminology.go:331-346 window Подтверждаю чтением. Окно режется в рунах, константа одна на все пары
10 Эмиссия банкноты — лотерея прогона (Жаккар 0.0770.40) glossary.go:258-299 (PK book_id,chapter,chunk_idx), banknote.go:330-379 Подтверждаю чтением и УТОЧНЯЮ механизм: внутри ОДНОЙ базы повтор невозможен — чекпойнт реиграет тот же текст, и bankProposalsJSON пересчитывается из него (stagerun.go:415). Разъезд возникает только когда черновики РЕАЛЬНО перекупаются; тогда UPSERT по PK затирает прошлые предложения — объединение теряется
11 Роль выдумывает dst 7782% на расширенном хвосте Принимаю; предмета для кода нет (расширение не строится)
12 Условие фолбэка «судья-с-декоем» наступило Принимаю. Считаю деньгами в §3.4

Побочная находка ревью (не скоуп-крип, а мой собственный хвост D39.47): в pipeline.go:255-261 doc-комментарий TerminologyGate всё ещё обещает роли «its genre glossary». Механизма нет с D39.47 — осталась строка документации. Правка на одну строку, кладу в стройку этого пакета, отдельного решения не прошу.


§2 Дыра языка ответа (главный предмет)

§2.0 Что именно сломано — точная формулировка

Не «модель иногда отвечает по-английски». Сломано то, что у конвейера нет ни одного места, где проверяется, что ответ роли написан на целевом языке, и все три существующих стража пропускают латиницу по своим законным причинам:

  • wellFormedLemma пропускает — латиница легитимна для латинских имён;
  • эхо-гейт молчит — ответ не равен исходнику;
  • CanonConflicts молчит — конфликтовать не с чем, якорь пуст.

Дальше строка едет по штатному маршруту: ParseReplyattachConsolidatedDst (terminologist.go:384-396) → status: draft → авто-банк → инъекция редактора с ⟨проверить⟩. То есть 修行 → cultivation становится рабочим каноном книги, и единственное, что стоит между ним и текстом, — глаз владельца на стопе, которого в авто-режиме нет по определению.

Важное уточнение к §3.4 полигона, в обе стороны:

  • риск в бою МЕНЬШЕ измеренного: в живом батче ~84% кандидатов (65 из 78 на срезе §1.3) несут строку drafts: с русскими вариантами, а замер их снял ради чистоты фактора. Целевой язык в запросе обычно присутствует;
  • но точка отказа ровно та, где этой страховки нет: холодный старт (якорь пуст) + кандидат от майнера без черновых вариантов (11 из 78) = батч, в котором целевого языка нет НИГДЕ, кроме промпта. Промпт написан по-русски целиком — и §3.4 показывает, что этого не хватило.

Отсюда конструктивное требование: гарантия языка не должна зависеть от содержимого запроса. Всё, что живёт во входе (якорь, черновики, примеры в промпте), — это снижение вероятности; гарантия может стоять только на ВЫХОДЕ.

§2.1 Вариант А (рекомендуемый): детерминированный экран целевого письма на выходе

Предикат. Строка ответа не является переводом, если в ней есть буквы и ни одна из них не принадлежит письму цели. Числа, пунктуация, дефисы — не буквы и не участвуют.

offLanguage(dst, script) := (letters(dst) > 0) && (hits(dst, script) == 0)

Общность. Письмо приезжает в terminology ЗНАЧЕНИЕМ (*unicode.RangeTable), как уже приезжает Conformance func(dst, typ) float64 (terminology.go:351-361). Ни имени языка, ни имени письма в пакете нет. Таблица резолвится по ИМЕНИ из unicode.Scripts — стандартная таблица Go, покрывающая любое письмо. Ревью-вопрос «заработает ли на паре, которой в репо ещё нет, без правки Go?» — да: новая пара объявляет своё письмо строкой конфига.

Проверено исполнением на реальных строках отчёта (не на выдуманных фикстурах):

Множество Строки Результат
Английские ответы §3.4/§2.6 cultivation · Primordial Sea · Rank 1 · Grade C · talent · Wine Bug · realm · Qingmao Mountain · Gu Yue · is 10 из 10 помечены
Подписанные владельцем и живые русские культивация · море истинной ци · гу-мастер · Фан Юань · гу · талант · винный червь · старейшина · Гуюэ · апертура · третья гу · гу Силы · Река времени · 9 · «Ода сливе» · Фан Юань 16 из 16 прошли
Тот же предикат при латинской цели культивация / cultivation роли меняются местами, Go не трогается

Ложных срабатываний нет ни одного, включая пограничные: гу (2 буквы), 9 (букв нет — не трогаем), кавычки и двойной пробел.

Где стоит. Ровно там, где уже стоит эхо-проверка — terminology.go:885, и по той же логике: «ответ, который есть исходник, — не перевод» ↔ «ответ, который написан не на языке цели, — не перевод». Строка считается плохой (bad), термин остаётся неконсолидированным (status: auto, инертен). Ни одна выдумка в банк не попадает.

Направление отказа названо явно. Предикат может отвергнуть ЗАКОННЫЙ латинский рендеринг (пара, где владелец хочет сохранить «Wi-Fi» латиницей). Цена ошибки — потерянное предложение, которое владелец подпишет руками; цена противоположной ошибки — англоязычная строка в каноне книги. Ошибаться в эту сторону — решение, а не недосмотр.

Наблюдаемость (обязательна, иначе экран сам становится тихой деградацией). Отдельный счётчик OffLanguage в terminologyResult (terminologist.go:65-81) — НЕ слияние с BadLines: «модель ответила не на том языке» и «модель сломала формат» требуют разных действий. Батч, где доля не-целевых строк ≥ ½, логируется Warn с именами строк: это ровно сигнатура срыва §3.4 (22 из 40), и она должна быть видна оператору в тот же момент, а не в отчёте через сутки.

Тот же предикат — на втором входе. Черновая банкнота тоже несёт dst в банк (banknote.go:330-379), и 是为 → is в §2.6 показывает, что канал не иммунен. Фильтр ставится в СВЁРТКЕ (bankObservedByKey) и на записи (bankProposalsJSON), а не в парсере — почему именно так, см. оси в §3.1.

Чего экран НЕ делает (границы, чтобы его не переоценили): он не ловит смешанную строку (cultivation (культивация) пройдёт — кириллица есть), не ловит правильный язык с неправильным смыслом, и не улучшает качество ни на одном термине. Он закрывает ровно один класс: язык.

§2.2 Вариант Б: строгая форма предиката (доля, а не наличие)

offLanguage := hits/letters < 0.5. Ловит cultivation (культивация) и пояснения в скобках, которые промпт запрещает, а wellFormedLemma пропускает (скобки не в списке []{}<>|). Не рекомендую в этом паке: это уже не «язык», а «формат ответа», отдельный класс с собственной ценой ложных срабатываний (законные скобки в титуле). Называю как открытую опцию, чтобы не подавать её потом как новую находку.

§2.3 Вариант В (вход, дополняющий, НЕ заменяющий): пример формата в промпте

§3.4 показывает не «модель не знает языка», а «модель повторяет письмо примеров, которые видит». Тогда самое дешёвое воздействие на вход — не якорь, а строка примера формата в промпте роли, написанная на целевом письме. Это ДАННЫЕ (prompts/zh-ru/terminologist.md), Go не трогается, снапшот не двигается, цена — перепокупка только батчей терминолога.

Но это гипотеза, а не замер, и я не предлагаю её строить вслепую. Она проверяется на уже построенном харнессе полигона одним армом: их же «3×40 без якоря» ×3 повтора, плюс строка примера. Критерий назвать ДО прогона (предлагаю: 0 не-целевых строк в 3 прогонах против 22/40 в контрольном). Цена по их же таблице §4 — ≈$0.009.

Порядок важен: вариант А строится независимо от исхода В. Если В сработает, А почти никогда не будет срабатывать — и это правильное состояние: страховка, которая молчит.

§2.4 Деньги и оси варианта А

Деньги стройки $0 (детерминированный предикат, ни одного вызова)
Деньги в проде $0 навсегда
Снапшот движка НЕ двигается: предикат читает ОТВЕТ, байты запроса не меняются; gates.terminology в снапшот не фолдится (pipeline.go:263-268)
Батчи терминолога НЕ перекупаются (адрес батча — байты запроса)
Книга без англоязычных строк в банке путь байт-в-байт прежний, не перебилливается
Книга, у которой такая строка в авто-банке ЕСТЬ строка исчезает → обогащённая версия банка сдвигается → редакторская волна перебилливается ОДИН раз. Это штатный путь «банк изменился», а не --resnapshot движка
Конфиг новый ключ gates.terminology.target_script (имя письма). Обязателен при enabled: true, проверяется на ЗАГРУЗКЕ по идиоме pipeline.go:986-1003 — гейт, который не может отработать, отвергается до денег

Почему конфиг, а не пар-данные langpack. Файл в configs/langpacks/<пара>/ фолдится в Pack.Version()LangpackVersionобе волны (snapshot.go:428) → полная перепокупка книги. Платить перепокупкой черновой волны за объявление письма — несоразмерно. gates.terminology не фолдится вообще, поэтому там объявление стоит $0. Общность при этом сохранена: значение — данные, не Go.

Альтернатива, которую я рассмотрел и отверг: выводить письмо цели из уже загруженных пар-данных (palladius.txt целиком на кириллице). Соблазнительно — ноль новых ключей, — но это скрытая связь: файл написан для транскрипции, и правка в нём молча меняла бы поведение языкового экрана. Явное объявление дороже на одну строку конфига и честнее.

§2.5 Приёмка (как это доказывается, а не заявляется)

  1. Мутация M-Я1: снять предикат → тест на реальных строках §3.4 обязан покраснеть.
  2. Мутация M-Я2: ослабить до «есть хоть одна буква цели ИЛИ строка короче N» → красное на is.
  3. Регресс ложных срабатываний: 16 живых русских строк (включая подписанные владельцем) — зелёные.
  4. Пин общности: тот же предикат с латинской целью меняет вердикты местами — доказывает, что в Go не осело знание о паре.
  5. $0-верификация на настоящем сырье: полигон сохранил все 147 вызовов пакета-8. Если сырьё живо — прогнать предикат по ним целиком: он обязан пометить ровно 27 известных строк §3.4 и ни одной сверх. Это лучшая приёмка, чем любая фикстура, и стоит $0. Запрос к полигону: доступно ли сырьё.

§3 Дисциплина банкноты

§3.0 Что здесь на самом деле лечится

Банкнота даёт 5/6 кандидатов и умеет класс term с dst — и она же невоспроизводима. Три дефекта разной природы, и их нельзя лечить одним средством:

Дефект Природа Лечение Цена
《咏梅》咏梅 нормализация детерминированное, $0 §3.1
объединение предложений теряется при перекупке черновиков долговечность детерминированное, $0 §3.2
48% предложений — разовые фразы («звук дождя») СЕМАНТИКА бесплатного нет — замер, потом судья или владелец §3.33.4

Почему мусор в банке вреден — назову прямо, потому что от этого зависит, сколько за отбор не жалко платить. Строка 雨声 → «звук дождя» со статусом draft попадает в инъекцию редактора с пометкой ⟨проверить⟩. Она (а) ест бюджет инъекции и вытесняет настоящие термы (n_evicted это уже считает), (б) засоряет карту подписи владельца и (в) — главное — предписывает редактору переводить обычное слово ОДНИМ фиксированным способом. Последнее прямо противоречит цели проекта: механизм, созданный против расхождения терминов, применённый к не-термину, производит ровно ту деревянность, ради победы над которой всё строится.

§3.1 Кавычки: нормализация обрамляющей парной пунктуации

Правило (пар-слепое по построению). Снять с ключа обрамление, если ПЕРВАЯ руна — открывающая (unicode.Ps unicode.Pi ASCII " '), а ПОСЛЕДНЯЯ — закрывающая (unicode.Pe unicode.Pf те же ASCII); повторять, пока снимается; остановиться, если остаток пуст. Требование парности не даёт покалечить O'Brien или строку, у которой кавычка только с одной стороны. Работает для 《》, 「」, “”, «», "" — то есть для пар, которых в репо ещё нет, без правки Go.

Где стоит — и это главная ось.

Место Что чинит Цена
(Q1) свёртка bankObservedByKey + запись bankProposalsJSONрекомендую ключи сходятся, 4 строки карты подписи становятся 2, ретроактивно для уже оплаченных прогонов $0. retrieval_state не wire-load-bearing (migrate.go:316-327); снапшот не двигается
(Q2) парсер parseBanknote то же bankParserVersion фолдится (snapshot.go:113-130) → --resnapshotперепокупка всей черновой волны каждой книги с включённым каналом
(Q3) text.NormalizeSourceKey то же + совпадение с ключами банка памяти NormVersionmemoryVersionобе волны; плюс смена семантики матчинга для всей памяти

Q2 и Q3 платят волной за косметику. Q1 даёт тот же результат бесплатно и вдобавок лечит УЖЕ записанные строки, потому что свёртка перечитывает сохранённый JSON.

Дом функции: internal/text рядом с прочими чистыми предикатами, с явным комментарием, что она не входит в нормализационный артефакт и NormVersion() не двигает, + тест-страж на неизменность NormVersion(). (Альтернатива — internal/terminology; выбрал text, потому что предикат нужен обоим потребителям и не содержит терминологической логики.)

Измеренный эффект: Жаккар между двумя прогонами одних глав 0.077 → 0.400 (§1.4), минус 3 дубля из 67 строк карты подписи (4.5%). Честно: это не лечение precision, это лечение тождества.

§3.2 Агрегация по прогонам: объединение вместо затирания

Что ломается сегодня. retrieval_state имеет PK (book_id, chapter, chunk_idx) и UPSERT (glossary.go:258-299). Пока черновики резюмятся из чекпойнтов — предложения воспроизводятся байт-в-байт. Как только черновик РЕАЛЬНО перекуплен (снапшот сдвинулся), новая строка затирает старую, и предложения прошлого сэмпла исчезают навсегда, хотя чекпойнт с их текстом лежит в базе.

Вариант A1 (рекомендуемый): объединение выводится из ЧЕКПОЙНТОВ, нового долговечного состояния нет. Свёртка читает не текущие retrieval_state, а все чекпойнты книги переводческой роли, и разбирает из них блоки ⟦TM-BANK-v1⟧ тем же кодом, которым это уже делается на резюме (stagerun.go:415). Нужен один новый store-запрос (checkpoints JOIN jobs ON job_id WHERE book_id=? AND role=?), с исключением производных (finish_reason='banknote_export' — там блок уже срезан) и с тем же гейтом finish=="stop".

  • Плюс: ноль новых таблиц и миграций; свойство «пересчитывается каждый прогон, самолечится» сохранено (это норма этого кода: производное лучше хранимого); покрытие монотонно — поверхность, однажды предложенная, больше не теряется.
  • Минус, называю прямо: в объединение войдут и предложения ОТВЕРГНУТЫХ попыток (эхо, обрыв). Считаю это приемлемым и объявляю решением: предложение — это улика, а не канон; владелец всё равно подписывает. Альтернатива (только победившие попытки) требует читать chunk_status.final_hash, который сам перезаписывается — то есть лечит симптом тем же механизмом, который сломан.

Вариант A2: отдельная таблица banknote_proposal (append-only). Отвергаю: долговечное состояние, не выводимое из чекпойнтов, миграция, и первый же баг записи становится неисправимым — против дисциплины всего этого хранилища.

Обязательство, которое я обязан доказать тестом, а не заявить. mining.go:108-120 прямо предупреждает: если дельта отличается между прогоном 1 и прогоном 2, сдвигается редакторская волна и книга перебилливается ни за что. Объединение это ужесточает. Инвариант: прогон, который не купил ни одного нового черновика, обязан дать байт-идентичную карту подписи. Он держится по построению (множество чекпойнтов не изменилось → объединение не изменилось), но именно поэтому его нужно запинить тестом, а не оставить рассуждением.

Что агрегация НЕ даёт — и это важно для ожиданий. Новых поверхностей она не приносит, пока никто не купил второй сэмпл черновика. Второй сэмпл — это вторая черновая волна, самая дорогая часть конвейера; рекомендовать её ради покрытия я не буду. Агрегация покупает немонотонную потерю → монотонность, и только её. Единственный бесплатный второй сэмпл, который у нас будет естественным образом, — мини-прогон холодного старта (§6).

Оси: объединение может ДОБАВИТЬ строк → авто-банк вырастет → обогащённая версия банка сдвинется → редакторская волна перебиллится один раз, и только у книги, у которой черновики покупались более одного раза. У книги с одним сэмплом — байт-в-байт прежнее поведение.

§3.3 Семантический отбор: сначала $0-замер, и он может закрыть вопрос

Прежде чем предлагать платный экран, обязан назвать бесплатный сигнал, который никто не мерил, — и мерить его я предлагаю ДО стройки, потому что если он работает, платить не надо вовсе.

Сигнал. Частота в тексте отвергнута замером. Но в свёртке уже лежит другое число: Chunks — в скольких ЧАНКАХ переводчик независимо предложил эту поверхность (banknote.go:351-356). Это не частота в тексте, а повторяемость решения: сколько раз независимый чтец, глядя на разные куски, счёл строку достойной объявления термином. Ставлю гипотезу на отношение

consistency := Σ chunks(предложившие поверхность) / (число чанков, где поверхность ВСТРЕЧАЕТСЯ)

少年 встречается в множестве чанков и предложен один раз → отношение близко к нулю. 光阴之河 встречается в двух и предложен в одном → 0.5. Это ортогонально частоте — то есть не повторение опровергнутого порога.

Замер P1 ($0, полигон, из уже сохранённых артефактов). На 74 размеченных поверхностях посчитать consistency и проверить, разделяет ли она «настоящий терм книги» и «обычное слово». Критерий назвать ДО счёта (предлагаю: разделение засчитывается, только если существует порог, оставляющий ≥80% настоящих термов и убирающий ≥60% мусора; иначе — «не разделяет»). Арм без фактора обязателен: та же проверка для голой частоты — она обязана провалиться (это уже известно из §5.1 и служит калибровкой методики).

Заранее говорю, чего этот замер не может. Разметка «настоящий/мусор» — один разметчик и один вкус; на границе (家主 «глава клана» против 中年 «средний возраст») она сама шатается. Поэтому положительный исход P1 даёт право строить БЕСПЛАТНЫЙ ранжирующий сигнал (сортировка карты подписи, порядок подачи), но не право автоматически выбрасывать строки. Выбрасывание — это решение о содержимом банка книги, и по духу D39.47 оно принадлежит подписи владельца, а не порогу в Go.

§3.4 Судья-с-декоем: где ставить и сколько стоит (расчёт, а не оценка)

Условие фолбэка D39.46 наступило. Считаю деньгами.

Популяция. 67 поверхностей на 10 главах → ~6.7 на главу. Для книги в 100 глав — ≈670 (линейная экстраполяция, то есть ВЕРХНЯЯ граница: словарь книги насыщается, и доля новых поверхностей к концу падает).

Цена одного кандидата. Улика судьи — та же, что у терминолога: исходник + контексты + предложенный dst. Мой собственный замер на боевой сборке: 223 токена на кандидата при kwic 3×40. Выход вердикта ≈10 токенов. Цены deepseek-v4-flash из models.yaml: $0.14/$0.28 за 1M.

Статья Токенов $
Вход 670 × 223 149 410 0.0209
Выход 670 × 10 6 700 0.0019
+10% строк-декоев (калибровка, не удвоение) 0.0023
Один проход, книга 100 глав ≈$0.025
Три повтора (если понадобится устойчивость) ≈$0.075

С кэшем (в пакете-8 75% входа пришло из кэша) реальная цифра ниже. Против цели ~$0.85/ранобэ — ≈3%.

Вывод по деньгам, который я обязан сказать честно: экран дёшев, и цена — НЕ аргумент против него. Но и экономии он не даёт: терминолог на тех же 670 кандидатах стоит примерно столько же, так что «отсечём мусор, чтобы сэкономить на роли» — самообман (экономия ≈$0.011). Оправдание у экрана одно — качество банка (§3.0), и решать его судьбу надо в этих терминах.

Где ставить — три позиции.

Позиция Что защищает Оценка
J1: перед терминологом, на banknote-only кандидатах И авто-банк, И карту подписи, И вход роли — одним экраном выше по течению предпочтительная, если экран вообще ставится
J2: после консолидации только банк; роль уже потратилась хуже J1 без выигрыша
J3: только на карте подписи владельца лист подписи; авто-банк остаётся замусоренным не закрывает §3.0(в) — редактор всё равно получает мусор

И тем не менее — рекомендация: сейчас НЕ строить. Три причины, по убыванию: (1) $0-замер P1 может снять предмет целиком; (2) новый ПЛАТНЫЙ вызов в прод-проводе требует отдельной санкции по этой же записке; (3) главная неизмеренная цифра — холодный старт (§6) — меняет саму популяцию, которую судья бы фильтровал: неизвестно даже, 670 их или 2000. Ставить фильтр на популяцию неизвестного размера — это проектировать по догадке ровно там, где пакет-8 только что показал цену догадки.


§4 kwic_width → пар-данные (значение не меняется, меняется место жительства)

Дефект (§6.3): 40 рун = 4384 морфемы на zh (две реплики диалога) и обрубок фразы посреди предложения на en. Одна константа означает разное — классическая утечка пары в общий слой.

Вариант A (рекомендуемый): необязательный пар-файл + инженерный фолбэк. configs/langpacks/<пара>/terminology.txt с парами ключ значение (kwic_width 40, при нужде kwic_per_term 3), читается идиомой readOptionalровно как heading.txt и dc-checkers.txt (langpack.go:196-225): отсутствует → фича инертна и Version() побайтно стабилен; присутствует → байты фолдятся, правка = громкий --resnapshot. Приоритет резолвинга в одном месте (terminologyOpts, terminologist.go:99-112): явный конфиг книги → пар-данные → инженерный фолбэк.

Для zh-ru файл НЕ шипуем. Измеренное значение совпадает с фолбэком, значит файл не купил бы ничего, а Pack.Version() сдвинулся бы → LangpackVersionобе волны → полная перепокупка книги. Платить перепокупкой за переезд константы, которая не меняется, нельзя. Механизм при этом построен: пара en-ru шипует свой файл со своим значением, и на ней ничего перекупать не надо — там ещё ничего не куплено.

Вариант Б: мерить окно не в рунах, а в предложениях источника (sentence-terminator.txt в паке уже есть, langpack.go:139-151). Единица становится пар-осмысленной по построению. Не рекомендую сейчас: это меняет БАЙТЫ контекстов → перекупку батчей терминолога, а сетка §3.1 показала, что ни одна форма подачи не отличима от другой. Менять то, что измеренно не влияет, — трата без гипотезы. Называю как опцию на случай, если пере-замер на трудной выборке (§7) покажет, что контексты всё-таки решают.

Оси варианта A: zh-ru — ничего не двигается, ноль перепокупок. Новая пара с файлом — её собственные батчи терминолога, волн нет. Go-фолбэк остаётся, но перестаёт быть единственным источником истины, и ревью-вопрос «заработает ли на паре, которой нет в репо» получает ответ «да, дропом файла».


§5 allow_short для / — данные, но НЕ бесплатные

Механизм подтверждён исполнением: SignificantLen(蛊)=1 при пороге 2, обход — allow_short: true в строке сида, Go не трогается (memory.go:276). Пока согласен с полигоном. Дальше — три вещи, которых в §1.5 нет, и они меняют вывод «просто добавь строку».

  1. Это перепокупка ВСЕЙ книги. AllowShort фолдится в хеш строки банка (memory.go:368), а — строка с source: seed, то есть входит в БАЗОВУЮ версию банка (snapshot.go:264-271). Базовая версия — это снапшот ЧЕРНОВОЙ волны. Правка данных, но цена — заново купленная книга.
  2. Ключ из одного иероглифа сидит внутри своих же составных. — подстрока 蛊师, 蛊虫, 力量蛊. Он начнёт срабатывать в каждом таком чанке, а разрешение вложенности уйдёт на trust-gate/суппрессор (n_trust_gated_suppress) — механизм есть, но нагрузка на него станет систематической, а не редкой.
  3. Пост-чек становится обязательством на почти каждый чанк. 188 вхождений на 10 главах: E1 начнёт требовать «гу» в выходе почти везде, и часть требований будет ложной — «гу» законно живёт внутри «гу-мастера», а совпадение считается по нормализованной целевой форме.

Рекомендация: строку сида добавить — но на холодном мини-прогоне (§6), где книга покупается заново по другой причине и цена пункта 1 равна нулю, а пункты 23 сразу измеряются готовыми счётчиками (n_evicted, n_trust_gated_suppress, n_postcheck_miss). Отдельного «просто добавим строку» я не рекомендую: это выглядит как правка данных, а стоит как волна.


§6 Холодный старт: как его снимает мини-прогон (предложение, не исполнение)

Почему это несущая цифра. Замер 1 не состоялся именно потому, что 49 из 51 термов сида были инъектированы, а промпт переводчика просит «новые, которых нет в глоссарии». Значит всё, что мы знаем о банкноте, измерено на канале, которому заткнули рот. Сколько она предлагает, когда её не глушит инъекция, не знает никто, — а от этого зависит и размер популяции для §3.4, и оценка покрытия, и веса §C2-3.

Предложение: мини-прогон D39.40 исполняется в холодной конфигурации. Не отдельный замер — тот же прогон, только с пустым банком.

  • Что меняется: книга стенда конфигурируется без сида (seed_glossary пуст) — правка ДАННЫХ; канал банкноты ON, терминолог ON, --verify-bank для полного стоп-стола.
  • Почему это не искусственно, а НАОБОРОТ: новая книга в проде приходит именно так — без единой подписанной строки. Холодный прогон репрезентативнее того, что мы гоняли до сих пор.
  • Цена: одна черновая волна на 10 главах. Мини-прогон 26.07 стоил $0.23; пустой банк убирает инъекцию (входные токены падают), так что это верхняя граница.

Что он закрывает за одни деньги — пять вопросов сразу:

Вопрос Чем измеряется Кем ждался
Сколько предлагает банкнота без цензуры инъекцией число поверхностей, n_banknote_lines §1.7, D39.50 п.7
Совместный recall майнер банкнота против подписанного сида сид не инъектирован → знаменатель чист, замер 1 состоится §1.2 (не состоялся)
Течёт ли язык ответа вживую при ПУСТОМ якоре счётчик OffLanguage из §2.1 — сам по себе измерительный прибор, $0 §3.4
Побочки allow_short на / n_evicted, n_trust_gated_suppress, n_postcheck_miss §5
Второй сэмпл черновика для агрегации (§3.2) объединение с существующим прогоном тех же глав §1.4

Условие, которое делает эти цифры сравнимыми: прогон должен идти в ОТДЕЛЬНОЙ базе, а не поверх существующей (иначе чекпойнты реиграют старые черновики и «холодного» ничего не будет). Это операционная деталь, но без неё замер молча превратится в резюм.


§7 Чего я НЕ проектирую и почему (границы)

  1. Расширение эмиссии — закрыто D39.50. Ни emitRankCap, ни фильтр типа, ни feed_cap в этом дизайне не трогаются.
  2. Частотный экран банкноты — опровергнут §5.1, повторять не буду.
  3. Подъём kwic-дефолтов — оснований нет (§3.2 полигона); переезд места жительства ≠ смена значения.
  4. Судья в прод-проводе — посчитан (§3.4), но не проектируется до отдельной санкции и до P1.
  5. Пере-замер «контексты — главный рычаг» на трудной выборке — напряжение D39.46 ↔ пакет-8 названо полигоном честно; это работа полигона, а не бэкенда, и до неё kwic трогать нечем в обе стороны.
  6. Второй детектор для латиницы (§6) — отдельный пак, отдельное решение; в этом дизайне только отмечаю, что он не блокирует ни один пункт выше.
  7. Строгая форма языкового предиката (§2.2), окно в предложениях (§4Б) — названы как опции, строить не предлагаю.

§8 Сводка: что строить, что мерить, что решать

# Пункт Действие Деньги стройки Деньги прода Двигает ли снапшот
1 Языковой экран на выходе (§2.1) СТРОИТЬ $0 $0 нет; редакторская волна ОДИН раз только у книги с англо-строкой в банке
2 Счётчик OffLanguage + громкий лог батча (§2.1) СТРОИТЬ вместе с 1 $0 $0 нет
3 Языковой фильтр на свёртке банкноты (§2.1) СТРОИТЬ $0 $0 нет
4 Нормализация обрамляющих кавычек, Q1 (§3.1) СТРОИТЬ $0 $0 нет
5 Агрегация из чекпойнтов, A1 (§3.2) СТРОИТЬ + пин инвариант «резюм даёт байт-идентичную карту» $0 $0 только у книги с >1 покупкой черновиков, один раз
6 kwic_width в пар-данные, вариант A (§4) СТРОИТЬ, файл для zh-ru НЕ шипуем $0 $0 нет
7 Doc-хвост TerminologyGate про жанровый словарь (§1) СТРОИТЬ (одна строка) $0 $0 нет
8 Пример формата в промпте роли (§2.3) МЕРИТЬ, ≈$0.009 (полигон) $0 перекупка только батчей терминолога
9 consistency как отбор банкноты (§3.3) МЕРИТЬ P1, $0 (полигон)
10 Судья-с-декоем J1 (§3.4) НЕ строить; ≈$0.025/книга, ждёт P1 + холодный старт + санкцию ≈$0.025
11 allow_short для / (§5) ДАННЫЕ, только на холодном прогоне базовая версия банка = вся книга
12 Холодный старт (§6) исполнить мини-прогон в холодной конфигурации ≤$0.23 новая база

Итого денег на всё до стройки: ≈$0.009 (полигон) + $0 (P1). Стройка пунктов 17 — $0 и без единого вызова провайдера. Прод после стройки не дорожает ни на цент.


§9 Адверсариальный селф-ревью

Бью по собственному дизайну; то, что устояло, оставлено, то, что не устояло, вычеркнуто выше.

  1. «Языковой экран — костыль; лечить надо промпт». Частично верно, и потому вариант В (§2.3) в дизайне есть. Но вход лечится вероятностно, а якорь пуст ровно там, где страшнее всего. Экран стоит $0 и молчит, когда всё хорошо. Оставляю оба, с явным порядком: гарантия не зависит от исхода замера.
  2. «Предикат сломает пару, где цель латиница, а исходник — тоже». Проверил исполнением: предикат слеп к исходнику и меняет вердикты вместе с целью. Для en→ru он даже ЦЕННЕЕ (§6.2 полигона: срыв в английский на английском исходнике глазом неотличим от работы).
  3. «≥1 буква цели — слишком слабо, cultivation (культивация) пройдёт». Верно. Назвал как вариант Б и не рекомендую в этом паке: это другой класс (формат, не язык) со своей ценой ложных срабатываний. Не прячу.
  4. «Агрегация из чекпойнтов затащит мусор отвергнутых попыток». Верно, объявил решением. Отвергнутый черновик — плохой ТЕКСТ; его банкнота от этого не становится неправдой, а подписи всё равно нет.
  5. «Агрегация нарушает детерминизм между прогонами и перебиллит волну». Самое серьёзное возражение; mining.go:108-112 предупреждает об этом прямым текстом. Ответ: множество чекпойнтов меняется ТОЛЬКО когда куплены новые черновики, то есть волна и так перекупалась. Но рассуждения мало — поэтому инвариант вынесен в обязательство приёмки (§3.2).
  6. «Кавычки надо чинить в NormalizeSourceKey, там место нормализации». Соблазнительно и почти правильно семантически. Цена — перепокупка обеих волн у каждой книги плюс смена семантики матчинга всего банка памяти ради 4.5% строк. Q1 даёт тот же результат за $0 и ретроактивно. Оси выписаны.
  7. «consistency — это переодетая частота, которую только что опровергли». Проверяю честно: частота считает вхождения в текст, consistency — долю чанков, где независимый чтец ОБЪЯВИЛ строку термином, к числу чанков, где строка вообще есть. 少年 частотен и почти никогда не объявлен — именно на этой паре они и расходятся. Но гипотеза остаётся гипотезой: поэтому P1, с критерием ДО счёта и с армом-без-фактора (голая частота обязана провалиться).
  8. «Судья дешёв — значит стройте». Считаю это ловушкой. Дёшев он на популяции, размер которой неизвестен (холодный старт не мерен), и его положительное решение — это выбрасывание строк из банка книги, то есть та самая «решение о переводе за владельца», от которой D39.47 только что отказался. Экран должен предлагать порядок, а не отнимать право подписи.
  9. «Вся эта фактура — одна книга, одна пара, одна модель». Да. Поэтому единственные вещи, которые я предлагаю строить, — детерминированные, пар-слепые и $0: они не опираются на числа этой книги. Всё, что опирается (пороги, отбор), отправлено в замер.
  10. Против собственной дельты. §2 моего же отчёта от 26.07 предлагала расширение эмиссии и считала его деньги. Замер закрыл посылку. Не переношу оттуда ничего, кроме оси «что банкуется без подписи ≠ что кладётся на подпись» — она пережила проверку и стоит под §3.0.

Критик полноты (норма D39.46, на эту фазу)

Проверка Итог
Каждый пункт записки покрыт 5 из 5 (§2 язык · §3 дисциплина · §4 kwic_width · §5 allow_short · §6 холодный старт)
Синк-таблица «источник ↔ код ↔ вердикт» §1, 12 строк + найденный doc-хвост
Проверено исполнением, а не чтением 4 опоры: приём английского боевым ParseReply, расхождение ключей кавычек, SignificantLen, предикат на 26 реальных строках
Арм без фактора заложен в P1 (голая частота) и в замер §2.3 (арм без примера)
Декой §3.4, 10% строк, посчитан деньгами
Право сказать «не надо» использовано трижды: судья (не строить), allow_short (не «просто строка»), пар-файл kwic для zh-ru (не шипуем)
Деньги названы до стройки §8, ≈$0.009 + $0
Оси каждого пункта §8, столбцы «двигает ли снапшот»
Общность по каждому пункту отвечен вопрос «пара, которой нет в репо, без правки Go»
НЕ покрыто размер популяции банкноты на полной книге (нужен холодный старт) · вторая пара · вторая модель · разметка «терм/мусор» одного разметчика · доступность сохранённого сырья пакета-8 для приёмки §2.5 п.5

§10 Что на владельце

  1. Санкция на стройку пунктов 17 §8 (языковой экран + счётчик + фильтр свёртки + кавычки + агрегация + kwic_width в пар-данные + doc-хвост). Всё детерминированное, $0, прод не дорожает.
  2. Санкция на два замера полигона: пример формата в промпте ≈$0.009 и consistency $0.
  3. Решение по §3.4: ставить ли семантический экран вообще, и если да — J1 перед терминологом, ≈$0.025/книга. Моя рекомендация — отложить до P1 и холодного старта.
  4. Решение по §6: идёт ли мини-прогон D39.40 в ХОЛОДНОЙ конфигурации (пустой банк). Одни деньги — пять ответов, включая единственную неизмеренную цифру пакета-8.
  5. Решение по §5: добавлять ли allow_short для / — и подтверждение, что это делается на холодном прогоне, где перепокупка книги стоит ноль.
  6. Вопрос к полигону (не к владельцу): живо ли сохранённое сырьё 147 вызовов пакета-8 — на нём языковой предикат принимается на настоящих данных вместо фикстур, за $0.

СТОП. Стройка — после ратификации. Ни строки продового кода эта сессия не изменила и не коммитила.