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

24 KiB
Raw Permalink Blame History

Пак-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-stage PromptSHA256 терминолога не видит (он не стадия, а адресный лейбл), 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): строки ⟨проверить⟩, которые едут в редактора и в следующие черновики покрытие; подпись НЕ требуется writeAutoBankseedGlossary (mining.go:176-181)
C3 вход ТЕРМИНОЛОГА покрытие; цена = токены buildBankCandidatesrunTerminologist

И вот что важно для решения: расширять один только вход роли (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.271.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.20.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 ратифицировал «контексты — главный рычаг» (арм без контекстов теряет 67 совпадений с подписью из 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 к $47/книга. Три ручки — один бюджет; развилка «широкая подача с узким контекстом» против «узкая подача с широким контекстом» на сегодня не измерена вовсе, и по-моему именно она — настоящий вопрос фазы 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 не трогал, чужих решений не принимал

СТОП. Лендинг — оркестратора. Сессия не коммитила.