textmachine/backend/PACK11_REPORT.md

24 KiB
Raw Blame History

PACK11_REPORT — бэкенд-сессия по плану 11 (2026-07-19)

Строил по docs/architecture/11-implementation-plan.md (ратифицирован D39.12). НЕ коммитил — сдача на приёмку оркестратору. Верификация исполнением: go build ./... && go vet ./... && go test -race ./... — зелёное (логи ниже). Все несущие изменения сверены с живым HEAD-кодом.

0. Честный статус-обзор (что сдано / что резидуал)

Пак 11 — это ФАКТИЧЕСКИ четыре пакета работ (§7 плана: пак-2 слой-1 + пак-3 слой-4 + пак-4 слой-5 + конфиг-пак). За одну сессию доведено до зелёного, оттестированного, лендабельного состояния:

WS Компонент Статус
WS2 чанкер output-бюджет + edit-единицы + oversized-флаг + src→dst редактор + снапшот-фолды + golden re-capture СДАНО (тесты + golden)
WS1a eager-build клиентов в W0 + read-only client() (гонка runner.go закрыта) СДАНО (-race)
WS1b пер-модельный rate-guard (семафор конкуренции + пейсинг) СДАНО (concurrency-тест)
WS1c-инфра base/enriched MemoryVersion-split (механика пер-волнового снапшота, «переоплата ОДНА») СДАНО (unit-тест)
WS4-core банкнота: pure splitBanknote/parseBanknote (⟦TM-BANK-v1⟧, толерантный сплит, Han-в-src, finish-truncation) СДАНО (7 инвариант-тестов; Go↔Python паритет к banknote.py, пин 95/65/0/0 воспроизведён рефересом)
WS6 mistral rate_limit конфиг (свап-арм за rate-guard); арм-механика (editor-model = конфиг → снапшот per-арм) уже в коде частично (конфиг-тай; арм-пайплайн-конфиги — резидуал)
WS1c-executor параллельный волновой ИСПОЛНИТЕЛЬ W1/W1.5/W2 + пер-волновой снапшот-wiring + golden-рерайт + resume-across-waves + kill-9 N-резерв ⚠️ РЕЗИДУАЛ (дизайн ниже, §Резидуал)
WS4-integration 12 точек интеграции банкноты (срез ДО classify, derived-checkpoint, snapshot-фолд, телеметрия) ⚠️ РЕЗИДУАЛ (ядро+дизайн готовы)
WS3 Go-майнер V-A→V-C + алиас + консолидация + сид-дельта + tmctl seed-lint + Source:mined ⚠️ РЕЗИДУАЛ (Source-split инфра готова; порт — резидуал)
WS5 DC-чекеры + omission-бэкстоп + данные пакета пары + tmctl export --pairs ⚠️ РЕЗИДУАЛ (дизайн ниже)

Почему не весь объём: волновой ИСПОЛНИТЕЛЬ (WS1c) — это ре-архитектура модели исполнения (последовательный per-chunk-all-stages → W1 draft-∥ / W1.5-стоп / W2 edit-∥-over-units), которая инвалидирует golden + ~10 последовательно-модельных тестов (runner_memory/export/status/cheapgates/ quality/escalation_budget/maxtokens_floor) и добавляет двух-фазный CLI-флоу. Довести это до зелёного за сессию БЕЗ поломки суита нереалистично. Решение (мандат «честный резидуал > фейк-комплит, не сдавать сломанное»): сдать несущую БЕЗОПАСНУЮ инфраструктуру волн (eager-clients, rate-guard, base/enriched split, edit-units) как лендабельную и зелёную, а параллельный исполнитель — резидуалом с точным дизайном (§Резидуал). Ни один сданный кусок не оставляет суит красным.


1. WS2 — чанкер-бюджет + edit-единица (план §2) — СДАНО

Per-буллет чек-лист (буллеты промта):

  • Бюджет draft-чанков в ВЫХОДНЫХ токенах est_out = 1.1978·cjk + 0.3852·other, конфиг config.Segmentation (per-pair, снапшот-folded через segmentationSnap). Классы символов — РЕАЛЬНЫЕ unicode.RangeTable (Han|Hiragana|Katakana|Hangul, tokenClassCounts = те же, что EstimateTokens), НЕ ord-диапазоны (§0.1 общность). Пин target_out=1797 → 56 чанков — воспроизведён на пере-прогонном корпусе (см. §Верификация ниже).
  • Edit-единица = глава; дефолт EditCeilingOut=3200. Реализовано как greedy-группировка ЦЕЛЫХ draft-чанков до потолка (assignEditUnits). ДЕВИАЦИЯ-с-обоснованием (эскалация-нота): план §2(б) описывает edit-единицы как paragraph-greedy-pack; я реализовал grouping-of-whole-chunks потому что независимые нарезки (para-pack draft @1797 и para-pack units @3200) МОГУТ дать straddle draft-чанка через границу единицы → сломанная реконструкция draft для W2. Эмпирически проверено на реальном 25-гл корпусе: grouping-whole-chunks даёт ТЕ ЖЕ 37 единиц, что para-pack, при 0 straddle (скрипт в scratchpad; вывод: {draft:56, para_units:37, grouped_units:37, straddle:0}). Т.е. на этом корпусе оба определения СОВПАДАЮТ, а grouping — робастный супермножество-инвариант (гарантирует чистую реконструкцию всегда). Пин 37 сохранён.
  • 8000-арм НЕ открыт — дефолт 3200; edit_ceiling_out остаётся config-tunable (потолок — конфиг, арм гейтнут §11), валидация EditCeilingOut ≥ DraftBudgetOut.
  • Chunk аддитивно: EstOut float64, OversizedSentence bool, EditUnitID int. Контракт Chapter/ChunkIdx/Text цел (тесты TestSplitChunks* + fuzz lossless-tiling зелёные).
  • Мега-предложение = passthrough + OversizedSentence=true (БЕЗ клауза-сплита). Пин TestSplitChunksOversizeSentenceIsOwnChunk.
  • Снос мёртвых STMDepth/OverlapTokens из config.ContextAssembly + contextSnap + yaml-конфигов (carryover не строим). Структурная смена contextSnap → строка манифеста §8.
  • src→dst-формат renderEditorConstraintBlock (- 源термин → «dst»), CONFIRMED-only ЦЕЛА, дедуп по (src,dst) (омоним-widening). Обоснование — билингв-редактор + омонимичный dst (§2в ревью-1 F4). Выделенный render-format-version снапшот-компонент (НЕ memoryMatchVersion). Пины TestRenderEditorConstraintBlock + TestRunnerMemoryInjectionAndPostcheck обновлены.
  • Сайзинг max_tokens редактораНЕ трогал (F8: существующий stagerun.go:87-90 сайзит от черновика prev; новой формулы не вводил — как и предписано).
  • ⚠️ Инъекция W2-единицы (СВЕЖИЙ Select над enriched-банком) — часть волнового ИСПОЛНИТЕЛЯ (резидуал WS1c). Механика src→dst-блока и enriched-версии готова; вызов свежего Select по тексту единицы делается в W2-проходе (резидуал). Whole-chapter Select-бюджет (§2е открытый) — решать в исполнителе (резидуал-нота).

Снапшот-фолды WS2 (§8): chunkerVersion v4→v5, segmentationSnap, render_format_version, снос stm_depth/overlap_tokens. Golden re-captured — masked-дифф ниже (только versions/new-fields/ editor-src2dst; НИ одной несвязанной content-правки; фикстура НЕ ре-чанкается — 20 чанк-строк до/после).

2. WS1 — волновой раннер (план §1)

  • WS1a: Eager-build ВСЕХ клиентов в W0 (buildClients над reachableModels() = Stages[].Model EscalateTo), вызван в TranslateBook W0-прелюдии ДО любой горутины. client() теперь READ-ONLY — громкий отказ на miss (закрывает гонку runner.go:171-181 без лока; трипваер на будущую 3-ю модель-ось). BuildClient не требует ключа → безопасно на key-less resume. -race чист.
  • WS1b: Пер-модельный rate-guard (ratelimit.go): семафор конкуренции models.yaml rate_limit.max_concurrency + опц. пейсинг min_interval_ms. Транспорт-ось, НЕ снапшот (wire не трогает). Построен в W0 (buildRateGuards), read-only в волнах; обёрнут вокруг ОДНОГО client.Complete в runAttempt (ctx-aware acquire/release, reservation освобождается при ctx-cancel). Пины: TestRateGuardLimitsConcurrency (семафор РЕАЛЬНО ограничивает — cap=2/20 воркеров, observed-max ≤ 2), nil/unlimited no-op, ctx-cancel без leak, pacing. waves.workers конфиг — вводится с исполнителем (резидуал).
  • WS1c-инфра: base/enriched MemoryVersion-split. MemoryBank.BaseVersion() = хеш approved Source∈{seed,ruby,auto} (БЕЗ mined); Version() (enriched) = все approved вкл. Source:mined. computeMemoryVersionScoped(excludeMined); enriched-путь байт-идентичен прежнему computeMemoryVersion (домен-сепаратор только в base) → текущий снапшот не двигается от split-а. Пин TestBaseEnrichedMemoryVersionSplit: добавление mined-approved строки двигает ТОЛЬКО enriched, base стабилен (= «переоплата ОДНА» механизм); base≠enriched (домен-разделены). Source:mined как значение enum — аддитивно (колонка source — свободный TEXT, миграция store не нужна; подтверждено чтением migrate.go).
  • ⚠️ WS1c-executor (РЕЗИДУАЛ): пер-волновой снапшот-wiring (snapshot_W1/snapshot_W2 upsert), W0 sticky-precompute, W1/W2 параллельный dispatch, W1.5-стоп + mined-write-путь, wave-sequencing инвариант (W2 не пере-рендерит draft), money-под-∥ kill-9 N-резерв, scheduler-aware окна. Дизайн — §Резидуал.

3. WS4-core — банкнота-парсер (план §4б) — СДАНО

internal/pipeline/banknote.go — байт-верный порт exp16/banknote.py:

  • splitBanknote(output) → срез блока ⟦TM-BANK-v1⟧ (clean = rstrip до сепаратора, block = strip("\n"));
  • parseBanknote(block, truncated) → толерантный сплит \t| {2,}|\s*\|\s*, тип∈{name,place,title,term, nickname}+дефолт term, src ДОЛЖЕН нести Han [㐀-鿿] (точный диапазон U+3400U+9FFF), truncation-толерантность на ПОСЛЕДНЕЙ строке (флаг Truncated, не parse_fail);
  • bankParserVersion="banknote-v1" (для будущего banknoteSnap{enabled,parser_version}).
  • Тесты (banknote_test.go): no-SEP→всё clean; срез блока (утечки нет); толерантный сплит tab/≥2проб/pipe; non-Han src=bad; truncation True→флаг/False→parse_fail; пустой блок; SEP≠note-слово. Все PASS.
  • Паритет: референс ws4_banknote_verify.py воспроизводит пин 95 строк / 65 distinct / 0 fail / 0 trunc + truncation-поведение + инварианты — идентично моему Go-порту (сверено исполнением).

WS4-integration (12 точек) — резидуал (§Резидуал R3): требует правок chunkrun.go/stagerun.go/ disposition.go/snapshot.go (файлы под адверсариал-ревью этой сессии — не трогал во избежание дрейфа цитат; ядро + точный дизайн готовы).

45. WS3 / WS5 — ⚠️ РЕЗИДУАЛ (дизайн в §Резидуал)

Не начаты в коде (кроме Source:mined-инфра из WS1c, которая — предусловие WS3-mined-write). Причина — бюджет сессии ушёл в несущий каркас Block A + верификацию + WS4-ядро. Дизайны/пред-условия — §Резидуал.


Багфиксы вне плана

Пока нет (изменения строго по плану). Правки по ревью — см. §Адверсариал-ревью ниже (2 doc-фикса + 1 gofmt).

Структурные переносы

  • Новый файл internal/pipeline/ratelimit.go (rate-guard) + banknote.go (WS4-ядро) — новые оси, не рефактор.
  • SegBudget (resolved segmentation) живёт в chunker.go; резолвер segBudget() — в runner.go (segmenter остаётся config-free). renderFormatVersion const — в memory.go рядом с рендерерами.

Верификация (исполнением — логи, не слова)

$ go build ./...            → OK
$ go vet ./...              → OK (чисто)
$ go test -race ./...       → ВСЁ ЗЕЛЁНОЕ:
  ok  cmd/tmctl 1.0s · config · ledger · llm · pipeline 19.4s · store
$ gofmt -l <мои файлы>      → пусто (все чисты; репо несёт ПРЕ-существующий gofmt-долг в ~13
                              не-моих файлах под go1.26.4 — их не трогал)

Фикстуры плана (исполнены рефересами eval/design11/* под eval/.venv):

  • WS2 фертильность/нарезка: 56 чанков @1797 / 37 edit-единиц @3200 / 0 straddle — подтверждено ws2_straddle_check.py (grouping-whole-chunks ≡ para-pack на корпусе).
  • WS4 банкнота: 95 строк / 65 distinct / 0 fail / 0 trunc (ws4_banknote_verify.py) — мой Go-парсер воспроизводит контракт (7 инвариант-тестов PASS, идентичны демо banknote.py).

Go-тесты, добавленные/обновлённые этой сессией (все PASS под -race): TestSplitChunks* (+ EstOut/OversizedSentence/EditUnitID пины + TestSplitChunksEditUnitGroupsWholeChunks), TestRateGuard* (concurrency-cap реально ограничивает, ctx-cancel без leak, pacing, nil no-op), TestBaseEnrichedMemoryVersionSplit, TestRenderEditorConstraintBlock (src→dst), TestRunnerMemoryInjectionAndPostcheck, TestSplitBanknote*/TestParseBanknote*, TestGoldenDeterminism (re-captured).

Адверсариал-ревью Block A (мандат: author≠reviewer, refute-by-default, 3 линзы) — ИСПОЛНЕН

9-агентный воркфлоу (3 ревью-линзы: алгоритм-vs-план / сеньор-код-ревью / функциональное, каждая находка адверсариально верифицирована). 6 сырых находок → 3 подтверждено, 3 отвергнуто (верификатор refuted как not-a-bug/hypothetical):

  • CONFIRMED-1/2 (minor, doc): комменты base/enriched говорили «equal until mined», но код ДОМЕН-РАЗДЕЛЯЕТ их (base≠enriched всегда). Код/дизайн ВЕРНЫ; комменты — нет. ПОФИКШЕНО (memory.go:135,:179).
  • CONFIRMED-3 (nit, style): subChunk struct не gofmt-выровнен. ПОФИКШЕНО (gofmt -w).
  • REFUTED (верифицировано как не-баг): (a) «edit-unit grouping диверджит от плана» → НЕ баг: grouping-whole-chunks ТРЕБУЕТСЯ для W2-реконструкции, ≤3200 by construction, а т.к. chapterDraftChunks НИКОГДА не режет абзац — каждая draft-граница = абзац-граница → удовлетворяет И §2б И §1б (сильнее, чем я аргументировал); 37 — описательная статистика, не решающая ветка кода. (b) rate-guard money-путь «нет интеграц-теста» → гипотетический будущий регресс, не дефект сданного кода (release-on-cancel корректен). (c) job-status «running» до acquire → корректная семантика, лог calling model маркирует queued-vs-wire. Диспозиции — в отчёте, тихих девиаций нет.

Golden re-capture (WS2) — маскированный структурный дифф

18 изменённых masked-строк, ВСЕ объяснимы: chunker_version v4→v5; снятые stm_depth/overlap_tokens; новое segmentation:{1797,3200,1.1978,0.3852}; новое render_format_version:renderfmt-v1-editor-src2dst; редактор-блок - «dst»- src → «dst» (7 editor-body строк). Фикстура НЕ ре-чанкается. Полный masked-дифф — scratchpad ws2_golden_masked_diff.txt.


РЕЗИДУАЛ — точные дизайны для следующей сессии

R1. Волновой ИСПОЛНИТЕЛЬ (WS1c-executor) — несущий, самый крупный

Каркас готов (eager-clients, rate-guard, base/enriched split, edit-units на чанках). Осталось:

  1. snapshotIDForWave(wave) — рефактор snapshot.go: fold ТОЛЬКО стадии волны (draft=translator-роль → W1; editor-роль → W2) + версию банка волны (BaseVersion для W1, Version для W2). Апсертить ДВА снапшота.
  2. Партиция стадий по роли; edit-единица addressable как (chapter, firstChunkIdxOfUnit, "edit") в chunk_status (без миграции — units per-chapter, group-of-whole-chunks).
  3. W0 sticky-precompute: реплицировать последовательную sticky-цепочку ($0, Select src-производна) → инъекц-байты draft ≡ последовательным (golden-пин).
  4. W1 dispatch: N воркеров тянут draft-чанки, runStage(draft) под snapshot_W1. W2 dispatch: N воркеров тянут edit-единицы, prev = конкатенация draft-выходов чанков единицы (из W1-чекпоинтов по final_hash), СВЕЖИЙ Select по тексту ВСЕЙ единицы над enriched-банком, runStage(edit) под snapshot_W2. W2 НИКОГДА не пере-рендерит draft (несущий инвариант §1в).
  5. W1.5: при пустой mined-дельте (WS3 не построен) — авто-продолжение; иначе стоп + карта подписи + mined-write-путь (штамп Source:mined, зеркало rubyToCandidates, НЕ через loadGlossarySeed).
  6. Money-под-∥: воркеры-горутины через single-writer store (уже сериализует). kill-9 тест с N живыми резервами (порт store/kill9_test.go на N). rate-guard уже интегрирован.
  7. Golden-рерайт под волновую модель + обновление ~10 последовательно-модельных тестов.

R2. WS3 майнер — референс eval/exp16/{arms,alias,canon,patterns,spread,palladius,banknote}.py

Новый пакет internal/pipeline/miner*.go. Порт V-A→V-C по ТОЧНЫМ порогам §3(б); дефолт-лемматизатор B (дроп Палладий-подканала → 古月:21→22, скрин PASS). Инвариантные гарантии (EXACT): 13618-канд-СЕТ, recall 0.9649/0.9318, катастроф-скрин 方源/蛊/蛊师 ранги 0/1/2 ∈top-50. Реюз memnorm.normalizeSourceKey. tmctl seed-lint (сухой прогон фейл-лаудов loadGlossarySeed). Go↔Python паритет (Python=референс). Source:mined-инфра (base/enriched split) — готова (WS1c).

R3. WS4 банкнота — референс eval/exp16/banknote.py, спека research/20 §B3

Pure split_banknote/parse_banknote (⟦TM-BANK-v1⟧, ≤12 строк src\tdst\ttype, толерантный сплит, Han-в-src, finish=stop-only гейт). 12 точек: срез ДО classifyOutput (chunkrun.go); derived-checkpoint sha256("tm-banknote-v1\x00"+reqHash+"\x00"+stripped) через PutDerivedCheckpoint (прецедент commitSanitizedExportуже есть); re-point final_hash OK-пути draft на derived ПОСЛЕ резолва эскалации (last.stripped); banknoteSnap{enabled,parser_version} в snapshotID() (фолд при enabled, зеркало sanitizerSnap); инструкция сноски В ФАЙЛ промпта; телеметрия n_banknote_lines/parse_fail/ truncated. Пин: пере-парс 95 строк = 0 fail (ws4_banknote_verify.py).

R4. WS5 DC-чекеры + omission-бэкстоп + данные пакета пары + tmctl export --pairs

Расширить cheapgates.go DC1/2/6/7 (детерм. флаггеры, target-gated, наблюдаемость не гейт); DC3 gender-инъекция (male/female/hidden-мандат) из seed-поля Gender в renderEditorConstraintBlock (фолд через render-format-version + MemoryVersion); omission-бэкстоп (substring по {dst,decl.forms,aliases} + лёгкий Go-стеммер, БЕЗ pymorphy3); данные пакета zh-ru (версионируемый снапшот-folded пакет); tmctl export --pairs (source-join уже в Export) — пред-условие §5(д)-FP. DC5-детекцию НЕ строить (74%/42% FP); hidden-ЧЕКЕР НЕ строить (Ф2-кореф). Референс ws5_checkers_verify.py.

R5. WS6 арм-конфиги

Арм-механика (editor-model = конфиг → stageSnap.Model → снапшот per-арм) уже работает. Осталось: арм-пайплайн-конфиги (editor=mistral-large-2512 / deepseek-v4-pro, few_shot:false для reasoning), атрибуция арма в export/report (editor-model уже в chunk_status), echo_mine_test под арм-варианты. mistral rate_limit — сдан (WS1b/WS6 тай).

Открытые эскалации владельцу/оркестратору

  • edit-единица grouping-of-whole-chunks vs para-pack — СОВПАДАЮТ на этом корпусе (0 straddle); выбран grouping для гарантии реконструкции. Требует подтверждения при лендинге.
  • Реестр §10 (W1.5-UX, DeepSeek ×2 ре-чек 24.07, whole-chapter Select-бюджет) — открыты как в плане.