43 KiB
Ревью-шапка оркестратора №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§0–14 + §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_class
(и repair_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.6–2.0× · запрет новых переводов строк · script-гард (ни одной новой руны исходного письма) · сохранение ≥60% словных токенов · сохранение всех цифровых прогонов, кроме ровно одной замены значения. Плюс ре-гейт по СОБРАННОМУ тексту: адресуемых дефектов стало меньше · cheap-суита не выросла ·classifyok · санитайзер не загрязнён · пост-чек глоссария не приобрёл confirmed-промахов. - (B) счётчики БЕЗ миграции. Выводятся из durable-артефактов (
role='repair'-чекпойнты + namespacetm-repair-v1:уfinal_hash), потому чтоpersistRetrievalStateстроит строку с нуля и обнулил бы любые колонки на каждом резюме драфт-волны. Ноль новых колонок. - (C) непересечение спанов —
DisjointCandidatesДО оплаты; перед сплайсом — проверка границ иutf8.ValidStringпо собранному тексту. - (D)
errReserveCeiling— деградация, а не падение книги. - (E)
classifyнад ответом — шестой гард (ловит «Не могу помочь…», которое прошло бы все контентные). - (F, §16.3)
repairMuНЕ введён: оценка «≤1 перебор» требует держать лок ЧЕРЕЗ провайдерский вызов, а обоснование эскалации опирается на её редкость; ремонт — общий путь, и лок над транспортным ретраем заморозил бы всех воркеров. Кап читается без сериализации, перебор задокументирован как ≤ (workers−1).
Промпты пары — 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 |
Два вывода, которые стоит прочитать вместе.
- Дробный зонд поймал РЕАЛЬНЫЙ дефект прод-арма, невидимый всей прежней сюите: источник «半个时辰之后» (≈ час) отдан как «За полчаса» — ошибка длительности в 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-eaea82333353 → langpack-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. Отмечаю, потому что
это третий случай в проекте, когда тесты пропадают при механической правке файла — и первый, когда механизм
поймал это до отчёта, а не после.