24 KiB
Пак-20, ДЕЛЬТА: размотка жанрового словаря (D39.47) + дизайн-секция по расширению эмиссии
Ревью-шапка (оркестратор №8, 26.07): РАЗМОТКА ПРИНЯТА И ЗАЛЕНДЕНА (D39.48). Проверено исполнением: остатков жанрового механизма в Go нет (grep по GenreGlossary/genreAgrees/TM-GENRE — только документирующая строка в промпте роли), файл данных удалён;
go vetчист, целевые пакеты (lang/terminology/pipeline)-raceзелёные, golden PASS БЕЗ пере-капчера — что независимо доказывает клейм «версия langpack побайтно та же» (LangpackVersion фолдится в снапшот, сдвиг уронил бы golden). Отклонение (пере-писанная интерим-шапка промпта роли) — принято: фаза B действительно закрыта. Три синк-находки §2 (три потребителя эмиссии и переформулировка оси · доля отказов роли на мусоре не измерена · kwic-дефолты не покрыты замером) — подтверждены чтением кода (attachConsolidatedDstдействительно матчит только по ключам mined) и вместе с вопросом совместного recall (майнер∪банкнота) составляют пре-замеры перед дизайном расширения; санкции — D39.48.
Статус: размотка ИСПОЛНЕНА, дизайн-секция §2 — ПРЕДЛОЖЕНИЕ, стройка НЕ начата. Записка оркестратора №8 от 26.07.2026 (D39.47) получена релеем и исполнена по букве: снято ровно перечисленное, ⟦TM-CANON⟧-якоря и
CanonConflictsне тронуты, узкое правило транскрипции в промпте роли не тронуто. Зона:backend/+ этот отчёт. Полигонскиеeval/pkg7/*и правки отчётов пакета-7 — чужие, не тронуты. Сессия НЕ коммитит. Деньги сессии: $0 — ни одного платного вызова (размотка и проверки офлайновы). Предыдущий отчёт пака:PACK20_BANK_BUILD_2026-07-26.md.
§1. Размотка — что снято и чем это доказано
Все адреса записки сверены по HEAD (e618b57) и отработаны.
| файл | что сделано |
|---|---|
internal/terminology/terminology.go |
снят ScoreOpts.GenreDst; снят фактор (d) genreAgrees/genreBonus из ScoreVariants; снята константа genreBonus; снята функция genreAgrees; снята substringsOf (её единственный потребитель — genreAgrees); GenreMarker снят, RenderAnchors(canon, genre) → RenderCanonAnchor(canon) |
internal/pipeline/terminologist.go |
снята genreGlossaryMap(); из ScoreOpts ушла передача; из terminologyMessages ушла сборка ⟦TM-GENRE⟧-блока |
internal/lang/langpack.go |
снято поле Pack.GenreGlossary, тип GenreTerm, parseGenreGlossary, GenreGlossaryFor, опциональное чтение файла в Load |
configs/langpacks/zh-ru/genre-glossary.txt |
удалён (rm, не git rm — индекс не трогаю, лендинг ваш) |
prompts/zh-ru/terminologist.md |
снят абзац про ⟦TM-GENRE⟧; шапка переписана с «ИНТЕРИМ, ждёт фазу B» на v2 с итогами фазы B (см. §1.3) |
| тесты | langpack_test.go — сняты три теста механизма словаря и хелпер mirrorPackWithout; terminology_test.go — снят пин фактора и пин двух-блочного якоря; miningstop_join_test.go — пин «канон перед жанром» заменён пином «канон — ЕДИНСТВЕННЫЙ блок» |
Осталось нетронутым по требованию записки: ⟦TM-CANON⟧-якорь и CanonFor/CanonConflicts; узкое правило транскрипции
в промпте роли; факторы частоты, конформанса, соседа и леммы. Формула §C2-3 вернулась ровно к ратифицированным
четырём множителям — лишний пятый был как раз жанровым.
1.1 Мутации (обязательный пункт записки: «вернуть фактор» должно быть красным)
| # | мутация | результат |
|---|---|---|
| M1 | фактор возвращён руками в ScoreVariants (жёстко зашитый 修炼 → культивация, ×1.6) |
КРАСНАЯ: TestScoreVariantsNeighbourAnchorAndMalformed — практик культивации обошёл совершенствующийся при 3 против 1, и в Signals появился genre |
| M2 | второй якорный блок возвращён руками в RenderCanonAnchor |
КРАСНАЯ ДВАЖДЫ: юнит TestCanonForRanksContainmentFirstAndCaps (пин рендера побайтно) и провод TestCanonAnchorPutsTheSignedBankInFrontOfTheTerminologist (на проводе найдено 2 якорных маркера вместо 1) |
| M3 | контрольная (обратная): правка surnames-single.txt и palladius.txt |
КРАСНАЯ без правки — новый TestPairDataEditMovesThePackVersion требует движения версии на любой авторский файл пака |
Пин проводом (M2) сделан обобщённым, а не по строке TM-GENRE: тест считает все ⟦TM- на проводе роли и
требует, чтобы их было ровно столько же, сколько ⟦TM-CANON⟧. Любой второй якорный блок — как бы он ни
назывался — красит его. Это дешевле, чем пин конкретного удалённого маркера, и переживает переименования.
1.2 Оси — проверено исполнением, а не заявлено
- Снапшоты книг и волны НЕ двигаются. Проверил прогоном на копии шиппингового пака: файл был вне
pack.Version(), поэтому его удаление даёт побайтно ту же версию (langpack-v2-a28ed743c99cдо и после восстановления файла в каталог пары).LangpackVersion— единственный вход пака в снапшот (snapshot.go:428), значит ни один юнит ни одной книги не пере-оплачивается и--resnapshotне нужен. Проба-скрипт удалён после замера. - Пере-покупаются только батчи терминолога. Байты промпта роли и отрендеренный якорь входят в
request-hash вызовов класса
terminologist; per-stagePromptSHA256терминолога не видит (он не стадия, а адресный лейбл),gates.terminologyне фолдится намеренно. - Мёртвого кода не осталось:
go build,go vetчисты;gofmt -lпоказывает только досессионныйinternal/llm/llm.go, которого я не касался.
1.3 Одно отклонение от буквы записки — объявляю, а не прячу
Записка перечисляла снятия и не говорила про шапку промпта роли. Я её переписал: она заявляла «ИНТЕРИМ v1,
финальный текст выберет фаза B», а фаза B уже исполнена и принята (D39.46). Оставить шапку значило бы
держать в бою заведомо неверную запись о статусе. Новая шапка фиксирует то, что фаза B измерила (контексты —
главный рычаг; лемма; батчи; узкая форма правила транскрипции — с указанием, что широкая опровергнута T2, а
полное снятие ломает 月光蛊/王婆) и что ⟦TM-GENRE⟧ снят по D39.47. Текст самих правил роли не изменён, кроме
удаления жанрового абзаца. Если это лишнее — откат в одну правку данных, Go не задет.
1.4 Верификация
go test ./... зелёный · -race зелёный на pipeline/terminology/lang/miner/membank ·
TestGoldenDeterminism PASS (инвариант детерминизма №8 держится) · go vet чист.
§2. Дизайн-секция: чем закрывать покрытие вместо словаря — СТРОЙКА НЕ НАЧАТА
Связывающее ограничение теперь одно: эмиссия. 修行 ×326 не увидит никто, потому что
emissionEligible требует тип ∈ {name, place, title}, а у доменного понятия паттерна нет
(miner_emit.go:302-313).
2.0 Находка чтением кода, которая меняет форму вопроса: потребителей у эмиссии ТРИ, а потолок один
G7 сформулирован как «показ владельцу ≠ подача терминологу». По коду их не два, а три, и один
emitRankCap=200 обслуживает все:
| # | потребитель | что ему нужно | где в коде |
|---|---|---|---|
| C1 | лист на ПОДПИСЬ владельцу | объём, который человек физически подпишет (17 строк — да, 15 418 — нет) | signatureMapPath(), стоп-таблица |
| C2 | АВТО-банк (D39.42 п.3): строки ⟨проверить⟩, которые едут в редактора и в следующие черновики | покрытие; подпись НЕ требуется | writeAutoBank → seedGlossary (mining.go:176-181) |
| C3 | вход ТЕРМИНОЛОГА | покрытие; цена = токены | buildBankCandidates → runTerminologist |
И вот что важно для решения: расширять один только вход роли (C3) почти ничего не даёт. Проверено чтением
потока: консолидация приклеивается перебором строк ДЕЛЬТЫ с поиском по ключу
(terminologist.go:384-396, вызов —
mining.go:141), поэтому dst кандидата, которого в mined
нет, в банк не попадает: он виден только в сайдкар-таблице стопа (она рендерится из полного cands,
mining.go:253-266) и умирает вместе с прогоном — ни в
инъекцию редактору, ни в следующий черновик, ни в лист подписи. Значит recall лечится только расширением
того, что попадает в mined, то есть C2, а C1 обязан остаться человеческого размера. Правильная формулировка
оси, по-моему, не «показ ≠ подача», а «что БАНКУЕТСЯ без подписи ≠ что кладётся НА ПОДПИСЬ»; подача роли
едет вместе с первым.
2.1 (а) Потолок и фильтр типа — три варианта, все на данных абляции полигона
Абляция фазы A (gu25, 56 термов сида в срезе): A — как в проде 17 строк / recall 0.089 · B — снят emitRankCap
624 / 0.429 · C — B + снят фильтр типа 13 246 / 0.875 · D — C + снята длина ≥2 15 418 / 0.911;
precision спот-чек по C ≈ 0.15 (один разметчик).
| вариант | что снимается | объём (gu25) | recall | деньги роли при kwic 3×40 | оси |
|---|---|---|---|---|---|
| а1 | emitRankCap поднимается до N (конфиг), фильтр типа остаётся |
624 при N=∞ | 0.429 | ≈$0.05 | самый дешёвый; жанровые понятия по-прежнему НЕ придут (это фильтр типа, не потолок) |
| а2 | а1 + фильтр типа снят ДЛЯ БАНКА (C1 остаётся под потолком) | 13 246 | 0.875 | ≈$1.10 | закрывает 修行/蛊师/真元; вносит ~11k шумных строк в авто-банк — см. риск 2.1.1 |
| а3 | а2 + одноиероглифные ПО ЧАСТОТЕ (не сплошь): runeLen==1 допускается, если терм в топ-K книги по частоте либо является головой ≥2 других кандидатов |
13 246 + единицы | ~0.88+ | +≈$0 | адресно возвращает 蛊 (8700×) и 转 (2171×) — заглавные понятия книги — не покупая +2 172 строки варианта D |
Замер, которого нет ни у кого и который решает вопрос (риск 2.1.1). При precision ≈0.15 расширение
кладёт в авто-банк ~11k мусорных кандидатов (便是, 的关, 动的). Мусор безвреден ровно до тех пор, пока
роль его отклоняет (⟦TM-NO-DST⟧ → status:auto, без dst, в инъекцию не попадает). Мусор, которому роль
ВЫДУМАЛА перевод, становится строкой draft ⟨проверить⟩ и едет в память редактора. Доля отклонений на
мусорном хвосте — единственное неизвестное, отделяющее «расширение работает» от «расширение отравляет банк»,
и она не измерена ничем: набор фазы B состоял из настоящих термов. Предлагаю мерить ДО стройки: 100
кандидатов из хвоста варианта C, один прогон, ≈$0.01. Критерий назвать заранее (моё предложение: доля
отклонений на заведомом мусоре ≥0.9, иначе расширение требует второго фильтра).
- Второй фильтр, если понадобится, уже ратифицирован как годный: D39.46 п.2 — слепой судья с декоем годен как катастроф-экран (разрыв до декоя 1.27–1.42 против межарменного 0.20). Это ровно задача «отсечь грубый мусор, не ранжируя тонкое». То есть при провале критерия ответ известен и не нов.
- Инженерная цена, названная честно: кластеризация алиасов —
proposeAliasEdges, полный перебор пар (miner_alias.go:80-82). 200 кандидатов = 19 900 пар, 13 246 = 87.7 млн пар, часть с посентенсным сканом текста. Полигон прогонял абляцию на 25 главах, значит это терминируется; на 100 главах и на приёмочной книге (где детектор сам квадратичен, A3.7) не мерено ни разу. Чинится дёшево, если подача роли собирается ДО кластеризации (роли нужны поверхности с контекстами, а кластеры нужны подписи) — но это уже решение о стройке, и я его не принимаю.
2.2 (б) feed_cap — сколько подаётся роли
Предложение прежнее (D39.45), с деньгами и без тихой вставки: gates.terminology.feed_cap — сколько
кандидатов уезжает в роль, ранжирование тем же §C2-3, таблица владельцу показывает ВСЁ, в лог — «подано N
из M». Измерено мной на живом прогоне: 223 входных токена на кандидата при kwic_per_term: 3, kwic_width: 40 (7 799 токенов на 35 кандидатов).
| подача | вход, токенов | цена книги | что покрывает |
|---|---|---|---|
| 200 (сегодня де-факто) | ~45k | ≈$0.02 | recall 0.089 |
| 400 | ~89k | ≈$0.035 | recall ~0.2–0.4 |
| 13 246 (весь ранжированный набор варианта C) | ~2.95M | ≈$1.10 | recall 0.875 |
Оси: feed_cap — деньги, а не качество; он не решает recall (см. §2.0 — подача без банка теряется) и
нужен как предохранитель на случай книги, где ранжированный набор окажется на порядок больше ожидаемого.
Сегодняшний бюджет-гейт (теперь предварительный) страхует по деньгам, но режет хвост батча, а не наименее
ценное — вот единственная содержательная разница.
2.3 (в) kwic_per_term / kwic_width — самая тонкая дыра и самая дешёвая её проверка
D39.46 ратифицировал «контексты — главный рычаг» (арм без контекстов теряет 6–7 совпадений с подписью из 19 при внутриармовом размахе 1). Но замер сделан на конфигурации полигона ~8 контекстов × ~120 символов (§A5.5), а в бою стоят мои дефолты 3 × 40 — то есть ⅛ объёма контекста. Измерены две точки, 0 и ~8×120; наша точка лежит между ними и ничем не покрыта. Формально ратифицированный вывод верен, а вот дефолт, который реально едет в книгу, не аттестован ни разу — и это мой долг, а не полигонский.
Предлагаемый замер (свой арм на каждый фактор — норма D39.46(а)): тот же harness terminologist_arms.py
на тех же 19 термах с подписью владельца, армы kwic 0 (контроль, он уже есть — P0b) · 3×40 (наш дефолт) ·
8×120 (точка полигона) · 8×40 и 3×120 (развязка «сколько контекстов» против «какой ширины»), по 3
повтора. По их же фактическим цифрам повтор = 12 вызовов ≈ $0.008 ⇒ весь замер ≈ $0.12. Критерий назвать
заранее: если 3×40 не хуже 8×120 больше чем на 1 терм из 19 (размах повторов у них = 1), дефолт
подтверждён и дешёвый; иначе дефолт двигается, и цена подачи умножается вместе с ним.
Почему это нельзя решать отдельно от (а) и (б): цена роли = подача × объём контекста. При 8×120
per-candidate вход растёт примерно вчетверо-вшестеро, и «весь банк» уезжает с ≈$1.10 к $4–7/книга. Три
ручки — один бюджет; развилка «широкая подача с узким контекстом» против «узкая подача с широким контекстом»
на сегодня не измерена вовсе, и по-моему именно она — настоящий вопрос фазы 2, а не выбор потолка.
Когда мерить: до стройки расширения и вместе с замером отклонений (§2.1) — оба идут на одном harness, суммарно ≈$0.13, оба дают критерий ДО трат на код.
§3. Что на владельце / оркестраторе
| # | пункт | почему это не моё решение |
|---|---|---|
| 1 | Расширять C2 (авто-банк без подписи), а не C1 (лист на подпись) — подтвердить формулировку оси | это решение о том, сколько неподписанного движок кладёт в память книги; принцип D39.47 («больше строк на решение, ничего не решая за владельца») формально выполняется и в том, и в другом варианте, но цена разная |
| 2 | Санкция на два дешёвых замера ДО стройки (≈$0.13 суммарно): доля отклонений роли на мусорном хвосте · кривая kwic между 0 и 8×120 | оба подрывают/подтверждают посылку стройки, а не её исполнение — правило №4 D39.47 требует поднять это, а не строить формально |
| 3 | Вариант расширения: а1 / а2 / а3 | деньги и объём различаются на порядок |
| 4 | Дефект кластеризации 葛家 ⊃ 族长 (G8) — чинить ли САМУ кластеризацию |
минимум пака-20 исполнен (поверхность не теряется при сборке входа, провенанс proposed for 族长 виден); сам фикс — отдельное решение, как и сказано в аддендуме |
| 5 | Досессионные долги из отчёта пака-20 остаются: AttachKWIC квадратичен (59 с на книжном масштабе) · чекпойнт роли — по прогону, а не по батчу · bankTokenBudget выведен из зашитого zh→ru worst case |
не трогал, чужих решений не принимал |
СТОП. Лендинг — оркестратора. Сессия не коммитила.