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

34 KiB
Raw Permalink Blame History

Отчёт бэкенд-сессии: ПАК-18 «долги контракта» (25.07.2026)

Ревью-шапка оркестратора №8 (25.07, приёмка execute-first, инлайн; ПРИНЯТ — D39.31). 0 блокеров. Ре-ранил сам: build/vet чисты · gofmt — только internal/llm/llm.go, воспроизводится на HEAD (файл не трогали) · -race 12/12 · -shuffle=on зелёный · парити EXACT n=13618 … 0.9655 (56/58) · ценз диффом ИМЁН 455→475, удалено 0. Санкционированный пере-капчер проверен МАШИННО: замаскировал esc_model="…" в HEAD-версии и в рабочей — файлы байт-идентичны; brief_hash, оба snapshot_*_payload, memory_version совпадают построчно ⇒ вердикт-изменение не утащило ни одной другой оси, пере-оплаты нет. Сдвинулись ровно 4 строки, все ch3/0 draft. Новый baseline golden — 211c9013…089045d6. Мои независимые мутации (не из списка сессии), обе пойманы: формула порога minmax → красный TestRebillConsentThresholdFormula + пять смежных, включая оба CLI-провода; в проекцию пущены строки, резюмящиеся за $0 (завышение суммы под подпись) → красные три TestRebillProjection*. Развилка «--resnapshot ≠ согласие» — принята и даже не является девиацией: спека прямо говорит «отдельный от --resnapshot-алиаса» (backend/docs/D15.2-content-addressed-resume-spec.md:590); аргумент сессии сверх того верен — при алиас-прочтении ратифицированный порог становится гейтом, который не может выстрелить. Отсрочка п.3 принята как ИЗМЕРЕННАЯ: pack.Version() a7d4be2c0465133d71e8d132, стендовая книга объявляет langpack_root ровно на репозиторный каталог (проверил /home/ubuntu/books/gu-zhenren/rerun2/book.yaml); определительный аргумент по Detail-строкам (дефолт шаблона обязан рендерить текущую строку ⇒ литерал остаётся в Go) принят — байт-нейтральной формы у этого нита не существует. Оба нита едут на ближайший полный --resnapshot. Дефект честности, найденный сессией у себя (проекция считала строки-сироты и завышала сумму под подпись), — правильного класса; зафиксирован нормой в D39.31 п.3.

Промт: docs/BACKEND_PACK18_DEBTS_SESSION_PROMPT.md (оркестратор №8, выдан 25.07). Итог: пункты 1 и 2 ПОСТРОЕНЫ и проверены исполнением; пункт 3 ОТЛОЖЕН с доказательством исполнением, что байт-нейтральной формы не существует (принятый промтом исход). Сессия не коммитила. git mv не выполнялось. Платных вызовов — ноль.

Аддендумы: за время сессии релеем не приходило ни одного (строка-подтверждение по требованию промта).


Эхо-блок (сверка со скоупом, выдан ДО работы)

Скоуп: --accept-rebill[=usd] на снапшотной гранулярности · escalation_model = авторитетная модель · два нита общности УСЛОВНО. Инварианты: согласие ДО денег · golden байт-идентичен кроме санкционированного пере-капчера п.2 (только колонки esc_model) · ни одна ось RequestHash/снапшота не двигается · CheapGateVersion/pack.Version() не двигаются · общность §0 · $0-контракт read-only путей. Не делать: guard_hash/пер-чанковый dry-run · ru-target долг · accepts_labels · L3 · live 18+ · миграции durable-строк · расширение скоупа без пинга.


ПУНКТ 1 — --accept-rebill[=usd]: согласие на пере-оплату. ПОСТРОЕН

Что построено

Новый файл backend/internal/pipeline/rebill.go (единственное место логики) + проводка:

Файл Что
internal/pipeline/rebill.go RebillConsent, RebillProjection, projectRebill, projectBookUSD, rebillConsentThreshold, checkRebillConsent
internal/pipeline/runner.go:52 поле AcceptRebill RebillConsent (ортогонально Resnapshot)
internal/pipeline/bookrun.go:137 гейт в TranslateBook — после seedGlossary+SplitChunks, ДО волн (значит до первой Reserve)
internal/pipeline/status.go:682 гейт в Redrive — ДО деструктивного ResetChunkStages
internal/pipeline/status.go:485 ProjectedBookUSD вынесен в общий projectBookUSD (одно определение на порог и на status)
internal/config/book.go:100 rebill_consent_usd — переопределение порога книгой (0 = ратифицированный дефолт)
cmd/tmctl/invocation.go --accept-rebill[=usd] через flag.Value+IsBoolFlag
cmd/tmctl/main.go проводка в translate и redrive

Формула (ратифицированная, D20.2-Q2): порог = min($0.50, 5%×ProjectedBookUSD), при ProjectedBookUSD = 0 — абсолютный флор $0.50. Книга может переопределить (rebill_consent_usd).

Проекция (снапшотная гранулярность): Σ chunk_status.cost_usd по строкам, которые (а) несут снапшот, (б) были ОПЛАЧЕНЫ (ok/flagged; skipped не доезжал до провайдера), (в) чей снапшот ≠ текущему снапшоту ЕГО ВОЛНЫ, (г) чьи стадия и позиция ещё существуют.

«РЕШИ САМ И АРГУМЕНТИРУЙ» — мои решения

1. Где живёт вычисление. Прайор промта принят: рядом со status-проекцией, но с уточнением — ProjectedBookUSD вынесен из Status в общую функцию projectBookUSD (rebill.go), которую теперь зовут ОБЕ стороны. Причина: порог = 5% от этого числа; порог, посчитанный от второй, дрейфующей копии той же величины — ровно тот класс, ради которого в паке-16 выносили memberDrops. Вынос доказан байт-нейтральным исполнением (golden и status-тесты зелёные без правок).

2. Форма сообщения об отказе. Несёт: сумму (6 знаков), число единиц (chunk×stage), порог И его происхождение (5% от проекции $X / флор $0.50 / book.rebill_consent_usd), строку «NOTHING was reserved and no row was touched», и точную команду-лекарство. Отдельная форма для превышенного потолка (--accept-rebill=1.50 при проекции $2.41). Когда --resnapshot не передан, добавляется хвост «run also needs --resnapshot» — оператор получает ОБА флага сразу.

3. Как флаг ложится на translate и redrive — ЭТО РАЗВИЛКА, РЕШЁННАЯ МНОЮ, ОТМЕТЬТЕ ПРИ ЛЕНДИНГЕ. Спека §7.3 говорит: «--resnapshot остаётся алиасом-синонимом (семантика — прими проецируемый re-bill)». Я это НЕ перенёс. Флаги сделаны ортогональными: --resnapshot = разрешение пере-пиннить, --accept-rebill = согласие на СУММУ; сверх порога нужны оба. Аргументы:

  • оговорка спеки живёт в §7 «Что демонтируется» — в мире, где fail-loud снапшот-гейт УДАЛЁН и --resnapshot иначе теряет смысл. Гейт не демонтирован (guard_hash не строим), поэтому у --resnapshot осталось своё живое значение;
  • при алиас-прочтении ратифицированный порог становится мёртвым кодом: на пути translate --resnapshot требуется в любом случае и сам же был бы согласием — порогу нечего гейтить. Ортогональность — единственное прочтение, при котором min($0.50, 5%×…) вообще что-то делает;
  • Р6 = согласие на КОНКРЕТНУЮ трату. Флаг, чьё имя и help-строка не упоминают денег, её не несёт; а долг сформулирован в трекере ровно так: «--resnapshot пере-пиннит без суммы»;
  • направление строго безопасное: ни один прогон, который раньше отказывал, теперь не проходит; часть прогонов, которые раньше проходили, теперь останавливаются с напечатанной суммой.

Цена решения (честно): 4 существующих теста, гонявших --resnapshot в одиночку, получили строку AcceptRebill = RebillConsent{Given: true} (перечислены ниже). Это и есть смена контракта.

4. Проекция ниже порога. Прайор промта принят — продолжаем. Но не молча-невидимо: сумма и число единиц уходят в лог (Info, «continuing without asking»). Деньги обязаны быть видны, даже когда не спрашивают.

5. Потолок ниже порога. Именованный потолок honoured всегда, даже когда проекция ниже порога: --accept-rebill=0.001 — это инструкция, а не форма согласия. Оператор, написавший «не больше $X», не должен быть оплачен на $X+ε на том основании, что сумма была мелкой.

6. Ловушка stdlib (найдена исполнением, а не чтением). IsBoolFlag — единственный способ дать флагу опциональное значение, но тогда --accept-rebill 1.50 (через пробел) не связывает сумму: флаг читается голым, 1.50 становится бесхозным аргументом, и потолок молча превращается в безлимитное согласие. Это отказ с показом правильного написания. Отказ прицельный (только при голом флаге + бесхозный аргумент) — исторически терпимый бесхозный аргумент везде ещё терпим (запинено тестом).

Граница скоупа — фиксирую строками, как требует промт

  • Каскад §7.1-бис на снапшотной гранулярности ВЫРОЖДЕН. Пере-оплачивается вся волна целиком, поэтому правило «стадия строго ниже re-bill-единицы сама считается re-bill» не добавляет ни одной единицы. Ратифицированный порядок «каскад ложится ПЕРЕД семантикой флага» не нарушен — он неприменим.
  • Ветка EstimateUSD из §7.2 не построена, и у неё здесь нет предмета. Она существует для каскадных downstream-единиц без прошлой цены; на снапшотной гранулярности каждая учтённая единица ИМЕЕТ хранимую цену — именно за то, что была оплачена. Оплаченная строка с cost_usd = 0 — это честно $0-модель (local/нулевой прайс), и её вклад $0 верен, а не пробел. Строить мёртвую машинерию (least mechanism) не стал.
  • Развилки «снапшотной гранулярности недостаточно» не возникло — пинг не потребовался. Проверено по коду: под --resnapshot fast-path chunk_status требует cs.SnapshotID == snapID (stagerun.go:86), промах ⇒ attempt-цикл ⇒ RequestHash несёт SnapshotID ⇒ промах чекпоинта ⇒ свежий платный вызов. То есть каждая оплаченная строка под чужим снапшотом действительно пере-оплачивается — сумма честна, а не консервативна.
  • НЕ покрыто (документировано, не умолчано): КОНТЕНТНАЯ ось. Правка исходника, не сдвинувшая манифест, меняет content_hash чанка, но не снапшот — она пере-оплатит ровно тронутые чанки и в проекции невидима. Это та же оговорка, что несёт status.bookChunks (status.go:169-175), и половина content-addressed resume v3, а не этого гейта.

Дефект, найденный МОИМ ревью собственного кода (и починенный)

Первая редакция считала строки-СИРОТЫ: позиции, исчезнувшие из манифеста (исходник укоротили) и стадии, которых в пайплайне больше нет. Их никто не пере-запустит ⇒ проекция завышала сумму, под которой просят подпись. Завышение — безопасное направление, но число, которое просят одобрить и потом не тратят, — это ровно то, отчего такие числа перестают читать. Обе сироты исключены, обе запинены тестами (TestRebillProjectionIgnoresVanishedChunks, …IgnoresRetiredStages) и мутациями M16/M17.


ПУНКТ 2 — escalation_model = модель, чей выход АВТОРИТЕТЕН. ПОСТРОЕН

Прайор промта принят целиком, альтернативы не приношу — аргумент «терять идентичность отказавшего хопа нельзя» проверен и не подтвердился: идентичность НЕ теряется. Отказавший хоп сохраняет (а) собственный durable-чекпоинт под своим request_hash на модели хопа, (б) собственную строку request_log с model_requested=fake-fallback и degraded=hard_refusal. Проверено исполнением: в golden секция request_log при пере-капчере не изменилась ни одной строкой, а тест TestRunnerEscalationFallbackAlsoFailsFlags теперь явно считает эту строку.

Правка: stagerun.go:158-193escalated = true выставляется при любом состоявшемся хопе (деньги/телеметрия), escModel — ТОЛЬКО когда выход хопа стал авторитетным (last = esc.fb, т.е. хоп прошёл ре-гейт или дал косметический sanitizer_stripped, который отгружается).

Миграции нет, старые строки не переписываются. Смешанная семантика в исторических данных допустима и записана в durable-месте, где её увидит следующий читатель: доккоммент поля store.ChunkStatus.EscalationModel (internal/store/chunkstatus.go:35-43) несёт и новую семантику, и абзац «HISTORICAL MIXTURE (pack-18, deliberate — no migration)».

В tmctl report баннер НЕ добавлял — аргумент. escalation_model не печатается ни одной поверхностью CLI (проверено грепом: потребители — только stagerun/resume/store/golden; render.go печатает Escalations: N, а не модель). Отличить старую строку от новой в данных нечем — маркера нет. Значит баннер был бы безусловным и неактивируемым текстом, который ничего не говорит оператору о конкретной строке. Место факта — схема (сделано) и D-лог.

Пере-капчер golden — дисциплина приёмки выполнена

TM_UPDATE_GOLDEN=1 go test ./internal/pipeline/ -run TestGolden

Маскированный структурный дифф (Ш-1), напечатанный ДО записи:

golden masked-diff (Ш-1): 8 verdict/wire change line(s), 0 version-only line(s)
  ~   stage=draft … disp=flagged flag="hard_refusal" … escalated=true esc_model="" finish="refusal" …
  ~ ch3/0 draft … disp=flagged flag="hard_refusal" … escalated=true esc_model="" detail="provider finish_reason=refusal"
  ~   stage=draft … resume=true … escalated=true esc_model="" finish="" …
  ~ ch3/0 draft … escalated=true esc_model="" detail="provider finish_reason=refusal"

Сырой дифф файла: 4 удалённых / 4 добавленных строки, все на ch3/0 draft — единственном хопе фикстуры, который ОТКАЗАЛ. Проверено машинно (не глазами): скрипт заменил esc_model="…" на плейсхолдер в обеих сторонах каждой пары и сравнил — only esc_model columns moved: True. Ни одной другой сдвинувшейся строки ⇒ стоп-условие промта не сработало.

Ни одна ось RequestHash/снапшота не двинулась — доказано исполнением:

snapshot_draft:      HEAD vs worktree — IDENTICAL
snapshot_edit:       HEAD vs worktree — IDENTICAL
brief_hash:          IDENTICAL
memory_version:      IDENTICAL
base_memory_version: IDENTICAL

плюс в самих изменённых строках content_hash/final_hash/cost побайтно те же; секции request_log, retrieval_state и wire bodies в диффе отсутствуют вовсе. Пере-оплаты нет.

Golden: f7641993…4f0020f8211c9013f5cfa31562857cc9518488393a647cce489d37113402b159089045d6. testdata/ в остальном чист (git status показывает только capture.golden).


ПУНКТ 3 — два нита общности. ОТЛОЖЕНЫ (байт-нейтральной формы не существует)

Промт: «берутся ТОЛЬКО в байт-нейтральной форме… Если не выходит — отложи и скажи явно с причиной; это принятый исход». Не вышло. Причина измерена, а не предположена.

Замер (throwaway-проба в песочнице, репозиторий не тронут)

Собрал книгу с langpack_root (как боевые стендовые), снял оба волновых снапшота, дописал в копию configs/langpacks/zh-ru/dc-checkers.txt одну строку (pattern\tshichen_hours\t2), пересобрал:

PROBE pack.Version():   langpack-v2-a7d4be2c0465 → langpack-v2-133d71e8d132   moved=true
PROBE snapshot_draft:   c4e204b020bbc550437aa567 → a234e731ebabb78ec12dc6d1   moved=true
PROBE snapshot_edit:    84abf42f15c9df7f0cf22a2b → 7c712192ada86df74a0b3b7e   moved=true

a7d4be2c0465 — та самая версия пака, что записана в PROGRESS за пак-16. Двигаются ОБЕ волны (LangpackVersion фолдится в общем билдере buildSnapshotID, snapshot.go:428, который зовут обе).

Кого это убивает: стендовые книги ссылаются langpack_root ровно на этот каталог репозитория — /home/ubuntu/books/gu-zhenren/rerun2/book.yaml:23, book-dspro.yaml:22, book-mistral.yaml:22. То есть любая правка данных пары стирает $0-резюм ДРАФТА, на котором должен поехать замер остатка пака-16.

Почему закрыты и остальные двери

Дом данных Итог
langpack (dc-checkers.txt) версия = sha256 БАЙТОВ файла ⇒ любая новая строка двигает оба снапшота. Замерено выше.
пар-слой configs/pairs/<пара>.yaml, фолдится новое поле с значением ⇒ payload снапшота меняется ⇒ тот же класс.
пар-слой, НЕ фолдится тихо снимает версионирование с правила, которое CheapGateVersion версионирует БЕЗУСЛОВНО (snapshot.go:432; checkers.go:27 прямо: «Version rides the langpack Version() (data) + CheapGateVersion (algorithm)»). Это вторая половина запрета промта: «молча НЕ бампить, поменяв версионируемое правило, — тоже [нельзя]».
пар-слой, фолдится через omitempty, zh-ru НЕ шлёт ключ байт-нейтрально — но тогда «2» остаётся в Go как молчаливый дефолт для любой пары, что шлёт DC1-паттерны без коэффициента. Утечка не устранена, а раздвоена на два дома — против принципа, который этот же файл декларирует про hourWordRE («how this target renders the double-hour is one fact with one home», checkers.go:53-56).

Для Detail-строк ограничение ещё жёстче и является определительным: байт-нейтральность ТРЕБУЕТ, чтобы дефолт шаблона рендерил ровно текущую строку — то есть 时辰-литерал обязан остаться в Go дословно. Нит про Detail-строки байт-нейтрально нереализуем в принципе, а не по обстоятельствам.

Ничего в internal/checks/ не тронуто: CheapGateVersion и pack.Version() не двигались, golden не трогался этим пунктом, парити EXACT воспроизведена.

Готовый план на момент, когда снапшот и так поедет (одним шагом, ~час)

Пять Go-сайтов, все в двух файлах (перечислены грепом, не памятью):

  • множитель: checkers.go:174 (expectedHours := n * 2), repair.go:124 (ruNum == n*2), repair.go:138 (~%d h, n*2), repair.go:392 (newNum == oldNum*2);
  • 时辰-текст: checkers.go:153, checkers.go:176, repair.go:103, repair.go:138.

Форма: новые ключи в configs/langpacks/<пара>/dc-checkers.txtunit_hours (коэффициент) и dc1_detail / dc1_fractional_detail (шаблоны с подстановками {n}/{got}/{want}/{word}, без англ. диагностики в теле). Fail-loud, а не дефолт: пара, что шлёт shichen_re без unit_hours, обязана падать на загрузке — иначе вторая пара молча получит «2». Версионирование приезжает даром: байты файла уже в pack.Version().

Два кандидатных слота (решение — оркестратору/владельцу): (а) вместе с включением платной петли ремонта (D39.24) — она и так двигает снапшот шиппинг-волны, но НЕ драфтовой, так что правка пака добавит пере-оплату драфта; (б) на ближайшем полном --resnapshot стендовых книг — тогда бесплатно целиком. Слот (б) чище.


Самопроверка ИСПОЛНЕНИЕМ (мандат владельца 12.07)

Манифест приёмки — заявление = команда (каждая ре-ранится)

cd backend
go build ./...                    # ok
go vet ./...                      # ok
gofmt -l .                        # internal/llm/llm.go — ВОСПРОИЗВОДИТСЯ НА HEAD (git show HEAD:… | gofmt -l), сессия файл не трогала
go test ./... -race               # 12 пакетов ok, 0 FAIL
go test ./... -shuffle=on         # 12 пакетов ok, 0 FAIL
sha256sum internal/pipeline/testdata/golden/capture.golden
#   211c9013f5cfa31562857cc9518488393a647cce489d37113402b159089045d6
TM_MINER_PARITY=1 go test ./internal/miner/ -run TestMinerFullBookParity -v
#   PARITY: n=13618 catastrophe{方源:0 蛊:1 蛊师:2 古月:22} recall@proposed=0.9655 (56/58 GT)  — тождественно пакам 15/16/17
grep -rh "^func Test" --include=*_test.go . | sed 's/(.*//' | sort   # ценз ДИФФОМ ИМЁН

Ценз тестов исполнением (диффом имён, не счётчиком): 455 → 475, удалено 0. Добавлено 20: 15 в internal/pipeline (rebill_test.go ×13, runner_test.go ×1 новый + 1 усиленный), 4 в cmd/tmctl (invocation_test.go ×3, rebill_cli_test.go ×2).

Изменённые существующие тесты (не удалённые — по строке AcceptRebill каждый)

TestRunnerSnapshotPinning · TestRunnerMemoryResnapshotOnApprovedChange · TestRedriveResnapshotAcceptsDrift · TestWaveMinedSignDoesNotRebillDraft — все четыре гоняли --resnapshot в одиночку и теперь несут явное согласие. Это прямое следствие решения №3 выше и единственное место, где пак сменил поведение существующих сценариев. TestRunnerEscalationFallbackAlsoFailsFlags дополнен тремя ассертами (не переписан).

Сквозные сценарии через НАСТОЯЩИЙ драйвер (норма 11)

Согласие сверх порога · согласие под порогом (через rebill_consent_usd) · отказ без флага · --accept-rebill=<сумма> ниже проекции и выше неё · хоп ответил · хоп отказал · read-only живы · redrive до сброса · redrive --dry-run · пер-волновая проекция на mined-sign · $0-резюм не спрашивает. Все — через TranslateBook/Redrive/translate()/redrive() над мок-провайдером; отказные сценарии проверяют не только текст ошибки, но число вызовов провайдера, committed/reserved и неизменность durable-строк.

Мутационный рубеж: 19 мутаций в КОПИИ дерева, 19 красных, 0 выживших

# Мутация Красный тест
M1 гейт отключён целиком RefusesOverThreshold, Cap, ReadOnlyPathsAlive
M2 skipped-строки считаются ProjectionExcludesSkipped
M3a edit-строки судятся по драфт-снапшоту ProjectionIsPerWave, IgnoresRetiredStages
M3b draft-строки судятся по edit-снапшоту ProjectionIsPerWave, ExcludesSkipped, IgnoresRetired
M4 потолок игнорируется CapBelowProjectionRefuses
M5a порог minmax ThresholdFormula
M5b флор $0.50 снят при нулевой проекции ThresholdFormula
M5c rebill_consent_usd игнорируется UnderThreshold, ThresholdFormula
M6 согласие в redrive ПОСЛЕ сброса RedriveRefusesBeforeDestructiveReset
M7a экстраполяция убрана ProjectBookUSDExtrapolates
M7b знаменатель processedtotal ProjectBookUSDExtrapolates
M7c flagged-единицы вне базы TestStatusAndRedrive
M8 read-only status зовёт гейт ReadOnlyPathsAlive
M9 гард пробельной формы снят ParseAcceptRebillSpaceFormRefused
M10 отрицательный потолок принимается ParseAcceptRebillRejectsBadCeiling
M11a согласие не проводится в раннер (translate) TranslateWiresAcceptRebill
M11b потолок теряется при проводке TranslateWiresAcceptRebill
M12 сравнение с потолком ослаблено CapBelowProjectionRefuses
M13 согласие не проводится (redrive) RedriveWiresAcceptRebill
M14 esc_model обратно = ПОПРОБОВАННАЯ GoldenDeterminism + EscalationFallbackAlsoFails
M15 esc_model не ставится никогда Golden + FixesEcho + SurvivesResume
M16 строки исчезнувших чанков снова считаются ProjectionIgnoresVanishedChunks
M17 live-множество инвертировано RefusesOverThreshold, Cap, IsPerWave

Две мутации СНАЧАЛА выжили и вскрыли дыры в моих же тестахобе закрыты, а не списаны:

  • M3 (пер-волновая ось) — первая формулировка мутации была ненаблюдаемой; переформулировал в обе стороны и обнаружил, что «чистая книга не проецирует пере-оплату» не ассертилось нигде. Добавил этот ассерт в TestRebillProjectionIsPerWave — теперь обе стороны красные.
  • M11 (проводка CLI) — ни один тест не проверял, что распарсенное согласие ДОЕЗЖАЕТ до раннера. Это буквально регресс-класс D20.4 («--resnapshot парсился и молча игнорировался»), который в этом репозитории уже случался. Написан cmd/tmctl/rebill_cli_test.go, гоняющий настоящие translate()/redrive() над мок-провайдером.

Что осталось / вопросы оркестратору

  1. Ратифицировать девиацию от алиас-оговорки §7.3 (решение №3 выше): --resnapshot и --accept-rebill ортогональны. Если оркестратор захочет алиас-семантику — это одна строка в checkRebillConsent, но тогда ратифицированный порог становится мёртвым.
  2. Проекция в tmctl status НЕ добавлена — сознательно (скоуп). Спека §9 предлагает заменить булев ConfigDrift числом «N чанков, ~$X»; на снапшотной гранулярности это дёшево и omitempty оставило бы JSON байт-идентичным для недрейфующей книги. Не строил без пинга. Сегодня сумму узнают из отказа translate ($0, без резерваций).
  3. Пункт 3 — слот для лендинга (см. выше, кандидат (б): ближайший полный --resnapshot стенда).
  4. Долг ru-target (слой 7) не тронут — ждёт своего дизайн-пака, как предписано.
  5. Чужая зона: за время сессии параллельная сессия тронула docs/architecture/05-decisions-log.md, docs/experiments/*, eval/README.md и создала docs/archive/reports/POLYGON_TOS_LABELS_REPORT_2026-07-25.md. Не трогал ничего из перечисленного. Мои файлы — только backend/** + этот отчёт.