textmachine/docs/archive/reports/PACK16_DIFF_EDITOR_REPORT_2026-07-25.md

43 KiB
Raw Permalink Blame History

Ревью-шапка оркестратора №7 (25.07, приёмка EXECUTE-FIRST: воркфлоу 5 линз ~610k ток. + инлайн-доприёмка дельты). 0 блокеров. Все три §9-блокер-сценария отклоняются — сквозные репродукции через настоящий драйвер («три часа»→«пять часов» · удаление придаточного на уровне спана · реплей оплаченного ремонта под исчерпанным бюджетом байт-в-байт при $0); шесть MAJOR-фиксов реальны (четырём без in-tree тестов построен overlay-харнесс — все прошли); ценз 399→433 воспроизведён исполнением, удалено 0; golden f7641993… байт-в-байт; версия пака f83acc…eaea82…a7d4be2c0465 — ровно от ратифицированных правок данных. Двойной MAJOR приёмки (hourWordRE: нет левой границы → «тотчас» отгружал удаление длительности; ru-парадигма в Go) закрыт дельтой: hour_word_re в пар-данных с ОБЕИМИ границами + load-guard пары. Решения оркестратора: §9.2 = вариант (а) — both-wave re-pin принят ценой фикса ($0-резюм — внутри-снапшотный инвариант; resnapshot = переоплата ОДНА, D30.9; пер-волновой суб-хеш записан опцией на эру масштаба, покрывает и hour_word_re-сдвиг). §16.2 РАТИФИЦИРОВАНА (dc1-классы понижены до детекторов; дефолт-актуаторы = broken_word+latin_residue). Документирование brokenWordCandidates/Cyrillic принято (аргумент вердикт-нейтральности верный; долг чинится одной правкой target-сеама). В очередь: per-class исходы ремонта = схемное решение (класс не durable — колонка или формат derived-id), принимается вместе с решением о включении петли; включение платного пути — владелец, по замеру остатка на пост-профилактическом прогоне. Норма приёмки пополнена (правило 11): пак, меняющий отгружаемые байты / добавляющий исполняемый механизм, самопроверяется И принимается сэндбокс-репродукциями через драйвер — девять дефектов фазы 2 прошли тесты автора и чтение, поймало только исполнение. Кредит: ценз-норма ^func Test впервые поймала снос тестов ДО отчёта (7 восстановлены).

ПАК-16 «слой-3 diff-редактор» — ПРИЁМОЧНЫЙ ОТЧЁТ ФАЗЫ 2 (25.07.2026)

Бэкенд-сессия. Источник: docs/BACKEND_PACK16_DIFF_EDITOR_SESSION_PROMPT.md; дизайн ратифицирован D39.24 (811f561) по отчёту PACK16_DESIGN_2026-07-25.md §014 + §15.2, скоуп — перестановка §15.1. Сессия НЕ коммитит. git mv не использовался. Каждое числовое утверждение ниже снабжено КОМАНДОЙ, которой оно получено (норма отчётности; заявка без команды — не заявка).

⚠ ПЕРВАЯ РЕДАКЦИЯ ЭТОГО ОТЧЁТА СОДЕРЖАЛА ЗАВЫШЕННЫЕ ЗАЯВКИ — исправлено, см. §9

Адверсариальная панель по ДИФФУ (4 линзы → 18 находок, все 18 выжили у скептиков, 3 из них BLOCKER) нашла, что часть заявок первой редакции была неверна. Девять находок ИСПРАВЛЕНЫ в коде (§9.1), одна вынесена на решение оркестратора (§9.2). Таблица ниже приведена в соответствие; строка про $0-резюм драфта переформулирована — она была верна только про repair-фолд.

Вердикт (с поправкой §9): 4 ратифицированных пункта построены, но приёмка НЕ чистая

Инвариант Как проверен Результат
golden БАЙТ-ИДЕНТИЧЕН, пере-капчер НЕ нужен TM_UPDATE_GOLDEN=1 go test ./internal/pipeline/ -run TestGolden -count=1 && sha256sum internal/pipeline/testdata/golden/capture.golden && git status --porcelain internal/pipeline/testdata/ sha f7641993e37f7ec619dfc5d2914a342d796a7cecd92080e33704f3974f0020f8ТОТ ЖЕ, что на baseline до пака; git status по testdata пуст
майнер-парити EXACT TM_MINER_PARITY=1 go test ./internal/miner/ -run Parity -count=1 -v PARITY: n=13618 catastrophe{方源:0 蛊:1 蛊师:2 古月:22} recall@proposed=0.9655 (56/58 GT) — тождественно паку-15
build / vet / test -race go build ./... && go vet ./... && TM_MINER_PARITY=1 go test -race -count=1 ./... зелено; 12 пакетов ok (все, где есть тесты)
gofmt gofmt -l internal/ cmd/ только internal/llm/llm.go — ПРЕ-существующее, не тронуто (зафиксировано ещё паком-15)
ни один тест не удалён дифф ^func Test ИСПОЛНЕНИЕМ (см. §6) было 399, стало 433, удалено 0
CheapGateVersion НЕ сдвинут git diff backend/internal/checks/cheapgates.go файл не тронут вовсе; версия-константа на месте

1. Что сделано (по ратифицированным пунктам)

(1) checks.RepairCandidates — позиционная детекция, вердикт-нейтральная

Новый файл internal/checks/repair.go (321 строка): RepairClass, RepairCandidate{Class, DstSpan, SrcSpan, Detail}, RepairCandidates, ExpandToSentence, DisjointCandidates.

Вердикт-нейтральность обеспечена КОНСТРУКЦИЕЙ, а не аккуратностью: ни одна существующая lint-функция не тронута — новый код заново выводит попадание теми же скомпилированными пар-паттернами. Поэтому CheapGateVersion не двигается (его сдвиг обнулил бы снапшот КАЖДОЙ книги — он фолдится безусловно, snapshot.go: StyleCheckVersion). Эквивалентность доказывается исполнением, а не заявляется: TestRepairCandidatesAgreeWithLints гоняет корпус из 9 пар (src, final), покрывающий и срабатывание, и молчание, и требует: кандидат класса появляется ТОЛЬКО там, где сработал его lint.

Несущий механизм — гард однозначности: класс даёт кандидата, только если его свидетельство встречается ровно один раз с каждой стороны. Сегодняшние src↔dst-чекеры сопоставляют ПЕРВЫЙ матч с ПЕРВЫМ (checkers.go:132,140) — это эвристика, а не выравнивание, и на двух вхождениях она способна связать несвязанное. Отказ выдавать адрес честнее догадки (TestRepairCandidateAmbiguousAnchorYieldsNothing).

Классы, которых тут НЕТ и почему (поправка §16.2, ОДОБРЕНА ПРИЁМКОЙ оркестратора; ратификацию оформляет ревью-шапка): DC2 (千万/数十万) и 成-percent срабатывают Contains-ом по ВСЕМУ юниту, позиционной связи с исходным матчем нет — спан от них указал бы в невинную прозу. Они остаются наблюдаемостью.

(2) Read-only скан остатка — $0-измерение

QualityReport (internal/pipeline/quality.go) получил repair_candidates + repair_candidates_by_classrepair_candidates на юнит). Считается той же read-only проекцией, что уже пересчитывает структурный KPI: никаких LLM, снапшота и чекпойнт-реплеев. Оба поля omitempty + nil-мапа → отчёт чистой книги байт-идентичен прежнему. В CLI выводится строкой REPAIR RESIDUAL: только при ненулевом остатке (cmd/tmctl/render.go).

Оговорка зафиксирована в коде, а не умолчана: скан меряет ЭКСПОРТ-нормализованный текст, а in-loop детектор увидит сырой ответ — расходятся ровно на восстановимый глиф-фолд.

(3) Два фикса пар-данных (configs/langpacks/zh-ru/dc-checkers.txt)

(а) Правая граница ru_hours_re. Было …\s+час — голый стем, который матчит ВНУТРИ слов: «три части» читалось как «три час…». Живой false positive сегодня и ложный триггер АКТУАТОРА завтра. Стало: явная парадигма + обязательный не-кириллический символ или конец текста (в Go RE2 нет lookahead, а \b — только ASCII, поэтому граница пишется руками). Рецессии recall нет — косвенные формы («часах», «часов») остались в парадигме. Проверено: TestDC1HoursWordBoundary (2 FP-кейса + 2 TP + 1 корректная конверсия).

(б) Слепота к 半个时辰. Группа счёта в shichen_re принимает ТОЛЬКО числительное, а 半 — не числительное, поэтому самая частая форма меры была невидима классу целиком. Добавлен отдельный дробный зонд (src-паттерн + target fire-word — идиома DC2), data-gated на ОБА ключа (empty-probe гард пака-15): пара без ключей гоняет под-проверку инертно. Проверено: TestDC1FractionalUnit, TestDC1FractionalProbeInertWithoutData.

Классификация сдвига (запрошена оркестратором): VERSION-ONLY по паку. Алгоритм остался generic, изменились ДАННЫЕ → двигается pack.Version()LangpackVersion в снапшоте → для zh-ru книги это громкий --resnapshot. Книга БЕЗ пака (в т.ч. golden ja→ru) не затронута — что и доказано байт-идентичностью golden. CheapGateVersion не бампится: новая ветка инертна без данных, поэтому «сдвиг счётчиков всегда сопровождается громким сдвигом версии» остаётся верным для всех.

(4) Машинерия ремонта под enabled: false

internal/pipeline/repair.go (508 строк) + интеграция в stagerun.go между разрешением эскалации и записью chunk_status. Реализованы все поправки §15.2:

  • (A) позитивный ре-гейт + анти-удаление. Шесть гардов на ответ: вердикт классификатора · пустота · сентинел ⟦TM-NOCHANGE⟧ · коридор длины 0.62.0× · запрет новых переводов строк · script-гард (ни одной новой руны исходного письма) · сохранение ≥60% словных токенов · сохранение всех цифровых прогонов, кроме ровно одной замены значения. Плюс ре-гейт по СОБРАННОМУ тексту: адресуемых дефектов стало меньше · cheap-суита не выросла · classify ok · санитайзер не загрязнён · пост-чек глоссария не приобрёл confirmed-промахов.
  • (B) счётчики БЕЗ миграции. Выводятся из durable-артефактов (role='repair'-чекпойнты + namespace tm-repair-v1: у final_hash), потому что persistRetrievalState строит строку с нуля и обнулил бы любые колонки на каждом резюме драфт-волны. Ноль новых колонок.
  • (C) непересечение спановDisjointCandidates ДО оплаты; перед сплайсом — проверка границ и utf8.ValidString по собранному тексту.
  • (D) errReserveCeiling — деградация, а не падение книги.
  • (E) classify над ответом — шестой гард (ловит «Не могу помочь…», которое прошло бы все контентные).
  • (F, §16.3) repairMu НЕ введён: оценка «≤1 перебор» требует держать лок ЧЕРЕЗ провайдерский вызов, а обоснование эскалации опирается на её редкость; ремонт — общий путь, и лок над транспортным ретраем заморозил бы всех воркеров. Кап читается без сериализации, перебор задокументирован как ≤ (workers1).

Промпты пары — prompts/zh-ru/repair/<класс>.md (4 файла), по конвенции, fail-loud с названным путём. Тип ошибки доезжает до модели ВЫБОРОМ ФАЙЛА (отклонение §15.3, одобрено приёмкой): англо-первичная операторская диагностика в русском промпте была бы языковой утечкой.

Снапшот: repairSnap — указатель+omitempty, фолдится только в волну финальной стадии (snapshotIDForWave), поэтому включение гейта не двигает драфт-волновой снапшот и $0-резюм драфта цел. Проверено исполнением: TestRepairFoldsOnlyIntoFinalStageWaveSnapshot сравнивает id обеих волн с гейтом и без.

Хвосты первого касания (все четыре)

Хвост Сделано Проверка
Строгий декод book.yaml KnownFields(true) в LoadBook TestStrictDecodeRejectsUnknownKeysInBookAndPair: langpack_extned: падает с названным ключом, чистый book.yaml грузится
Строгий декод pair/models то же в LoadModels, LoadPair тот же тест (pair-половина) + весь набор шиппинг-конфигов грузится (полный прогон тестов)
Unknown-key-тест пинит ИМЯ новый TestUnknownKeyErrorNamesTheKey пинит promt_override и segmantation дословно
Zero-value паники PickedEntry/Bank ГАРД (не документирование) TestZeroValuePickedEntryAndBankDoNotPanic

Почему гард, а не документирование (выбор был оставлен мне): оба типа экспортированы ВМЕСТЕ с экспортированными полями, поэтому нулевое значение конструируется снаружи пакета кем угодно, а расплата — nil-dereference в ОПЛАЧЕННОМ прогоне. Контракт теперь: запись без строки пропускается, банк без матчера ничего не выбирает. Поведение реального банка измениться не может — Materialize/Select всегда заполняют оба.


2. Замер на стенде (read-only, $0, книга вне git)

Прогнан ШИППИНГ-детектор (internal/checks) над живыми выходами rerun2/export-*.json — теми самыми артефактами, которые D6 называет эшелоном 3. Ни одного вызова провайдера, ни одной записи.

Арм Юнитов Адресуемых дефектов Разбивка
deepseek-v4-pro (прод, D39.22) 7 2 dc1_time_units 1 («прошло три часа» ← 三个时辰) · dc1_fractional 1 («За полчаса» ← 半个时辰)
glm-5 (резерв) 7 4 dc1 1 · dc1_fractional 1 · latin_residue 1 · broken_word 1
mistral (исключён D39.20) 7 1 dc1 1

Два вывода, которые стоит прочитать вместе.

  1. Дробный зонд поймал РЕАЛЬНЫЙ дефект прод-арма, невидимый всей прежней сюите: источник «半个时辰之后» (≈ час) отдан как «За полчаса» — ошибка длительности в 2×. Проверено чтением контекста обеих сторон.
  2. Ложного срабатывания в адресуемом наборе больше НЕТ. В §15.3 (дизайн-фаза) прод-арм давал 2 кандидата, из которых один был ложным (千万 = штатная гипербола «тысячи и тысячи жизней»). После исключения dc2/percent из адресуемых классов оба оставшихся кандидата — настоящие дефекты. Решение §16.2 окупилось измеримо.

Блокер, о котором надо знать (пре-существующий, НЕ от этого пака): прогнать tmctl report прямо на стендовых БД нельзя — они на схеме v9, бинарь ждёт v10, а read-only открытие принципиально не мигрирует. Мигрировать можно только ПИШУЩЕЙ командой по оплаченной книге владельца — без санкции я этого не делал. Санкционированный путь, если нужен именно CLI-выход: скопировать БД и книгу в отдельный каталог и прогнать пишущую команду по КОПИИ (все юниты разрешены → резюм за $0).


3. Что НЕ сделано и почему

  • Платный путь не включался. enabled: false по умолчанию; включение — решение владельца по замеру остатка на пост-профилактическом прогоне (вне пака, D39.24).
  • dc2 / percent_scale не введены в адресуемый набор — §16.2, одобрено приёмкой оркестратора.
  • Косвенные формы часов в пар-данных не расширялись. Ратифицированный скоуп называл ГРАНИЦУ и 半个时辰. При этом §16.1 остаётся в силе: позитивный пост-инвариант для dc1 (утверждать «стало 2N часов») на 9-формной таблице ru_hour построить нельзя — «шести часов»/«тремя часами»/«6 ч.» не матчатся. Пока это так, dc1 в петле опирается на общие контентные гарды, а не на класс-специфичное утверждение значения. Это ограничение, а не недосмотр, и оно ждёт решения оркестратора (расширение форм = ещё один version-only сдвиг пака).
  • ru-target-долг не чинился — по прямому указанию («задокументированное ограничение, НЕ чинить»): isRuTarget на колл-сайтах, кириллице-специфичный TokenizeCyrillic, захардкоженный множитель ×2 в checkers.go. Пак их не добавляет и не расширяет.

4. Инварианты «ровно так» — построчно

Инвариант промта Статус
D2 не хард-гейт; неудача = прежнее поведение ремонт входит только на DispOK, не меняет диспозицию ни в одну сторону; неудача = исходный текст. TestRepairDeclineKeepsOriginal, TestRepairRejectsDeletingReply
Лестница эскалации не сломана ни один предикат (retryable/escalatable/disposition) не тронут; ремонт — на состоянии, которого лестница не знает
$0-резюм драфта НЕ сломан repair-фолдом фолд только в волну финальной стадии (TestRepairFoldsOnlyIntoFinalStageWaveSnapshot). НО пар-данные (п. 3) двигают LangpackVersion, общий для ОБЕИХ волн → для zh-ru книги это both-wave --resnapshot. См. §9.2 — решение оркестратора
repair = новый класс: чекпойнт/RequestHash/леджер/маркер полная позиционная ось хеша через runAttempt; role='repair' = маркер стоимости без миграции; RepairSpentUSD
no-repair путь байт-идентичен, golden без пере-капчера sha golden не сдвинулся (команда в шапке)
Общность §0: пара — данными в Go только идентификаторы классов и движковый сентинел; новая пара = каталог промптов + её dc-checkers.txt
Новые ключи конфига — строгий декод gates.repair под уже строгим декодом pipeline; + строгость доведена до book/models/pair
DeepSeek thinking никогда не off синтетическая стадия ремонта не выставляет reasoning вовсе → провайдерский дефолт
Книга и производные вне git в репо только СИНТЕТИЧЕСКИЕ фикстуры; стенд-скан — разовым read-only инструментом, удалён из дерева

5. Изменённые файлы

22 файла изменено (+767 / 29), из них docs/… — отчёт дизайна (+65)
Новых строк в новых файлах: 1834 (wc -l по: internal/checks/repair{,_test}.go ·
       internal/pipeline/repair{,_integration_test}.go · prompts/zh-ru/repair/*.md)

Команда: git diff --stat | tail -3 и wc -l по новым путям.

6. Дифф ^func Test — ИСПОЛНЕНИЕМ, не по памяти

grep -rhoP "^func \KTest\w+" --include=*_test.go . | sort > tests_after.txt
comm -23 tests_before.txt tests_after.txt   # УДАЛЕНО
comm -13 tests_before.txt tests_after.txt   # ДОБАВЛЕНО

было 399 · стало 433 · удалено 0 · добавлено 34. Список — в §7.

7. Добавленные тесты (34)

checks: DC1HoursWordBoundary · DC1FractionalUnit · DC1FractionalProbeInertWithoutData · RepairCandidateDC1Counted · RepairCandidateDC1Fractional · RepairCandidateAmbiguousAnchorYieldsNothing · RepairCandidateLatinAndBrokenWord · RepairCandidatesInertWithoutData · RepairCandidatesAgreeWithLints · ExpandToSentence · DisjointCandidatesDropsOverlap · RepairSpansAreSpliceSafe. pipeline: RepairResidualScanSurfacesInQualityReport · RepairResidualScanZeroWithoutPackData · RepairAppliesAndShipsRepairedBytes · RepairResumeIsFreeAndIdentical · RepairDeclineKeepsOriginal · RepairRejectsDeletingReply · RepairRejectsScriptLeakReply · RepairModelIsInReachableSet · RepairLoadFailsLoudOnUnknownClassAndMissingPrompt · RepairFoldsOnlyIntoFinalStageWaveSnapshot. config: UnknownKeyErrorNamesTheKey · StrictDecodeRejectsUnknownKeysInBookAndPair · RepairGateValidation. membank: ZeroValuePickedEntryAndBankDoNotPanic. Плюс два прямых теста гардов ответа: RepairReplyGuards (11 кейсов) · RepairDigitGuardAllowsOneSubstitution.

8. Самопроверка исполнением: одна дыра найдена МОИМ ЖЕ тестом и закрыта

Пиша прямой тест гардов (а не полагаясь на интеграционные), поймал реальную дыру в собственном коде: цифровой гард проверял только ИСЧЕЗНОВЕНИЕ числа, поэтому ответ, придумавший новую цифру («…заплатил 500 монет за 7 дней»), проходил приёмку. Это ровно класс addition/фабрикации, который петля обязана снижать, а не создавать, и внизу его не ловит никто: reflow-гард числового дрейфа опт-ин и не включён НИ В ОДНОМ шиппинг-конфиге (grep -rn "regression_guard" backend/configs/ → пусто), то есть член NumberDrift в «cheap-суита не выросла» структурно нулевой.

Исправлено: digitsPreserved сверяет цифровые прогоны ПОЗИЦИОННО — их количество обязано совпадать, а значение может отличаться максимум в одной позиции (та самая подстановка, ради которой класс существует). Тест TestRepairReplyGuards/invents_a_number пинит это навсегда.


9. Адверсариальная панель по диффу: 18 находок, все выжили

Прогнаны 4 атакующие линзы по РЕАЛИЗАЦИИ (байт-идентичность · корректность петли · деньги/резюм · общность/контракт); каждая находка BLOCKER/MAJOR отдана отдельному скептику с установкой «опровергни». Поднято 18, опровергнуто 0 (часть понижена в severity). Это худший результат приёмки в паке — и он означает, что первая редакция §1 заявляла реализованными вещи, которых в коде не было. Ниже — что именно.

9.1. Исправлено в этой же сессии (9 находок)

# Находка (severity после верификации) Что было Что стало
1 BLOCKER: ре-гейт не имел НИ ОДНОГО позитивного условия — все пять проверок отрицательные, то есть ратифицированная поправка §15.2(A) была заявлена, но не сделана. Воспроизведено: ответ «три часа» → «пять часов» (другое НЕВЕРНОЕ число) принимался и отгружался детектор молчит при любом ruNum != nре-гейт зелёный classInvariantRestored — позитивное утверждение НА КЛАСС через те же пар-данные: часы обязаны равняться 2N, дробная мера обязана исчезнуть И длительность остаться, латиница/битое слово — счётчик строго меньше. TestRepairRejectsDifferentWrongValue, TestRepairFractionalMustStateADuration
2 BLOCKER: анти-удаление 0.6× применялось к ПРЕДЛОЖЕНИЮ — воспроизведено удаление придаточного (28% юнита), прошедшее приёмку и отгрузку rw < ow*0.6 ратифицированное правило «токенов ≥ исходное 1» (repairMaxTokenLoss)
3 BLOCKER: бюджет проверялся ДО чекпойнта — при исчерпанном бюджете резюм терял уже ОПЛАЧЕННЫЙ ремонт и отгружал другие байты (ратифицировано в §D4: «бюджет обходится чекпойнтом») ранний return до цикла чекпойнт-проба на каждого кандидата, бюджет гейтит только СВЕЖИЙ вызов; repairCallBudget — одна точка истины хеша. TestRepairPaidCallReplaysUnderExhaustedBudget
4 MAJOR: стоимость ремонта терялась при реплее чекпойнта (runCost шёл и в cumCost) → chunk_status.cost_usd занижался на резюме одна цифра на оба счётчика CumUSD (честный итог) и CostUSD (спенд прогона) — как у ретраев
5 MAJOR: счётчики §15.2(B) никем не читалисьstageSeqResult.repair был мёртвым полем, деливерабл D5 не выполнен ничего Store.RepairStats выводит calls/declined/applied из durable-артефактов, QualityReport + CLI их показывают. TestRepairCountersDerivedAndSurviveResume, TestRepairDeclineIsCountedSeparately
6 MAJOR: latin_residue не гейтился по таргету — на латинском таргете КАЖДОЕ слово становилось кандидатом (скан выдавал бы бессмыслицу) без гейта тот же isRuTarget на обоих колл-сайтах, что у lint-близнеца
7 MAJOR: оба класса dc1 были актуаторами по умолчанию вопреки §16.2 в дефолт-наборе дефолт = latin_residue + broken_word; dc1 — детекторы, актуаторы только по явному указанию оператора
8 MAJOR: ExpandToSentence без абсолютного потолка → спан мог стать целым юнитом без потолка repairMaxSpanBytes; кандидат отбрасывается ДО оплаты
9 MAJOR: спан dc1_fractional не выровнен (одно «полчаса» в юните может не соответствовать 半个时辰) смягчено п. 7 (не актуатор по умолчанию); при явном включении ограничение остаётся и задокументировано

9.2. НЕ исправлено — решение оркестратора

MAJOR: фикс пар-данных двигает снапшот ОБЕИХ волн. Проверено мной исполнением: пак-версия langpack-v2-f83acc67ea0f (HEAD) → langpack-v2-eaea82333353 (рабочее дерево); LangpackVersion лежит в ОБЩЕЙ части payload (snapshot.go), а buildSnapshotID — один на обе волны. Значит для любой zh-ru книги с langpack приземление пака = громкий --resnapshot, при котором пере-оплачивается и драфт-волна, а не только edit. Это шире, чем «version-only сдвиг пака», как я классифицировал в первой редакции.

Практическая цена сегодня — вероятно ноль: rerun2 закрыт (D39.22), книг «в полёте» нет. Но инвариант «$0-резюм драфта» формально задет, поэтому решение ваше:

  • (а) принять both-wave re-pin как цену фикса (мой выбор по умолчанию: книг в полёте нет, а фикс — предусловие любого актуатора);
  • (б) отложить правку dc-checkers.txt до следующего санкционированного полного пере-прогона (тогда из пака уходит ратифицированный пункт 3);
  • (в) построить пак-суб-хеш, фолдящийся только в волну финальной стадии (чистое решение, но это НОВЫЙ механизм и расширение скоупа — без вашей санкции не делаю).

9.3. Что это говорит о процессе

Все девять исправленных дефектов лежали в коде, который прошёл мои собственные тесты и мою собственную 4-линзовую самопроверку из §11 дизайна. Их поймала только панель, гонявшая КОД, а не читавшая его: шесть из девяти воспроизведены исполнением в песочнице (реальный пайплайн, мок-провайдер), а не выведены из чтения. Вывод для нормы приёмки: для пака, который МЕНЯЕТ отгружаемые байты, чтения диффа недостаточно — нужен прогон враждебных сценариев через настоящий драйвер.

Итоговое состояние после исправлений: build/vet/-race — 12 пакетов ok; golden f7641993e37f7ec619dfc5d2914a342d796a7cecd92080e33704f3974f0020f8 (не сдвинулся); тесты 399 → 432, удалено 0; gofmt чист кроме пре-существующего llm.go.


10. МИНИ-ДЕЛЬТА ПРИЁМКИ (4 пункта оркестратора)

(1) hour_word_re — левая граница + переезд в пар-данные

Дыра воспроизведена ДО правки (пробой по реальному паку): MentionsHourWord возвращал true для «полчаса», «тотчас» и «сейчас» — то есть ответ, УДАЛИВШИЙ длительность и подставивший любой филлер на «-час», удовлетворял позитивное условие дробного класса, ради которого оно и существует.

Дом — пар-файл configs/langpacks/zh-ru/dc-checkers.txt, ключ hour_word_re. Аргумент: пробу потребляет ТОЛЬКО позитивное условие дробного класса DC1, а тот не может сработать без пар-ключей halfshichen_*; его родня (ru_hours_re, ru_hour) уже живёт в пар-файле. Класть в internal/lang/data/target-ru.txt значило бы (а) разнести данные ОДНОГО класса по двум файлам и (б) заявить пробу target-general, хотя ни один target-general чекер её не читает. Новая пара, объявляющая дробный зонд, обязана объявить и свою пробу — иначе она молча унаследовала бы русскую орфографию.

Обе границы явные (RE2 без lookaround, \b только ASCII). После правки: «час»/«шесть часов»/«два часа» → true; «полчаса»/«тотчас»/«сейчас»/без длительности → false. Тесты: TestHourWordProbeBoundaries (8 кейсов, включая названные оркестратором), TestRepairFractionalRejectsHourFiller (сквозной).

Плюс fail-loud, которого не просили, но без которого правка неполна: пара, объявившая дробный зонд БЕЗ hour_word_re, не может утвердить инвариант — раньше это дало бы «платим и отвергаем» на каждом юните молча. Теперь класс не грузится, ошибка называет недостающий ключ (TestRepairFractionalRequiresHourProbe).

Классификация сдвига версии пака: langpack-v2-eaea82333353langpack-v2-a7d4be2c0465 (команда: lang.Load("configs/langpacks","zh","ru").Version()). Класс тот же, что в §9.2 — version-only по паку, но both-wave re-pin для zh-ru книги. Дельта НЕ добавляет нового класса сдвига: она попадает внутрь того же единственного --resnapshot, который уже несёт основной фикс данных. Решение §9.2 остаётся за вами и покрывает оба изменения разом.

(2) brokenWordCandidates + unicode.Cyrillic — задокументировано

Выбрал документирование, а не data-drive, и вот почему это не отговорка: сканер кандидатов ОБЯЗАН токенизировать так же, как lint-близнец (text.TokenizeCyrillic), иначе ломается свойство вердикт-нейтральности, на котором стоит весь файл («кандидат только там, где сработал lint»). Data-drive ТОЛЬКО сканера сделал бы их разными. Долг репарируем лишь на общем токенизаторе — одной осознанной правкой target-сеама, а не побочным эффектом петли ремонта. Это записано в комментарии у самой функции, со ссылкой на близнеца и на D39.24 («ru-target-долг — задокументированное ограничение, НЕ чинить»).

(3) D5 — доставлено частично, раскрываю + доделал дешёвую половину

Пункт D5 Состояние
CostSource-маркер role='repair' без миграции; RepairSpentUSD
Счётчики починок в quality-report calls / applied / declined / rejected, выведены из durable-артефактов
Поля паспорта главы доделано в дельте: ChapterPassport.RepairApplied, выведено из namespace final_hash (TestRepairSurfacesInChapterPassport)
Исходы ПО КЛАССАМ (applied/declined/rejected на класс) НЕ доставлено — в очередь. Причина механическая: класс нигде не записан durable. Чекпойнт ремонта несёт роль, но не класс; derived-id несёт namespace, но не класс. Вывести per-class исходы можно только записав класс — то есть колонкой ИЛИ расширением формата derived-id/finish_reason. И то и другое — решение о СХЕМЕ/ФОРМАТЕ, а не мелкая доводка, поэтому не делаю молча. Кандидатов по классам (repair_candidates_by_class) это НЕ касается — они есть.

(4) Гигиена

  • Секция в docs/PROGRESS.md добавлена.
  • Стейл-цифры первой редакции приведены к фактическим: ценз тестов 427 → 433 (после дельты добавились тесты границы часа, филлера, load-guard и паспорта), дифф +675/19 → +767/29, объём новых файлов 1416 → 1834.
  • «§16.2 ратифицирована» → «одобрена приёмкой оркестратора» (в трёх местах); ратификацию оформляете вы.

Честная поправка о процессе внутри самой дельты

При правке теста фолда я заменил текст «от функции до конца файла» и тем самым снёс 7 тестов, дописанных ранее (гарды ответа, цифровой гард, позитивный ре-гейт, дробная длительность, реплей под исчерпанным бюджетом, счётчики, декларация деклайна). Поймал это ценз-диффом ^func Test СРАЗУ (это ровно то, ради чего ценз и введён нормой), все семь восстановлены, финальный дифф против baseline: удалено 0. Отмечаю, потому что это третий случай в проекте, когда тесты пропадают при механической правке файла — и первый, когда механизм поймал это до отчёта, а не после.