textmachine/docs/archive/reports/BANK_CLUSTER_FIXPACK_2026-08-08.md

65 KiB
Raw Blame History

Отчёт бэкенд-сессии: ФИКС-ПАК БАНКА «КЛАСТЕРЫ + НАБЛЮДАЕМОСТЬ» (строка 128 + хвост 129 + $0-минимум 130)

⟶ РЕВЬЮ-ШАПКА ПРИЁМКИ (оркестратор №15, 08.08.2026, D39.118): ПРИНЯТ И ЗАЛЕНДЕН bccf2d8. Приёмка адверсариальная, четыре контура: слепой дифф-ревьюер (мнение ДО отчёта) · пере-ран заявлений исполнением · 8 своих посадок в копии зоны (6 CAUGHT сразу; 2 выжившие добиты дофиксом) · охота вне карты отчёта (5 находок, 1 major). Дофикс-раунд закрыл ВСЁ: блокер lint (3× errcheck) · MAJOR «WARN строки 130 структурно мёртв» (само-исключение через сырой seed) · 2 дыры пинов на space-run-форме · инертный bankParseVersion · 3 тихих отказа канала семей. Ре-чек дофикса: make battery целиком EXIT=0 (lint 0 issues) · -race EXIT=0 · 685→717 подтверждён независимым счётом · пере-посадки 3/3 CAUGHT. Поправки приёмки к телу: клейм «батарея зелёная» ПЕРВОЙ редакции был ложным (признано в §3 честно) · сдвиг edit-волны происходит и при ВЫКЛЮЧЕННОМ гейте терминолога (дельта пишется на $0-пути mining-стопа) и перекупает также classify-чекпойнты (контент батча делит рендер) — уточнение к §2 · замер §7.3 «0/585» = ВНУТРЕННИХ прогонов пробелов (наивный grep без rstrip даёт 55 хвостовых хитов, парсер их триммит) · пин покрытия типов работает по зеркальной фикстуре zhFamily — рассинхрон зеркала со shipped-строками пином не ловится (принято риском). Решения §7 ратифицированы D39.118.

Исполнен 08.08.2026 по промту docs/BACKEND_BANK_CLUSTER_FIXPACK_SESSION_PROMPT.md (выдан 04.08, актуализирован 08.08; после приёмки — в архиве промтов). Зона: backend/. Деньги: $0 — платных вызовов не было ни одного; вся батарея локальная. НЕ коммичено — дерево передаётся оркестратору. Живой сосед на момент сессии — полигон эксп-22 (eval/tenant_panel/, eval/role_topology/absjudge.py): не тронут, git status до и после чист по чужой зоне.


§1. Что сделано — по работам, с file:line

G1. Семьи термов и со-батчинг юнитами

Что Где
Данные семей (per-script, НЕ Go-константы) backend/internal/lang/bankdata/family-morphology.txt
Загрузчик + резолв по языку + версия плоскости backend/internal/lang/bankdata.go (FamilyMorphology, BankDataVersion)
DetectFamilies рядом с DetectSeries backend/internal/terminology/series.go:199-295
MergeUnits (серия ⊕ семья ⊕ containment → один юнит) backend/internal/terminology/series.go:314-394
Batch/orderByUnit переведены на словарь ЮНИТОВ backend/internal/terminology/terminology.go:1119, series.go:115-142
Проводка + WARN на отказ слияния backend/internal/pipeline/terminologist.go:301-323
Резолв параметров на ЗАГРУЗКЕ (fail-loud на опечатку типа) backend/internal/pipeline/terminologist.go:126-158

Слияние и два гарда (правлено по адверсариальному ревью, см. §4а). Юниты сливаются семьёй; серия придерживает ТОЛЬКО своих членов (семья формируется без них), а не отменяет слияние целиком; кап членов отказывает слиянию. Оба случая — это семья, которую владелец встретит разрезанной, поэтому оба СЧИТАЮТСЯ (MergeStats{Refused, Held}) и попадают в один WARN. Тест родства — «делят руну» (в плотном письме руна = морфема), а не подстрока.

Правила (как ратифицировано промтом). Имя — общая ПРЕФИКС-морфема ≥2 рун; реалия (term|place|title) — общая СУФФИКС-морфема ≥2 рун (head-final); асимметрия и обе длины — данные. Якорем семьи может быть и сам кандидат — иначе голова семьи уезжает в другой вызов; ⚠ якорь обязан быть ≥2 рун, поэтому ОДНОРУНОВАЯ голова (, , ) под это не подпадает и остаётся в отдельном вызове от своей семьи — класс НЕ покрыт, см. §5. Containment-канал (元海 ⊂ 元海空窍, оба mined) — отдельный генератор с тем же механизмом; якорь ≥2 рун, чтобы одна общая морфема не смела полбанка в один вызов.

(а) Многорунные модификаторы серий — критерий выполнен. 一转…九转 — равнодлинная серия; 十一转/十二转 — три руны и в неё не попадают. Выбранная форма — НЕ отдельное правило серия-канала, а слияние юнитов: семья с якорем 一转 (containment/суффикс) пересекается с серией, и юниты сливаются. Линейка 1..12 держится ОДНИМ батч-юнитом — пин TestFamilyKeepsAVariableLengthRankLineWhole. Равнодлинные серии не регрессируют: у двухрунных поверхностей нет собственного суффикс-корня ≥2 рун, семья про них молчит — пин TestFamilyDoesNotRegressAnEqualLengthSeries.

(б) Майнерские containment-парыTestContainmentPairSharesAUnit.

Наблюдённый эффект на реальном корпусе (testdata/bankfull-surfaces.txt — дистилляция банка coldrun-a, 150 поверхностей; TestFamiliesOnBankFullFixtureAreBoundedAndWhole): 150 кандидатов → 14 юнитов, наибольший 20, отказов по капу 0, придержано серией 2. Содержательно юниты правильные: вся линейка (12 членов вместе с 一转蛊师/九转境界), композиционный куст 元海/真元/空窍 (10 — ровно тот, на котором §A4 намерил внутри-прогонные противоречия), клан 古月 + построенные на нём поверхности (20, включая 方源/方正/族长/四代族长). Два «придержания» — это ранговый юнит , которому гард не дал поглотить чужие реалия-семьи (巅峰, 蛊虫): они модифицируются рангом, но морфологически к нему не относятся. Цена придержания названа честно ниже.

G2. ConsolidationConflicts

backend/internal/terminology/terminology.go:918-941 — тонкий брат CanonConflicts: тот же containment + lexemeSubset, правая сторона — консолидации ТЕКУЩЕГО прогона. Вызов terminologist.go:424-437 рядом с канон-проверкой; WARN с именованными парами, счётчик terminologyResult.SelfConflicts, per-term Contradictions → колонка стоп-таблицы (mining.go, renderBankStopTable, cmd/tmctl/render.go). $0, evidence-сторона, не фолдится. Пины: TestConsolidationConflictsCatchesTheRunContradictingItself, TestSelfContradictionIsReportedAtTheStop.

G3. Наблюдаемость арбитража

  • (а) BankStopRow += Signals (сигналы §C2-3 ТОП-варианта — решил печатать по строке, а не по-вариантно: подписывают строку, а не вариант), Invented (норм-dst ∉ вариантов черновиков, сравнение под тем же фолдом целевой формы), Conf, Conventions, Contradictsmining.go:244-310. Стоп-таблица и stdout-баннер печатают всё это (mining.go:renderBankStopTable, cmd/tmctl/render.go:103-153).
  • (б) Третье поле «уверенность 0100»backend/prompts/zh-ru/terminologist.md (дата-правка + шапка с ратифицированной ГРАНИЦЕЙ применения). Парсер: terminology.go:1022-1027 + confidenceField (:1073) — девиация от буквы промта, см. §4 п.1.
  • (в) reasoning_content в схему НЕ заведён (проверено grep'ом — его там и не было).
  • (г) Находки приёмки:
    • alias-джойн подписной картыproposalsFromObserved заменён на proposalsFromCandidates (mining.go:404-427): карта теперь строится из ТЕХ ЖЕ кандидатов, что и стоп-таблица, поэтому предложение, пришедшее через алиас кластера, попадает в note представителя по построению, а не вторым джойном, который надо руками держать в согласии. Провенанс алиаса едет в note (miner_emit.go: DstProposal.Via + viaNote). Пин TestSignatureMapJoinsAliasProposals.
    • имена эвиктнутыхchunkrun.go:168-236 (recordEvicted/reportEvicted) + аксессор membank.PickedEntry.Src(); отчёт один на волну (waverun.go, после драфт-волны). Форма хранения — ЛОГ, без миграции схемы (обоснование и альтернатива — §4 п.4). Пин TestEvictedBankRowsAreNamed.
    • per-batch «спросили N / ответили M» + WARN на ПУСТОЙ ответ — рендер terminologist.go:337-360, классификатор terminologist.go:530-548. Ложной тревоги на батчах, до которых бюджет не дошёл, нет: bankRoleRun.attempted отделяет «вызвали и получили пусто» от «не вызывали».
    • TestTerminologistEmptyReplyIsLoud переписан на два подслучая — прозу (старая ветка) и НАСТОЯЩИЙ пустой completion (miningstop_join_test.go). Имя теста перестало врать.
  • (д) $0-минимум строки 130mining.go:525-575: дифф старого и нового авто-файла при writeAutoBank + WARN с ИМЕНАМИ выпавших термов, названным rank_cap и советом «промоутни в mined_delta, тогда это сид-поверхность и её больше не срежет». ГРАНИЦА соблюдена: накопительного мерджа нет, слоты не освобождаются, memory_version не двигается — пин TestAutoBankRewriteNamesDroppedTerms проверяет и то, и другое.

G5. foldVariants

terminology.go:222-296. Голос считается по классу целевой формы (NormalizeTargetForm инжектится как функция — пакет остаётся пар-агностичным), показывается СЫРАЯ форма (представитель — самая частая сырая форма класса). Рядом со Spread() (сырых форм) появился Conventions() (классов). Via при фолде агрегируется честно: если хоть один вклад пришёл на СВОЙ ключ — вариант прямой; если все через алиасы — перечисляются все. Пины: TestFoldVariantsCountsConventionsAndShowsRawForms, TestFoldVariantsKeepsViaProvenanceHonest.

Хвост 129. Парсер банкноты

banknote.go:236-274. Тип опознаётся по ЗАКРЫТОМУ словарю на ПОСЛЕДНЕМ поле, остальное — src + dst с ре-джойном (зеркало дисциплины terminology.ParseReply). Восстановление порядка колонок обобщено на разъехавшийся dst: src — одно поле при любом чтении, поэтому «объявленный порядок» и «обратный» отличаются только тем, какое поле берётся за src. Пин TestParseBanknoteRejoinsASplitRendering (обе формы + «неизвестное последнее поле — часть dst, а не молча выброшенная колонка» + обратный порядок с разъехавшимся dst, который до пака был parse_fail в обеих версиях).

Нашёлся ли двойной пробел в артефактах. ~/books/gu-zhenren/coldrun-a/ не читал: доступ к нему в этой сессии не проверялся, а промт разрешает только чтение замороженного эталона, не требует его. Цена фолда названа честно: неизвестна — авто-банк изменится ровно на тех строках банкноты, где модель поставила двойной пробел внутри dst; поиск по эталону — задача одного grep при следующем касании артефактов, вопрос вынесен в §5.

Опции

  • BanknoteTruncated (гигиена) — СДЕЛАНО, вариантом «честно провести флаг»: колонка была структурно недостижима (truncatedGeneration пинился в false), теперь читается из finish-причины (banknote.go:352-362). Поведение finish=stop-only НЕ тронуто — кандидаты по-прежнему не принимаются, NLines по-прежнему 0. Пин TestBanknoteTruncatedIsReachable. Миграции не потребовалось.
  • NoDst — СДЕЛАНО: узкий матч испорченного сентинела («⟦TM-NO-DST⟧», ⟦TM-NO-DST⟧.) — terminology.go:1096-1103, пин TestParseReplyAcceptsAMangledDeclineSentinel. Отказ — это РЕШЕНИЕ §C2-7, а до пака мятый сентинел считался сломанной строкой.
  • Перф AttachKWIC через Aho-Corasick (строка 37)НЕ делал, см. §4 п.5.

§2. Фолд-ось и голден (обязательная секция)

Предъявление, ИСПРАВЛЕННОЕ по многоагентному ревью. Первая редакция §2 утверждала «волны и снапшоты книг НЕ двинулись» без оговорки — это неверно и опровергнуто исполнением. Верно так: ДРАФТ-волна не двинулась; EDIT-волна двигается, и двигается bank-only.

  • Драфт-волна цела. Строки авто-банка — Source:"mined", а BASE-версия банка их исключает (membank/memory.go:349), поэтому черновые чекпойнты резюмятся за $0. Проверено ИСПОЛНЕНИЕМ (см. ниже).
  • Edit-волна двигается. Пак меняет ПОЛЕ dst в дельте: источник предложений сменился с сырого драфт-порядка (proposalsFromObserved) на ранжированные и фолднутые кандидаты (proposalsFromCandidates). На одинаковом входе это даёт другой memory_version тремя независимыми механизмами (пере-ран рядом с HEAD-функцией через настоящие DeltaYAML → LoadGlossarySeed → ComputeVersionScoped): (i) ре-ранжирование §C2-3 против «сколько чанков предложило»; (ii) фолд орфографии §G5; (iii) алиас-маршрутизация — этот третий канал ЗАКРЫТ правкой по ревью, см. §4-0(ж). ENRICHED-версия (memory.go:348) — вход снапшота edit-волны, значит он сдвигается.
  • Цена названа. Сдвиг НЕ тихий (stagerun.go требует явный --resnapshot) и классифицируется moveBankOnlyrepin.go отдаёт $0-репин каждой единице, в инъекцию которой терм не попадает. Платятся только единицы, где терм реально встречается. Для частотного терма (фамилия протагониста) это может быть почти вся книга — это решение владельца, а не гигиена, и оно вынесено вопросом в §7.

Что проверено ИСПОЛНЕНИЕМ и держится:

  1. git status по backend/internal/lang/data/пусто: ни один файл ВОЛНОВОЙ плоскости не тронут, значит lang.EmbeddedVersion() байт-стабилен. ⚠ Это ВХОД волновой плоскости, а не содержимое банка — именно эту разницу первая редакция §2 и смешала.
  2. TestGoldenDeterminismPASS без пере-захвата testdata/golden/capture.golden. Голден пинит snapshot id обеих волн и request_hash каждого вызова; если бы снапшот сдвинулся, он бы упал первым.
  3. TestTerminologyIsNotSnapshotFolded — PASS.
  4. Новый пин TestBankPlaneIsOutsideTheWaveFold (internal/lang/bankdata_test.go) держит это ВПЕРЁД: файл семей не должен попадать в набор, который хеширует EmbeddedVersion.

Что двигается. Состав батчей терминолога → RequestHash бэнк-батчей: G1 (состав юнитов), G3 (промпт-файл в system-сообщении) и G5 (фолд-счёт и Via меняют строку drafts: на проводе) двигают их ОДНИМ разом. Перекупка — центовая (весь проход терминолога на 10-главном стенде — $0.0057, D39.90). Вне снапшота и вне провода: G2 целиком, печать стоп-таблицы, дифф авто-банка, список эвиктнутых.

⚠ Расхождение с промтом, разрешённое в пользу §3 промта. Промт §2 G1 предлагал положить параметры семей в internal/lang/data/script-series.txt «новой колонкой или соседний файл». Проверено по коду: internal/lang/data/* целиком хешируется в lang.EmbeddedVersion() (embedded.go:72-82), а EmbeddedVersion фолдится в снапшот ВОЛНЫ безусловно (snapshot.go:381-389,442). Любая правка там сдвинула бы снапшот обеих волн, и classifySnapshotMove (repin.go:61-91) классифицировал бы это как moveOther — то есть без права на $0-репин: перекупалась бы вся драфт-волна книги, а не центовые бэнк-батчи. Это прямо противоречит §3 промта («волны/снапшоты книг НЕ двигаются») и ратифицированной позиции «терминология не фолдится» (config.TerminologyGate, TestTerminologyIsNotSnapshotFolded). Выбрана «соседний файл» в буквальном смысле — отдельная embed-плоскость internal/lang/bankdata/ со своим //go:embed и своей версией BankDataVersion(), которая логируется с прогоном (terminologist.go, поле bank_data=) и фолдится там, где ей место — в содержимом батча, то есть в RequestHash бэнк-батчей. Байты по-прежнему ЕСТЬ версия; они просто не выдают счёт за волну, которую не могут изменить.

Наблюдение по батч-чекпойнтам (строка 38, только наблюдение, работа в пак не входит). Бэнк-батч адресуется ординалом (chunk.Chunk{Chapter: 0, ChunkIdx: i}, terminologist.go), а порядок батчей — частотный (Batchsort.SliceStable по batchFreq). G1 меняет СОСТАВ юнитов, значит меняет и суммарную частоту батчей, значит двигает те же ординалы: на первом прогоне после пака перекупаются все бэнк-батчи, а не только те, чей текст изменился. Цена та же центовая, но механика именно такая — «со слов» строки 38 подтверждается кодом.

Голден-счётчики волн прошли БЕЗ пере-захвата (см. п. 2 выше). Пере-захвата голдена в паке НЕ было.


§3. Самопроверка (мандат 12.07)

  • make battery — ЗЕЛЁНАЯ ЦЕЛИКОМ (build · vet · fmt · lint · test), локально, $0. ⚠ Первая редакция отчёта заявляла «батарея зелёная», прогнав только build/vet/test: гейт lint не гонялся и был КРАСНЫЙ — 3× errcheck в новом bankfixpack_test.go (runToSignatureStop возвращает sentinel-ошибку *WaveSignatureStop, и её отбрасывание errcheck ловит). Поймано приёмкой; клейм был ложным, теперь предъявляется тем, что названо.
  • -race на всех затронутых пакетах (internal/pipeline 83 с, terminology, miner, cmd/...) — EXIT 0.
  • Дифф ^func Test — ИСПОЛНЕНИЕМ (git grep HEAD против рабочего дерева, не по памяти): было 685, стало 717, добавлено 33, удалён 1. Удалён TestParseBanknoteTruncationTolerated — вместе с недостижимой веткой, которую он пинил (см. §1 «Опции»); его заменил TestParseBanknoteShortLastLineIsAParseFail. Новые: TestAutoBankRewriteNamesDroppedTerms · TestBankPlaneIsOutsideTheWaveFold · TestBankStopTableCarriesTheArbitrationRecord · TestBanknoteTruncatedIsReachable · TestConsolidationConflictsCatchesTheRunContradictingItself · TestContainmentPairSharesAUnit · TestDetectFamiliesGroupsBySharedRootAsymmetrically · TestEvictedBankRowsAreNamed · TestFamiliesOnBankFullFixtureAreBoundedAndWhole · TestFamilyChannelOffIsTheSeriesMap · TestFamilyDoesNotRegressAnEqualLengthSeries · TestFamilyKeepsAVariableLengthRankLineWhole · TestFamilyMergeRefusesAnOversizeUnit · TestFamilyMorphologyForZhIsLive · TestFamilyMorphologyIsData · TestFamilyMorphologyRefusesCorruptRows · TestFoldVariantsCountsConventionsAndShowsRawForms · TestFoldVariantsKeepsViaProvenanceHonest · TestParseBanknoteRejoinsASplitRendering · TestParseReplyAcceptsAMangledDeclineSentinel · TestParseReplyTakesTheConfidenceFieldBeforeRejoining · TestSelfContradictionIsReportedAtTheStop · TestSignatureMapJoinsAliasProposals · TestSeriesHoldsOnlyItsOwnMembers · TestFamilyMergesAcrossASeriesWhenTheRootIsShared · TestBankStopRowCarriesTheWinningVariantsSignals · TestTerminologistBudgetCutIsNotReportedAsAnEmptyReply · TestParseBanknoteShortLastLineIsAParseFail. Изменён на месте один: TestTerminologistEmptyReplyIsLoud (стал двухподслучайным — проза + настоящий пустой completion).
  • Изменены сигнатуры (обновлены все вызовы, включая тесты): terminology.Merge(+foldDst), terminology.ParseReply(+conf map), Batch(seriesID→unitID), pipeline.writeAutoBank(+ctx), bankStopRows(+terminologyResult).
  • Фикстура projectOpts получила поле glossaryTokenBudget (0 → историческое 800, все прежние проекты байт-идентичны) — иначе ветка эвикции недостижима из теста.
  • archguard-тесты не трогал; backend/.env не читал; PUML не рендерил (правка backend/docs/pipeline.puml — текстовая, синтаксис-ловушки из памяти учтены: ; внутри метки нет, строк, начинающихся со спец-терминатора, нет).
  • МНОГОАГЕНТНОЕ АДВЕРСАРИАЛЬНОЕ РЕВЬЮ — ИСПОЛНЕНО по санкции владельца: 13 агентов-опусов (шесть независимых линз → опровергатель на каждую → синтез), $0, ~46 минут. Результат — §4-0-БИС: четыре major, включая ИЗМЕРЕННЫЙ регресс на замороженном эталоне и вред, ехавший на провод. Все закрыты; одиннадцать мутаций пере-проверены исполнением и убиты (родство-подстрока · инвертированный ранг семей · игнор разделителя · алиас-dst · несчитаемая битая уверенность · статистика по попытке · ordinal-порядок · плюс четыре из первого прохода). ⚠ Счётчик воркфлоу «36 выжило / 0 опровергнуто» — артефакт моего скрипта (id находок не сматчились с вердиктами опровергателей); реальное опровержение выполнено, его результаты — в тексте синтеза, и часть находок им снята.
  • АДВЕРСАРИАЛЬНОЕ ревью №1 (author≠reviewer, одиночное) — ИСПОЛНЕНО отдельным агентом по санкции владельца: дифф + окружающий код + ТЗ + замеры-основания, с мутационным тестированием. Нашёл 1 major (см. §4а), 1 дыру-майнор и 4 выжившие мутации; опроверг три клейма этого отчёта. Все находки закрыты в коде и тестах, четыре мутации пере-проверены исполнением и теперь УБИТЫ (M1 гард серия-vs-семья · M2 игнор attempted · M3 сигналы не заполняются · M4 инертный путь отдаёт копию). Батарея под -race на затронутых пакетах — зелёная.
  • Ревью исполнением своего кода (соло, до адверсариального). Что пере-проверял отдельно и чем: детерминизм MergeUnits (никакого выхода из итерации по map — id раздаются по отсортированным корням) · инертный путь (TestFamilyChannelOffIsTheSeriesMap требует ТОТ ЖЕ объект-словарь) · Spread() для кандидатов, собранных вне Merge (Forms=0 → считается как 1, обратная совместимость) · «уверенность» не ломает двухпольный ответ (TestParseReplyTakesTheConfidenceFieldBeforeRejoining, подслучай plain) · ложная тревога пустого батча при срезе бюджетом (отсечено attempted) · кап семей — подобран НЕ на глаз, а прогоном по реальному корпусу при cap ∈ {8,12,16,24,32,∞}: 16 разрезает клан 古月 (6 отказов), 24 не отказывает ничему, дальше 24 ничего не меняет.

§4. Девиации от промта и почему

0-ТРИ (фикс-лист приёмки, всё мелкое — но одно структурное).

  • БЛОКЕР: make battery была красной — см. §3; клейм отчёта исправлен.
  • MAJOR: WARN строки 130 был структурно МЁРТВ в проде. ownerHandled получал СЫРОЙ seed из стора, а тот со ВТОРОГО прогона авто-режима содержит собственные mined/auto-строки движка (seedGlossary засеивает авто-банк на старте, seeding.go:83-93) ⇒ каждый выпавший терм читался как «владелец решил», дифф всегда пуст, предупреждение не могло сработать НИКОГДА. Это risk 2 (self-exclusion), который сам файл документирует и обходит unsignedEngineSurfaces в двух других местах — и не обходил здесь. Мой тест это МАСКИРОВАЛ: кормил рукодельную map мимо стора. Стало: ownerHandled(unsignedEngineSurfaces(seed), rejects) + тест ДВУМЯ реальными прогонами (терм уходит из дельты между ними, строки движка в сторе есть) — посадка «сырой seed» теперь убивается.
  • Две дыры пинов, обе на SPACE-RUN-форме: conf-тесты и тесты 129 кормили только ТАБ-строки, где join(fields[1:]) == fields[1], поэтому и снятие уверенности на пробельном пути, и ре-джойн банкноты можно было отключить незаметно. Добраны пробельные формы; обе посадки убиты.
  • bankParseVersion ничем не потреблялся — не логировался и не фолдился, комментарий «logged with the run» лгал, артефакты v2.1 и v2.2 были неотличимы, то есть бамп подавался как механизм против дрейфа, а механизма не было. Стало: печатается на стопе (bank-mining: draft-side proposals folded, вместе со slice_version), комментарий честен.
  • Три ТИХИХ отказа канала семей — класс, который пак сам объявил нетерпимым: опечатка в script-колонке гасила канал молча (валидации не было, и флагманский тест общности объявлял devanagari, который резолвер молча дропал — тест не доказывал ничего); family_affix без family_min_members = канал молча выключен; словарь типов рассинхронен — nickname легитимен в банкнот-канале и доезжает до DetectFamilies, но loadFamilyParams его отвергал, а текст ошибки врал про состав словаря. Стало: скрипт валидируется по реестру диапазонов, пол-таблица отвергается, введён terminology.CandidateTypes (шире классификаторского Types ровно на nickname, с объяснением почему), сообщение об ошибке перечисляет реальный набор, и пара получила правило для nickname.

0-БИС (правки по МНОГОАГЕНТНОМУ ревью, шесть линз + опровергатели + синтез, всё опусами; 13 агентов, $0). Ревью нашло то, что соло-проход и первый адверсариальный пропустили:

  • (ж) MAJOR: парсер банкноты дал РЕГРЕСС на замороженном эталоне. Реплей 56 реальных блоков coldrun-a: 6 строк против HEAD, +0, при banknote_parse_fail НЕИЗМЕННОМ во всех шести — то есть тихо. Механизм воспроизведён мной лично: трактовка «последнее поле не из словаря ⇒ часть перевода» портит строку, чей третий столбец — тип ВНЕ словаря (clan, event, proverb, onomatopoeia — 1.25% эталона): 古月|Гу Юэ|clan → dst «Гу Юэ clan»; а на ОБРАТНОМ порядке src становится ненаходимым и строка выбрасывается совсем. Исправлено принципом «разделитель несёт информацию»: строка с ТАБОМ или ПАЙПОМ прочитана позиционно (семантика HEAD), эвристика словаря применяется только к строке, разбитой прогоном пробелов — там, где неоднозначность и возникает. Дефект «Фан␣␣Юань», ради которого делался хвост 129, при этом чинится ЛУЧШЕ: в позиционном чтении он схлопывается внутри колонки. bankParseVersion поднят до v2.2 — правило парса изменилось, а тег версии в первой редакции остался прежним, что и есть тот дрейф, ради которого версия разделена надвое.
  • (з) MAJOR: перевод АЛИАСА становился dst терма и ехал на провод. props[0] после моей правки мог быть предложением, пришедшим через алиас кластера — и попадал в st.Dst, то есть в строку банка, которую рендерит инъекция (memory.go:699-724), причём на $0-пути, БЕЗ модели и при ВЫКЛЮЧЕННОМ гейте терминолога. Ровно тот вред, ради предотвращения которого Via и заводился, — через правку, которая Via и вводила. Исправлено: st.Dst берётся только из предложения, сделанного НА СВОЕЙ поверхности; алиасные перечисляются в note со своим Via.
  • (и) MAJOR: третье поле, не являющееся голым числом, молча приклеивалось к переводу. 师父⇥наставник⇥95% давал канон «наставник 95%» при bad_lines=0 — форма проходит wellFormedLemma, проходит экран языка, не эхо. Класс НОВЫЙ: промпт v3 сам просит третье поле. Исправлено тем же принципом разделителя: в таб/пайп-строке третья колонка ЕСТЬ колонка уверенности, нечитаемое значение считается (ReplyStats.BadConfidence) и НИКОГДА не склеивается.
  • (к) MAJOR: MergeStats.Held считался по попытке, а не по итогу. Из трёх «придержаний» на корпусе одно было ложным (蛊师 и 一转蛊师 в итоге оказывались в ОДНОМ юните), и отчёт трижды называл ценой гарда именно несуществующий случай. Считается по ФИНАЛЬНОМУ разбиению: реально разрезаны 巅峰 и 蛊虫, придержаний 2.
  • (л) MAJOR: две мутации в ядре пака выживали — родство подстрокой вместо морфемы и инвертированный ранг семей (обе меняют СОСТАВ вызовов, то есть деньги, при зелёной батарее). Закрыты пином точных чисел И ФОРМЫ разбиения плюс кейсом «серия 等资质 и семья {丙等,丙等资,丙等资质} обязаны слиться через общую руну ».
  • (м) MINOR: WARN строки 130 срабатывал на ШТАТНЫЙ цикл подписи. Промоут в mined_delta или отклонение в mined_rejects убирает терм из дельты НАМЕРЕННО, а дифф печатал его как молчаливую потерю и советовал сделать то, что владелец только что сделал. Исправлено: обе владельческие двери исключены из диффа.
  • (н) MINOR: единственная РАЗРЕШЁННАЯ D39.102 форма уверенности не была реализована. Число печаталось и не использовалось, хотя промпт и два комментария обещали ordinal-очередь ревью. Реализовано ровно там, где ему место: капнутый stdout-обзор (surface «что читать») сортируется «сначала неуверенное», полная стоп-таблица остаётся в ключевом порядке (reference-документ).
  • (о) Лгущие комментарии сняты: mining.go больше не утверждает «с выключенным гейтом дельта байт-идентична» (это перестало быть правдой из-за (ж)/(з)); док поля banknote_truncated больше не описывает удалённую tolerance-ветку и честно назван полом (обрыв, срезавший разделитель, он не видит); комментарий про 蛊 среди 蛊-* больше не выдаёт непокрытый класс за работающий; ссылка на удалённую proposalsFromObserved поправлена.

0 (правки по первому, соло-адверсариальному ревью).

  • (а) MAJOR: гард «серия сильнее семьи» растворял семью целиком. Было: если хоть один таргет нёс чужой серийный корень — слияние отменялось ВСЁ, включая не-серийных членов; клан 古月 из пяти имён, три из которых образуют -серию, рассыпался на синглтоны — ровно та химера §B4, которую канал и строится закрыть, и приходила она через гард, который должен был её предотвращать. Блок при этом не считался ничем. Стало: придерживается только конфликтующий таргет, остаток семьи сливается, факт считается MergeStats.Held и попадает в WARN. Пин TestSeriesHoldsOnlyItsOwnMembers (мутация «убрать гард» теперь убивается).
  • (б) Тест родства был подстрочным и переусердствовал. Замер на реальном корпусе: 5 семей придержаны ЛОЖНО — серия 等资质 против семьи {丙等, 丙等资, 丙等资质}: ни одна строка не содержит другую, хотя они очевидно делят морфему , чья согласованность и есть предмет. Стало: «делят руну» = делят морфему (плотное письмо); придержаний на корпусе 11 → 3, и все три по делу.
  • (в) Дубли групп задваивали счётчик. Аффиксный и containment-каналы законно описывают один и тот же набор с двух сторон; дедуп ПОСЛЕ ранжирования (выживает самое специфичное описание) — придержаний 6 → 3, число в WARN стало означать то, что написано.
  • (г) Conf=0 был неотличим от «уверенности нет». Обе печати прятали строку с уверенностью 0 — ровно ту, ради которой ordinal-ревью и существует. Стало: отсутствие = 1, ноль печатается.
  • (д) Мёртвый параметр truncatedGeneration снесён вместе с недостижимой веткой и стейл-комментом (вторая половина опции промта; первую — проведение флага через finish — сделал сразу).
  • (е) Три дыры в тестах (мутации выживали): гард серия-vs-семья · attempted · заполнение Signals. Закрыты пинами, мутации пере-проверены исполнением.
  1. Уверенность снимается по ПОЛЮ, а не регуляркой \d{1,3} по хвосту строки. Промт просил «числовой хвост \d{1,3} снять КАК conf ДО ре-джойна». Буквальная форма съедает легитимный перевод, кончающийся числом («Пик 108»), у пары, чей промпт просит два поля. Реализовано: если полей ≥3 И последнее поле целиком 13 цифры в 0…100 — это уверенность. «Пик 108» — одно поле (один пробел, сплиттер режет по ≥2 пробелам), так что не задевается вовсе; проверено тестом в обе стороны. Число вне 0…100 остаётся частью перевода.
  2. DetectFamilies возвращает []Family, а не map[string]int. Слияние обязано знать ЯКОРЬ семьи, чтобы исполнить правило «серия сильнее семьи ТОЛЬКО при несовпадающих корнях»; из голых id якорь не восстановить. Итоговый map[string]int отдаёт MergeUnits — то есть контракт «на вход Batch идёт тот же словарь» соблюдён.
  3. «Серия сильнее семьи» реализована как ПРИДЕРЖАНИЕ серийного таргета, а не как исключение серийных членов из семей и не как отмена слияния. Черновик §G предлагал «серийные члены из семей исключаются»; промт это уже амендировал 转-случаем. Исключение членов не выполняет критерий (а): при нём 十一转 навсегда остаётся снаружи. Реализовано: юниты сливаются, если якорь семьи и корень серии делят морфему (sharesMorpheme); если нет — серия сохраняет СВОИХ членов, а семья формируется без них, и факт считается. Цена придержания, названная честно (пере-мерена по ИТОГУ, а не по попытке): на корпусе это 2 случая вида «реалия-семья, модифицированная рангом» — 巅峰 и 蛊虫 оказываются в другом вызове, чем 一转巅峰/一转蛊虫. Альтернатива — разрешить слияние — даёт ранговый юнит-свалку; выбран гард + счётчик + WARN, чтобы решение было видно на холодном прогоне, а не выведено из банка, который сам себе противоречит.
  4. Имена эвиктнутых — в ЛОГ, а не «рядом с NEvicted» в строке retrieval_state. Промт требовал форму БЕЗ новой миграции схемы; свободной TEXT-колонки в таблице нет (все шесть *_detail заняты), а класть список эвиктнутых в чужую колонку (trust_gate_detail) — нечестно. Лог — вторая наблюдаемая плоскость движка, и именно её читают при бюджетных событиях. Вопрос владельцу/оркестратору: если список нужен durable — это колонка evicted_detail и миграция; заводить её без слова не стал.
  5. Перф AttachKWIC через готовый Aho-Corasick — НЕ делал. Автомат живёт в membank неэкспортированным (memory.go:882-927). «Чисто» это не ложится: либо terminology начинает зависеть от membank (инверсия слоя — пакет объявлен PURE, «no I/O, no network», и банк-стор в него импортировать нельзя), либо автомат выносится в новый общий пакет, что уже рефакторинг за пределами скоупа пака. Промт разрешал опцию «только если чисто ложится» — не ложится.
  6. Ранжирование семей инвертировано против буквы ТЗ. §2 G1 требует «специфичный длинный корень бьёт короткий, ЗАТЕМ большая семья»; реализовано наоборот — сначала размер, потом длина якоря. Причина: при спековом порядке containment-якорь 一转 (2 руны) обходит корень (1 руна) и рвёт ранговую линейку на пары, то есть буквальный порядок ЛОМАЕТ критерий §2(а) того же ТЗ. На корпусе порядок кода строго лучше, и он запинен формой разбиения. Девиация не была объявлена в первой редакции §4 — поймано приёмкой, ратифицировано.
  7. Снятие уверенности не гейтится данными пары — канал работает для любой пары, чей промпт-файл попросит третье поле. Риск-остаток назван: у пары с ДВУХпольным промптом легитимный перевод, чьё последнее слово — голое число ≤100, отделённое ДВОЙНЫМ пробелом («Пик␣␣99»), потеряет это число в уверенность. Уже, чем буквальная форма промта, но не ноль; если понадобится — это дата-декларация пары, отдельным касанием.
  8. ~/books/gu-zhenren/coldrun-a/ не читал (см. хвост 129 выше): цена фолда 129 названа как «неизвестна», а не выдумана.

§5. Чего НЕ сделал и почему

  • G4 (кластерный роутинг) — по промту в пак не входит (правило дизайна замера строки 5, кода не требует).
  • Накопительный мердж авто-банка и освобождение слотов emitRankCap — СТОП-решение владельца; сделан ровно $0-минимум (дифф + имена).
  • Строка 38 (ординальная адресация чекпойнтов) — только наблюдение, см. §2.
  • Строка 140 (декларация банкнота-канала в shipping-c1) — вне пака; чинил только ПАРСЕР.
  • term-класс эмиссии, равноправный совет, кросс-модельные веса, self-consistency, селекция из вариантов, отказ-сигнал, эмбеддинг-фетч — закрыто числами D39.102, не строил.
  • ОДНОРУНОВАЯ голова семьи (, , ) — класс НЕ покрыт. Якорь обязан быть ≥2 рун, иначе одна общая морфема якорит полбанка; на дистилляции это значит, что (кандидат банка) едет в другом вызове, чем 12 несущих его поверхностей. Включается ОДНОЙ строкой данных (family_containment_runes han 1), без правки Go. Не включил, потому что цена (размер юнитов) не замерена — предмет холодного прогона, не догадки. Строка бэклога — §7 п.4.
  • Семейные правила резолвятся по ЯЗЫКУ и применяются к поверхностям любой письменности. FamilyMorphology("ja")/("ko") отдают han-правила и режут аффиксы по кане и хангылю. Замерен эффект на синтетике: 24 катаканных имени с общим «アリ» → один юнит → батч 8036 рун при бюджете 6000. Пары ja/ko в репо нет, та же скрипт-слепота уже в HEAD у серий — это норма-долг по ревью-вопросу общности, а не живой дефект. Строка бэклога — §7 п.5.
  • banknote_truncated — ПОЛ, а не полный счёт. Обрыв, срезавший сам разделитель, флага не даёт (ранний возврат по пустому блоку выше проверки finish); по эталону это большинство length-обрывов. Буквальный фикс («ставить флаг и при пустом блоке») сделал бы ХУЖЕ — зажёг бы колонку на каждом обрыве переводчика в прогонах вообще без банкноты; нужен гейт по Gates.Banknote.Enabled, и это отдельное касание. Строка бэклога — §7 п.6.

§6. План верификации эффекта на следующем ХОЛОДНОМ прогоне (D39.103 п.1)

Конкретно, что смотреть и где. Всё $0 — читается из артефактов прогона, отдельных вызовов не требует.

  1. Семьи вообще заработали. В логе строка terminology finished — поля families=, families_refused=, families_held=. Ожидание: families заметно >0 (⚠ поле печатает число ДЕТЕКТИРОВАННЫХ ГРУПП, а не юнитов: на дистилляции 37 групп → 14 юнитов), families_refused=0, families_held малое (на дистилляции — 2). Если families=0 — канал инертен, и первое, что проверять, это bank_data= в строке terminology render: estimate before any call (плоскость данных доехала) и source_lang книги.
  2. Химера семьи на границе батча ЗАКРЫТА — прямая проверка. В стоп-таблице (<project>.db.bank-stop.txt) взять клан 古月 и линейку : все члены каждого должны иметь СОГЛАСОВАННЫЙ dst (одна конвенция клана, одна голова ранга). До пака ds1-b0 дал все шесть членов слитно, ds1-b2 — все семь раздельно (research/24 §B4). Если расхождение осталось — смотреть families_refused/families_held и WARN «renders OVER the size budget»: значит юнит разъехался по батчам или по капу.
  3. Юнит не разъехался по батч-бюджету. WARN terminology: a batch renders OVER the size budget — считать, сколько раз. Ненулевое количество ожидаемо и легитимно (юнит держится целым по дизайну), но если их много, следующий шаг — поднять gates.terminology.batch_runes (конфиг, не код), а не резать юниты.
  4. G2 нашёл то, ради чего строился. Поле self_conflicts= в terminology finished и WARN «consolidations of THIS run contradict each other» с именами. Основание-ожидание: 18/149 на живом банке (§A4). Ноль на большой книге — подозрительно, надо перепроверять lexemeSubset на реальных формах, а не радоваться.
  5. Уверенность доехала и не врёт. В стоп-таблице колонка confidence=. Проверять ДВЕ вещи: (i) она вообще непустая — значит модель выдаёт третье поле и парсер его снял; (ii) bad_lines в terminology finished НЕ вырос против прошлого прогона — если вырос, модель ломает формат из-за третьего поля, и это откат промпт-правки, а не повод её терпеть. Полезность (сортирует ли она ревью честно) меряется глазами владельца на первом же листе: строки с низкой уверенностью должны быть теми, которые он и правда правит.
  6. Invented — класс, который надо прочитать первым. В стоп-таблице пометка INVENTED. Ожидание: доля небольшая; каждая такая строка — консолидация, которой не предлагал ни один черновик. Это легитимно (роль видит всю книгу), но это же и первое место, где ищется выдумка.
  7. Conventions против Spread. Строки, где spread заметно больше conventions, — чистая орфографическая дробь, которую фолд теперь склеил; именно на них §C2-3 раньше делил доказательства. Если таких строк ноль — фолд не сработал (проверять NormalizeTargetForm на паре).
  8. Строка 130 видна. WARN bank-mining: terms that were in the auto-bank are NOT in the one this run just wrote с именами. На первом прогоне его не будет (нечего диффить); он появится на ВТОРОМ и дальше. Если появился — это и есть материал для решения владельца по накопительному мерджу.
  9. Эвикция названа. WARN memory: the injection token budget DROPPED bank rows с именами и rows=. Если он есть — банк не доезжает до модели, и это отдельный разговор про glossary_token_budget, а не про терминолога.
  10. Деньги. terminology render: estimate before any call (до вызовов) против cost_usd/classify_cost_usd в terminology finished; ожидание — перекупка бэнк-батчей ОДИН раз, порядок центов, никакого касания волновых чекпойнтов (их $0-резюм должен пройти, что видно по tm_hit/resume в отчёте прогона).

§7. Вопросы оркестратору и предложенные строки бэклога

РЕШЕНО оркестратором/владельцем при приёмке (войдёт в D-ноту лендинга):

  • §7.1 — сдвиг edit-волны bank-only ПРИНЯТ. Слово владельца («не против реснапшота») совпало с рекомендацией сессии: новый dst лучше старого, сдвиг громкий, репин $0 там, где терма нет.
  • §7.2 — миграцию под имена эвиктнутых НЕ заводим: лог достаточен; durable-форма уходит строкой бэклога с гейтом «доказанная потребность», вместе со строкой 49а.
  • §7.3 — закрыто замером (0 прогонов ≥2 пробелов на 585 строк эталона).
  • Девиация bankdata-плоскости РАТИФИЦИРОВАНА — отдельный embed вне волнового фолда вместо script-series.txt признан правильным швом: иначе перекупалась бы вся драфт-волна.
  • Инверсия ранжирования семей ПРИНЯТА по корпусному пину; объявлена девиацией §4 п.6 (в первой редакции отсутствовала).

Историческая формулировка вопросов (для читателя D-ноты):

  1. Edit-волна двигается bank-only — принимается ли цена? Пак меняет поле dst дельты (ранг §C2-3 и фолд орфографии вместо сырого драфт-порядка), значит memory_version, значит снапшот edit-волны. Сдвиг громкий (--resnapshot) и репинится за $0 для каждой единицы, где терма нет; платятся только единицы, где он встречается — для фамилии протагониста это почти вся книга. Альтернатива, если цена не принимается: держать dst дельты на предложениях СВОЕЙ поверхности в исходном драфт-порядке (тогда карта подписи снова разойдётся со стоп-таблицей — это и был дефект, который §G3 чинил). Моя рекомендация: принять, потому что новый dst лучше старого, а перекупка разовая и видимая.
  2. Имена эвиктнутых durable? Сейчас — лог (миграции не заводил, см. §4 п.4). Столбец retrieval_state.evicted_detail — это миграция, нужно слово.
  3. Двойной пробел в coldrun-a — искать ли (одна grep-команда по замороженному эталону) и в чьей это сессии; я в артефакты не ходил. ⚠ Замер многоагентного ревью уже дал ответ по эталону: 0 прогонов ≥2 пробелов на 585 строк, то есть класс, ради которого делался хвост 129, на этой книге не встречается ни разу — фикс остаётся верным, но его цена по эталону нулевая, а не отрицательная только после правки §4-0(ж).

Предлагаю СТРОКАМИ БЭКЛОГА (таблицу ведёт оркестратор, я в неё не пишу):

  1. Одноруновая голова семьи// в отдельном вызове от своей семьи; включается строкой данных family_containment_runes han 1, гейт — замер размеров юнитов на холодном прогоне. Вес: скоро (то же касание, что и п. 7).
  2. Скрипт-слепой резолв семей/серий — han-правила применяются к кане и хангылю; норма-долг общности, живого вреда нет (пары в репо нет), но при первой ja-книге это батч 8036 рун вместо двух в бюджете. Вес: когда-нибудь (к преп ja→ru, строка 81).
  3. banknote_truncated — пол, а не счёт — обрыв, срезавший разделитель, невидим; фикс требует гейта по Gates.Banknote.Enabled, иначе шум на каждом обрыве в прогонах без канала. Вес: когда-нибудь.
  4. Пере-подтвердить family_max_members = 24 и families_held на первом холодном прогоне: 24 откалиброван на дистилляции в 150 поверхностей, у книги в 1000 глав список кандидатов другой (кап эмиссии 200 + реверс-секция). Если families_held велик — гард ослабить до «родство по любому члену юнита» вместо «по корню серии». Вес: скоро (гейт — тот же прогон, что и §6).
  5. Уверенность не гейтится данными пары — канал снимает третью колонку у любой пары, чей промпт её попросит; у пары с ДВУХпольным промптом остаётся узкий риск («Пик␣␣99»). Если понадобится — дата-декларация пары. Вес: когда-нибудь.