textmachine/docs/archive/PROGRESS-2026-08-17.md

34 KiB
Raw Blame History

Слайс хроники: бэкенд-пак «честные числа и статусы» (17.08.2026)

Архив. Инструкции отсюда НЕ исполнять, при онбординге НЕ читать — только по конкретной ссылке. Вынесено оркестратором №18 22.08 из docs/PROGRESS.md секции «Бэкенд»: секция объявляет себя местом ТОЛЬКО для пингов живых сессий, а держала 60 строк отчёта закрытой работы.

Исход: пак ИСПОЛНЕН, ПРИНЯТ С ФИКС-ЛИСТОМ и ЗАЛЕНДЕН f840867 (17.08) — ратификация D39.149, решение владельца по ФЧ-5 — D39.150. ⚠ Прежний заголовок блока писал «дерево не закоммичено»; это было верно один день и опровергалось записью приёмки в том же блоке — испр. при выселении. Живой остаток работы — строка бэклога 197 (фикс-лист ФЧ-1…ФЧ-8), её тело ссылается СЮДА. Состояние бэкенда — CURRENT-STATE в ../PROGRESS.md, решения — D-лог.

Пак «честные числа и статусы» (строки 181 · 176 · 172-г · 187) — ИСПОЛНЕН, дерево не закоммичено (17.08)

Записка-план (пункт заказа → где). 1) 181 — internal/pipeline/reprice.go (нов.) + store/ledger.go (CheckpointUsageForBook) + rebill.go/status.go; тест reprice_test.go. 2) 176 — internal/obs/panic.go (нов.), recover воркера waverun.go:260-268, exitOf в cmd/tmctl/main.go, ветка *obs.PanicError→1 в exitCode; usage-строка invocation.go:96 + её замороженный тест; тесты wavepanic_test.go, panic_exit_test.go. 3) 172-гlive_reprobe_threshold_test.go (нов., без тега) + риг live_reprobe_test.go. 4) 187 — snapshot.go:179-190, membank/memory.go:359-365. 5) живой прогон — исполнен, $0.005582.

181, развилка источника чисел (решение). Пере-прайс идёт ФАКТИЧЕСКИМИ токенами чекпоинтов через тот же шов, что и сеттл (Pricer.PriceForResponse + ledger.CostUSD), а не оценкой EstimateUSD. Причины: оценка резервирует весь max_tokens + reasoning-буфер и завысила бы кратно (лечим «число врёт вниз» числом, врущим вверх); оценке нужны отрендеренные сообщения, которых read-путь сознательно не имеет; через шов сеттла проекция не может разъехаться с тем, что забронирует леджер. Мост к usage — НЕ FinalHash (он только на ok-пути, как и предупреждал заказ), а джойн checkpoints ⋈ jobs по (chapter, chunk_idx, stage): он достаёт usage и для flagged-строк, и для всех попыток. Обе половины гейта починены вместе: projectBookUSD (база 5%-порога и projected_book_usd) пере-прайсится тем же репрайсером — иначе порог сравнивал бы две разные валюты. Историческими остаются committed/reserved и CostUSD глав-паспортов: потраченное — факт, а не проекция. Не проецируется (задокументировано, как и контент-ось): РОУТИНГ — строка прайсится моделью, которая ОТВЕТИЛА, а не той, в которую стадия резолвится сейчас. HistoricalRows считает строки, чей usage недостижим (чекпоинт снесён redrive'ом; billed-decode с {}-usage) — они несут историческую сумму, и операторский текст это НАЗЫВАЕТ.

176, выбор кода. Паника (любой горутины) → 1, «infra failure and everything else». Не новый номер: обе полосы — замороженный шов, а слово в ратифицированном словаре не заводится сессией. Ветка *obs.PanicError стоит ПЕРВОЙ в exitCode. obs.PanicError намеренно БЕЗ Unwrap: паника значением context.Canceled/*CeilingHalt иначе уехала бы в 5/4 при формально нетронутом словаре (покрыто тестом на 7 значений). obs.SafeGo НЕ применён — он глотает. Стек едет в Error(), значит на stderr при любой обёртке.

Команды и результаты. make battery → EXIT=0; SKIP ровно два: TestHelperEventsRun, TestHelperKillLoopоба helper-процессы подпроцессных харнессов (t.Skip("helper process only") без env-ключа), не пропуск покрытия. Корпусные четыре (TestMinerFullBookParity, TestCheckerLabels*, TestK6LabelsBaseline) на этом стенде ПРОШЛИ, не скипнулись. Голден: go test ./internal/pipeline/ -run Golden -count=1 → ok, бит-в-бит. go vet -tags live ./... → чисто. grep -rh '^func Test' backend --include='*_test.go' | wc -l = 819; в диффе ^-func Test = 0 удалённых, +9 добавленных. ⚠ Испр. приёмкой №18: финальное число — 827 (пере-считано командой отчёта после последних правок; 819/823 — промежуточные снимки), удалённых имён 0 подтверждено.

Красное до фикса — исполнением (откат в рабочем дереве, без git): 181 — re-payment projected at $0.003640 but the current price table makes that work $0.014560; отдельно откат только базы порога — status projected_book_usd = $0.003640, want $0.014560. 176 — при снятом recover тест-бинарь падает паникой (panic: worker exploded mid-chunk), а реальный бинарь при непойманной панике выходит 2 (замерено: собранная программа-однострочник, exit=2). Usage-строка — usage error text is frozen, got: usage: tmctl <translate|report|status|export|redrive|manifest> --config book.yaml. Правок тестов ради зелени нет: изменены только invocation_test.go (санкция тела 176 + D39.134 п.3) и механические сайты вызова после смены сигнатур projectRebill/projectBookUSD — утверждения не тронуты.

Пункт 5, живой прогон (санкция владельца 17.08, потолок $0.20). Стенд ~/books/gu-zhenren/coldrun-b/reprobe/classify6/ был на схеме v14 против v15 — status дал ратифицированный exit 13 schema_mismatch, прогнан tmctl migrate (v14→v15, restore point взят). Замер: 4/5 прогонов достигли 6/6, finish=stop на всех пяти, bad_lines=0, модель deepseek-v4-flash, max_tokens=8000, эффорт unset. Итог: ГЕЙТ ПРОЙДЕН по ратифицированной форме — и это ровно тот 4/5, на котором старая форма «6/6 на КАЖДОМ» упала бы ложно. Трата $0.005582424; леджер пробы 0.00038997→0.00597239, дельта совпала с числом рига до последнего знака, reserved_usd=0. Ревью исполнением: запросы прочитаны глазами (дамп classifierMessages в classifier-request.jsonreasoning:"", то есть reasoning-off НЕ шлётся и thinking у DeepSeek остаётся ON, эхо-мина не взведена), сырые ответы прочитаны из classifier-6of6-unset.json, стоимость двух прогонов пересчитана вручную по новой таблице flash и сошлась (run0 = 1143·0.44 + 869·1.32 за 1M = $0.001650). ⚠ Строку 172-г НЕ закрываю: риг меряет классификатор на flash, а сменившиеся веса — у pro (редактор).

Диспозиция строки 164 — ОСТАВЛЕНА ОТКРЫТОЙ, и она по-прежнему верна. Сверено кодом: Status() (status.go:388) не зовёт seedGlossary, поэтому r.baseMemory на read-пути nil (seeding.go:157-159 — единственное место присвоения), precomputeSticky даёт пустую инъекцию и renderedContentHashes не опознаёт repin (rebill.go:166). Пак этого не двигает: он чинит ЦЕНУ строки, а 164 — про то, какие строки вообще попадают в счёт. Разделение честнее, потому что лечение 164 — материализация банка на КАЖДОМ status (его платформа зовёт перед каждым спавном), то есть решение о цене read-пути, а не правка текста. Пере-прайс её не маскирует — он масштабирует завышение той же таблицей.

Находки вне заказа (не чинил, оркестратору на завод строк). (1) Промт §5 называет LOG_LLM_BODIES=1+LOG_LEVEL=debug носителем тел запросов для рига — не работает: ключ читается из obs.ReqInfo контекста (cmd/tmctl/main.go:196-200 → гейт obs/logging.go:77), а live_reprobe_test.go:119 строит ctx без WithReqInfo, поэтому тела не логируются ни при каких env. Использован второй разрешённый носитель (дамп). (2) docs/experiments/00-provider-quirks.md до сих пор пишет «цены DeepSeek не изменились (flash $0.14/$0.28)» — протухло со сменой 16.08 (D39.137), зона не моя. (3) Якоря строки 181 ПРОВЕРЕНЫ и оказались точными (не расхождение, называю потому что сверял): в HEAD rebill.go:166 = p.USD += cs.CostUSD, :252 = сравнение с потолком согласия, stagerun.go:431/:613 = EstimateUSD(PriceFor(...)) и PriceForResponse(...). (4) Прежний артефакт стенда classifier-6of6.json — старого одно-сэмплового формата; текущий риг пишет classifier-6of6-<effort>.json, старый файл рядом никем не перезаписывается и вводит в заблуждение. (5) Список команд usage-строки и подсказки unknown command были двумя ручными копиями; сведены в один dispatchCommands (байты подсказки не изменились). (6) Строки 146/173/177 по пути видел — не трогал (подписанный пропуск заказа).

Адверсариальное ревью §4 — ПРОВЕДЕНО (17.08, воркфлоу wf_6f671334-f69: 23 агента, 0 ошибок, 1.95M токенов, 37 мин). Три независимые линзы (fable — деньги/алгоритм/контракт; opus — ремесло/проза/идиоматика; opus — шов exit-кодов + верность §3/§4), по скептику-опровергателю на каждую находку, критик полноты финалом. 20 находок → 11 опровергнуто, 8 выжило, +5 от критика.

Находка (линзы) Вердикт Диспозиция
Репрайсер суммирует чекпоинты ВСЕХ поколений: checkpoints append-only на всю жизнь книги и не несут снапшота, а chunk_status.cost_usd ПЕРЕЗАПИСЫВАЕТСЯ текущим поколением ⇒ после первой же пере-покупки число согласия и projected_book_usd завышены ×N CONFIRMED ×3 линзы, воспроизведено исполнением ПРИНЯТА — регресс МОЕГО же фикса, исправлен. Членство поколения выводится из единственного пер-поколенного авторитета в сторе — денег самой строки: обход вызовов С НОВОГО КОНЦА, пока они помещаются в cs.CostUSD (reprice.go). Потребовало порядка вставки: ORDER BY c.rowid (по attempt нельзя — он перезапускается каждый прогон)
Пропущенные (skipped) строки прайсились по осиротевшим чекпоинтам своей позиции — фантомные деньги в базе порога, без всякого --resnapshot PARTIAL (тот же корень) ПРИНЯТА, закрыта тем же фиксом: у skipped-строки cs.CostUSD=0 ⇒ обход не берёт ни одного вызова
Паника воркера, проигравшая гонку другой ошибке волны, исчезала бесследноfail first-wins, и прогон уходил кодом сиблинга: 4 (платформа пишет paused) или 5 (graceful stop), стек уничтожался PARTIAL, воспроизведено на трёх видах первой ошибки ПРИНЯТА, исправлена. У краха свой слот panicErr, возвращается ПРЕИМУЩЕСТВЕННО перед firstErr; first-wins для обычных ошибок не тронут. Разошёлся со вторым скептиком, советовавшим только печатать стек: упавший процесс не «пауза» — врать в пользу возобновляемости хуже
Обоснование правила остатка ложно: докстринг ссылался на redrive, а ResetChunkStages сносит строку диспозиции ВМЕСТЕ с чекпоинтами в одной транзакции — сценарий недостижим CONFIRMED / PARTIAL ПРИНЯТА: остаток пере-описан честно (пол для стора, потерявшего вызовы — рестор, легаси), тест пере-написан
Спека-проект backend/docs/D15.2-*.md:398 всё ещё определяет проекцию как Σ хранимого cost_usd (критик; моя зона, заказ требовал «правда или прочь») найдено критиком ПРИНЯТА: эррата в теле спеки
Три непокрытые мутации (цена по ОТВЕТИВШЕЙ модели · раскрытие непере-прайсенной части в тексте согласия · порядок panic-first при errors.Join) найдено критиком ПРИНЯТЫ: три пина добавлены; последний показан красным (перенос ветки паники ниже сентинелов даёт 2/3/10)
Проза: история про exit 2 пересказана в шести блоках; 33-строчная шапка reprice.go ОПРОВЕРГНУТЫ по метрике (плотность файлов сдвинулась на 12 пункта, норма репо такая же) ЧАСТИЧНО ПРИНЯТЫ ВОПРЕКИ ОПРОВЕРЖЕНИЮ — слово владельца о лаконичности выше довода «дом так пишет»: шапка reprice.go 33→14 строк. ⚠ Испр. по раунду 2 (находка F6): моё утверждение «история про exit 2 рассказана ОДИН раз» было НЕВЕРНО — она изложена на четырёх сайтах (obs/panic.go, waverun.go, wavepanic_test.go, panic_exit_test.go) и упомянута ещё на двух в cmd/tmctl. Ревьюер рекомендовал НЕ резать: каждый текст говорит про свой шов и все четыре фактически верны; исправлено утверждение, не код
status жёстко падает при ошибке загрузки репрайсера · dispatchCommands не выводится из switch · порядок в CheckpointUsageForBook избыточен · TM_CLASSIFY6_N мусорный игнорируется · плоская карта вместо вложенной · и ещё 6 ОПРОВЕРГНУТЫ исполнением Действий нет. По одной опровергнутой находке действие всё же сделал: EXPLAIN QUERY PLAN показал лишнюю temp-B-tree сортировку по колонкам, которых логика не использует → ORDER BY c.rowid

РАУНД 2 адверсариального ревью — ПРОВЕДЁН по дельте первого (воркфлоу wf_bb22ee8a-a4b: 15 агентов, 0 ошибок, 1.22M токенов, 70 мин). Скоуп узкий и намеренный: свежий, никем не смотренный фикс. Линзы — fable по правилу членства поколения (с прямым указанием, что это ВЫВОД, а не тождество), opus по слоту panicErr + мутационная проверка тестов. 12 находок → 5 опровергнуто, 7 выжило, +3 от критика. Вердикт критика: HAND OVER WITH NAMED CAVEATS.

Находка Вердикт Диспозиция
R2-1: обход ломается на ОТКАТЕ конфига. Правило считает, что вызовы текущего поколения — самые НОВЫЕ в ячейке. Откат промпт-конфига адресуется старым request_hash, сеттл становится no-op (ON CONFLICT DO NOTHING), строка пере-записывается стоимостью СТАРОГО вызова, а новый остаётся на файле. Воспроизведено сквозным прогоном: тихое ×2 завышение при HistoricalRows=0 PARTIAL major. ⚠ Скептик снял главное обвинение: это НЕ регресс моего фикса — до-фиксный код даёт то же число в этом потоке; направление «занижение» ново, но показано только синтетически ПРИНЯТА. Добавлен детектор: перебор accounted > cs.CostUSD доказывает, что новейшие вызовы не принадлежат строке ⇒ откат к сумме счёта с fromHistory=true. Тихое ×2 меняется на устаревшую цену, названную вслух. Точное решение требует маркера поколения на checkpoints (схема денежной таблицы) — вопрос на ратификацию, а не третья заплата
F1 (blocker): направление обхода НЕ ПРИБИТО — переворот на «от старых» переживал всю зелень: мой фикстур был симметричен (два одинаковых поколения) PARTIAL ПРИНЯТА: поколения сделаны асимметричными ($0.60 против $0.05); мутация теперь убита
F2: ORDER BY c.rowid не прибит — и ORDER BY c.attempt, и полное удаление переживали зелень. Плюс: планировщик показывает, что клауза temp-B-tree не убирает, а ДОБАВЛЯЕТ PARTIAL major ПРИНЯТА: store-тест с немонотонными attempt (0,1,0,1 против порядка вставки). ⚠ Моё обоснование через EXPLAIN QUERY PLAN в записи выше было неверно: клауза нужна для КОРРЕКТНОСТИ, а не ради плана
F4: счётчик раскрытия HistoricalRows не прибит — удаление инкремента переживало зелень PARTIAL minor ПРИНЯТА: тест ведёт флаг из rp.usd через реальную projectRebill до текста согласия
F8/R2-2: докстринг guard-теста называл формы, которых фикстуры не строят (ретрай, производные экспорты) — дрейф док-против-кода в паке про честность PARTIAL ПРИНЯТА: комментарий говорит ровно то, что тест гоняет
C2 (критик): текст согласия звал ЧАСТИЧНО пере-прайсенную единицу целиком исторической («their usage is no longer on file») найдено критиком ПРИНЯТА: формулировка исправлена
C1 (критик): dispatchCommands прибит не в ту сторону — выброс команды ИЗ списка оставлял зелень найдено критиком ПРИНЯТА: AST-тест читает сам switch run() и сверяет со списком в обе стороны
C3 (критик): мусорный TM_CLASSIFY6_N тихо игнорировался и проба покупала 5 вызовов — прямо над громким гейтом найдено критиком ПРИНЯТА: нечисловой/неположительный — громкий отказ до первого вызова
F6 (проза), и 5 опровергнутых: $0-строка не пере-прайсится вверх · $0-вызов теряется · при победе паники теряется ошибка сиблинга · Status жёстко падает · «крах не может уйти кодом 2» неверно для fatal error ОПРОВЕРГНУТЫ Кода не трогал. Последнее — честное уточнение: неперехватываемые fatal error (конкурентная запись в map, OOM) по-прежнему дают 2; recover их не ловит по устройству Go. Назвать оркестратору как остаточный класс

Мутационная приёмка исполнением. Четыре мутации, которые у ревьюера ПЕРЕЖИЛИ зелень, теперь убиты: обход от старых → TestRepricerCountsOnlyTheCurrentGenerationOfCheckpoints; ORDER BY c.attemptTestCheckpointUsageForBookReturnsInsertionOrder; снятие HistoricalRows++TestProjectionCountsRowsItCouldNotRePrice; снятие детектора перебора → тот же генерационный тест.

Что критик проверил САМ и подтвердил (не мои клеймы, его прогоны): батарея EXIT=0 с ровно двумя SKIP; голден сверяется с ФАЙЛОМ testdata/golden/capture.golden, не сам с собой, и файл не в diff; wire-нейтральность доказана трижды (голден · оба хеш-несущих файла в дельте изменены ТОЛЬКО в комментариях · под prompts//configs//langpacks//testdata/ нет ни одного изменения); ноль удалённых имён тестов; оба регресса раунда 1 красные на до-фиксном коде; ни platform/, ни frontend/, ни контракт 14 не читают projected_book_usd/rebill_usd; новое чтение замерено на синтетическом сторе 800 глав / 4800 чекпоинтов / 64 МБ — 3943 мс против 10-секундного бюджета операции. Мой счёт тестов был на единицу меньше фактического.

Перепроверка после ревью — командами. make battery → EXIT=0, те же два SKIP. Голден → ok. go vet -tags live ./... → чисто. ^func Test = 823 (было 819), удалённых имён 0. Красное до фикса показано исполнением для ОБОИХ регрессов: projected $0.007280 против настоящих $0.003640 (пере-купленная книга) и wave returned *pipeline.CeilingHalt / *errors.errorString: context canceled вместо паники. Замер стоимости нового чтения: на самом большом реальном проекте (coldrun-a) 149 чекпоинтов, план — два индексных поиска, 2.13 мс.

Obstacle. (а) Интервальная самопроверка субагентом в СЕРЕДИНЕ работы не проводилась — ревью запускалось финалом по слову владельца; оба регресса нашлись бы раньше, промежуточный прогон линз стоил бы дешевле. (в) Живой прогон стартовал ~00:58 UTC и захватил границу пикового окна 0104 UTC (правило-рекомендация «не стартовать в пик»): при трате $0.0056 против потолка $0.20 ожидание трёх часов было бы несоразмерно, но факт называю. (г) git status показывает чужой ИНДЕКС (полигон: eval/, docs/experiments/) — не трогал и не прибирал; своё не стейджил.

Запись оркестратора №18, 17.08 — ПРИЁМКА ПАКА «ЧЕСТНЫЕ ЧИСЛА И СТАТУСЫ»: ПРИНЯТ С ФИКС-ЛИСТОМ, залендено. Панель 5 линз (слепая по заказу+диффу ДО отчёта · деньги · шов · вне карты · собственные мутации в копии) + пере-раны оркестратора. Своей рукой пере-проверено исполнением: make battery EXIT=0, SKIP ровно два и оба helper-процессы · голден -run Golden ok · ^func Test = 827, удалённых имён 0 · деньги пробы ДВУМЯ независимыми путями — сумма чекпоинтов минус вызов 02.08 и пере-счёт из сырого usage_json по запинённой таблице models.yamlоба дали $0.005582424, до последнего знака · вердикт гейта из сырого артефакта: runs: 5, runs_at_threshold: 4, finish=stop ×5 ⇒ PASS по ратифицированной форме и ложное падение по прежней. Регрессов против HEAD панель не нашла: три места, где движок врал, действительно перестали врать.

Фикс-лист ФЧ-1…ФЧ-8 (ни один не блокирует лендинг; все — «заявленное не прибито» либо «текст обещает больше числа»). ФЧ-1 (HIGH, три неприбитые гарантии, мутации ПЕРЕЖИЛИ зелень): снятие фильтра по книге в CheckpointUsageForBook (store/ledger.go:371) не валит ни store-, ни денежные pipeline-тесты — воспроизведено оркестратором лично, а это единственная защита от того, чтобы деньги ЧУЖОЙ книги вошли в число согласия · ключ ячейки можно лишить chunk_idx, и обход начнёт съедать деньги соседнего чанка (во всех фикстурах глава = один чанк) · ветка пометки «строка не пере-оценена» не ловится на продовом пути. ФЧ-2 (MED): два докстринга reprice.go описывают НЕ то, что делает код (детекция перебора односторонняя — суффикс вытесненных вызовов с точной суммой проходит молча; «остаток несётся по счёту» — код сперва съедает вызовы прошлых поколений) — в паке про честность это тот же класс, что чинился. ФЧ-3 (MED): заказ 187 исполнен наполовину — снятый закон approved-only продолжает утверждаться в докстринге теста и в backend/docs/D15.2-*.md:317. ФЧ-4 (MED): раскрытие «часть суммы — старые деньги» не доезжает до status/--json (HistoricalRows теряется на границе status.go), а второй операторский текст отказа (--accept-rebill) цитирует пере-прайсенное число без basis. ФЧ-5 (MED, деньги — решение владельца): пере-прайс чинит ось ЦЕНЫ, но не ось МОДЕЛИ — при смене модели стадии (модель входит в снапшот) число считается ценой ОТСТАВЛЕННОЙ модели, замер линзы: $0.003640 против честных $0.036400 при HistoricalRows=0, и фраза согласия при этом заявляет «current price table» без оговорки. Не регресс, но обещание §0 промта для этого триггера остаётся живым. ФЧ-6 (LOW): Status() жёстко падает при ошибке репрайсера — в трёх строках над сознательной политикой «ошибка проекции репортится, но отчёт не валит»; это канал сеттла платформы. ФЧ-7 (MED, шов): паника воркера ПОВЕРХ пойманного потолка уничтожает факт потолка на обоих каналах — прогон, остановленный ceiling'ом, платформа запишет failed, что PD-113 прямо запрещает (не регресс; до пака терялось иначе). ФЧ-8 (LOW): докстринг TranslateBook про «каждый выход через terminal()» неверен для паники вне волны · комментарий dispatchCommands («список теперь один») — usage-строка осталась второй копией · шапка рига ссылается на заменённую ратификацию.

Подтверждено панелью как ВЕРНОЕ (клеймы сессии, пере-проверенные независимо): словарь кодов побайтно не двинут, добавлен ровно один return 1; «паника → 1» читается платформой как failed во всех пяти точках потребления (инвентарь: ingest/exit.go · ingest/supervisor.go · runner/engine.go · runner/marker.go · runs/reconcile.go · books/parse.go), тогда как ДО пака упавший посреди книги прогон записывался ready · остаточный класс подтверждён: непойманные fatal error (конкурентная запись в map, OOM) по-прежнему дают 2, recover их не ловит · wire-нейтральность: оба хеш-несущих файла изменены ТОЛЬКО комментариями · строка 164 после пака по-прежнему верна · клейм сессии про банковые роли верен И это преexisting — заведена строка 194.

Что НЕ проверено: мутации ФЧ-1 (2) и (3) приняты со слов мутационной линзы — лично воспроизведён только фильтр книги · старый вызов 02.08 в леджере пробы не сошёлся ни с одной из двух ценовых таблиц (вне скоупа пробы, не разбирался) · перф нового чтения на боевой книге — замер только синтетический и со слов сессии.