textmachine/backend/docs/DISCLOSURE_LAW_DESIGN.md

73 KiB
Raw Blame History

Закон раскрытия — дизайн на ратификацию (фаза 1 пака «ДЕНЬГИ И ЧЕСТНОСТЬ ВЫДАЧИ»)

СТАТУС: РАТИФИЦИРОВАН ВЛАДЕЛЬЦЕМ 31.08 — нота D39.181. Норма зоны. Ратифицированы три вещи: сам закон · поля леджера доставки в кадре finished (A5) · config_drift_basis в status --json (A7/A12). Все десять экземпляров корпуса ПРИМЕНЕНЫ — разбор по пунктам в MONEY_HONESTY_REPORT.md §9. ⚠ Оговорка к Ст. 3 (дешевизна полосы 1019) дописана ПОСЛЕ ратификации по находке оркестратора и стоит ниже; она сужает полномочие, а не расширяет его. ⚠ Честная цена, названная при ратификации: закон загейчен примерно на 40% — см. §7.

Написан бэкенд-сессией textmachine-main-8a (промт — docs/archive/prompts/BACKEND_MONEY_HONESTY_SESSION_PROMPT.md §1), ратифицирован владельцем через оркестратора №21. ⚠ Каждый факт ниже снят ИСПОЛНЕНИЕМ на HEAD 9d27c0b, командой, названной рядом.


0. Что это и чего это не

Это — правило вида «факт КЛАССА X, вычисленный движком, обязан достичь потребителя Y по каналу Z», плюс механический гейт на его нарушение.

Это НЕ — каталог полей, не расширение шва и не новое слово ни в одном ратифицированном словаре. Там, где закон ТРЕБУЕТ расширения, он это называет отдельным пунктом на ратификацию (§7).

Проверка качества — на корпусе: закон обязан объяснить все десять строк таблицы промта и назвать канал каждой. §5 это делает. §6 предъявляет ОДИННАДЦАТЫЙ экземпляр, которого в таблице нет.


1. Болезнь — одним предложением, и почему она не десять багов

Движок вычисляет факт и не доносит его до того, чьё решение от этого факта зависит — молча, ложью о причине, или в канал, который этот потребитель не читает.

Десять экземпляров — не десять багов, а один отсутствующий закон: у движка нет дисциплины «что потребитель обязан узнать и по какому каналу». Чинить поштучно значит заказать одиннадцатый.

И закон не импортируется извне — он уже наполовину написан В ЭТОМ КОДЕ, просто применён точечно. Две нормы живут в комментариях и не имеют ни имени, ни гейта:

  1. «unknown, not zero»internal/pipeline/quality.go:321=the UNSIGNED BANK count is unknown, not zero, internal/pipeline/status.go:839=the unsigned-term count is unknown, not zero, internal/pipeline/status.go:922=reported as unknown, not as none, internal/pipeline/bookbuild.go:231=(reported as unknown, not as none).
  2. Фигура едет С БАЗИСОМRebillBasis (internal/pipeline/status.go:293=RebillBasis string), четыре значения pending|stored|none|failed (internal/pipeline/bankmaterialize.go:364-367), и её докстринг формулирует ровно закон: «RebillBasis says WHAT the two figures above are a projection of, because the number alone cannot carry that and a reader must never have to guess … failed — the figures are zero because they are UNKNOWN.»

§2.3 закона ниже есть ОБОБЩЕНИЕ RebillBasis на все опубликованные фигуры и флаги — на этой одной статье приёмке проще всего: она не вводит новой философии, а даёт имя и гейт тому, что зона уже признала правильным на одном поле и не распространила на остальные.

И не больше того — разделение своего и заимствованного (ратифицировано, D39.181 §2). У §2.1 и §2.2 родословная другая — норма «unknown, not zero» и дисциплина причин; §2.4 и §2.5 родословной в этом коде НЕ имеют вовсе, это НОВЫЕ нормы, и приёмка обязана судить их как новые. ⚠ И сам RebillBasis богаче §2.3: он ЧЕТЫРЁХзначен, и три его значения (pending/stored/none) означают «факт известен», различая ИСТОЧНИК проекции, — то есть §2.3 берёт от него только ось «известно/не установлено» и не воспроизводит ось источника.


2. Потребители — перечень снят ГРЕПОМ, а не принят с промта

Промт (§1) велел проверить перечень самой: «я его уже один раз назвал неверно». Проверила.

потребитель что решает что ЧИТАЕТ (проверено)
Оператор править ли ВХОД: конфиг, промпт, исходник, ключи, потолки stdout/stderr (log/slog), вывод translate, report, build
Платформа в какое состояние перевести прогон · сколько списать · респавнить ли РОВНО пять каналов, §3
Полигон / замер доверять ли этому экспорту как измерению tmctl export --json (контракт слоя 6, backend/README.md инвариант 8)
Владелец-покупатель покупать ли дальше через платформу; сумм на экране не видит (D39.84)

Оператор и платформа — НЕ один потребитель с разной вежливостью. Они читают физически разные байты, и ни один канал не общий: платформа не читает прозу движка нигде. Единственное касание — ПЕРВАЯ СТРОКА stderr, вклеиваемая в текст ошибки исполнителя (platform/internal/runner/engine.go, греп firstLine(errOut.Bytes())), и это диагностика для человека, а не канал факта.


3. Каналы — замкнутая таблица, снята с кода ОБЕИХ зон

3.1 Что платформа декодирует (platform/internal/, read-only)

канал тип на приёме что реально берётся
код выхода ingest.OutcomeOf (platform/internal/ingest/exit.go) 0·1·2·3·4·5 + ПОЛОСА 1019; неизвестный код → OutcomeFailed
status --json ingest.StatusReport (platform/internal/ingest/resync.go:20) аллоулист 12 ключей: book_id · total_units · done · in_progress · flagged · pending · progress{draft,edit} · eta_seconds · unsigned_bank_terms · committed_usd · reserved_usd · chapters[]. ⚠ И УЖЕ: продакшн-читателя имеют ПЯТЬflagged (только как подтверждение кода 2) · progress.draft.total + progress.edit.total · eta_seconds · committed_usd · reserved_usd. Остальные декодируются и не читаются никем вне тестов
manifest --json ingest.Manifest дерево глав/юнитов + artifacts с ОДНИМ полемbank_export (platform/internal/ingest/manifest.go:53-57)
export --json --pairs ingest.Export пары для читателя
events.jsonl 7 типов кадров hello·progress·unit_done·bank_stop·ceiling·spend·finished
bank-apply stdout ingest.BankReport решения по термам

Чего платформа НЕ берёт, хотя движок это печатает (снято сверкой двух структур поле-в-поле): config_drift · snapshot_drift · current_snapshot · snapshot_id · rebill_units · rebill_usd · rebill_output_units · rebill_basis · projected_book_usd · book_ceiling_usd · ceiling_pct · stages_skipped · escalations · postcheck_misses · style_flags · content_labels · routing · artifacts.book_files · artifacts.project_db · artifacts.mined_delta · artifacts.mined_rejects · chapters[].worst_flag_reason · chapters[].cost_usd · chapters[].verdict.

Два следствия, которые меняют диспозиции пака, и оба против промта:

  • config_drift платформе НЕ ВИДЕН. ⇒ A7 — факт ОПЕРАТОРА и ПОЛИГОНА, не платформы.
  • artifacts.book_files платформа НЕ ДЕКОДИРУЕТ ВООБЩЕ. У неё StatusArtifacts из одного поля. ⇒ A3(б) как «платформа обманута» не существует (разбор — §5).

3.2 Что движок вообще умеет сказать (backend/)

Пять родов каналов и больше никаких: код выхода · stdout-документы · файлы-артефакты рядом с БД · stderr-логи log/slog · ничего больше.

3.3 Словари СВАРЕНЫ — и это решает A5

platform/internal/ingest/exit.go дословно: «Outcome is the engine's own verdict about a run, and it is deliberately readable from EITHER channel: it is what the terminal finished line carries and what OutcomeOf reads off an exit code. The two are one vocabulary because they are one fact travelling twice.» Значения помечены номерами: clean=0 · failed=1 · flagged=2 · bank_stop=3 · ceiling=4 · stopped=5.

Довод «пара БИЕКТИВНА» НЕВЕРЕН и не годится (D39.181 п.2 стоит на другом): OutcomeRefused уже существует и counterpart'а в потоке НЕ имеет — та же константа, дословно: «OutcomeRefused is the band, and it has no finished counterpart: an invocation that was turned down did no work, so the engine writes no verdict about work». Асимметрия уже узаконена, и аргумент «так нельзя, потому что нельзя» падает.

Точный аргумент — другой. Асимметрия сегодня односторонняя: у КАЖДОГО значения finished.outcome есть код-двойник, а у кодовой стороны есть одно лишнее. Значение, живущее ТОЛЬКО в потоке, было бы первым в обратную сторону — и тогда для ОДНОГО прогона два канала назвали бы РАЗНЫЙ исход: поток сказал бы volume, а OutcomeOf(0) на том же прогоне — clean. Это ровно то, что запрещает сама константа («one fact travelling twice»): не асимметрия, а РАСХОЖДЕНИЕ.

Признак остановки по объёму не может быть словом ни в одном из двух словарей — не потому, что словарь заперт, а потому, что слово в одном канале сделало бы каналы противоречащими. Он обязан быть ЧИСЛОМ в поле: у чисел словаря нет, и расходиться нечему.


4. ЗАКОН — четыре статьи

Ст. 1. Что такое подотчётный факт

Факт ПОДОТЧЁТЕН, если выполнены оба условия:

  • (а) движок его ЗНАЕТ — значение лежит в памяти процесса в момент решения, ЛИБО выводится за $0 из строк, которые движок уже записал (леджер · chunk_status · checkpoints · snapshots · events_outbox);
  • (б) есть потребитель, чьё СЛЕДУЮЩЕЕ ДЕЙСТВИЕ от него меняется (§2).

Условие (б) — это и есть ограничитель: закон не требует печатать всё, что известно. Он требует печатать то, без чего кто-то примет другое решение.

Ст. 2. Четыре обязанности. Ни одна не заменяет другую

§2.1 — НЕ МОЛЧАТЬ. Подотчётный факт обязан быть на канале своего потребителя. Экземпляры: A0 · A3б · A5 · A6 · A10.

§2.2 — НЕ НАЗЫВАТЬ ПРИЧИНУ, КОТОРУЮ НЕ СРАВНИВАЛ. Формально: сообщение, утверждающее причину C, обязано производиться на площадке, которая держит ОБА операнда сравнения, устанавливающего C, и обязано их напечатать. Причина, не сравнённая на месте, не называется вовсе — «снапшот разошёлся» честно, «конфиг/промпты изменились» ложно. Экземпляры: A4.

§2.3 — ФИГУРА ЕДЕТ С БАЗИСОМ. (Обобщение RebillBasis.) Опубликованная фигура или флаг, чьё значение может быть произведено И самим фактом, И неудачей установить факт, обязаны нести поле-БАЗИС, различающее эти два случая. Ноль, который значит и «нет», и «не знаю», — ложь по построению, и стоит она денег: false в config_drift читается как «строки актуальны». Экземпляры: A7 · A2 · A12 (одиннадцатый, §6).

§2.4 — КАНАЛ НАЗНАЧАЕТ ПОТРЕБИТЕЛЬ, А НЕ УДОБСТВО ПИШУЩЕГО.Отношение к §2.1 названо, а не оставлено читателю: §2.4 — не независимая обязанность, а ПРОЦЕДУРА исполнения §2.1. §2.1 говорит «факт обязан быть на канале своего потребителя»; §2.4 говорит, КАКОЙ это канал и как его выбрать. Строка корпуса нарушает §2.1 всегда, когда нарушает §2.4; отдельный номер у §2.4 есть потому, что нарушают его по-разному — молчанием (§2.1) или громкой публикацией не туда (§2.4), — и лечатся они разным. Перечни «экземпляров» под §2.1 и §2.4 поэтому ПЕРЕСЕКАЮТСЯ намеренно, и §5 называет для каждой строки ту, которая ведёт к правке.

  • Проза (stdout/stderr, секция report) — канал ОПЕРАТОРА и только его.
  • Факт, нужный платформе, обязан ехать в канале, который platform/internal/ingest ДЕКОДИРУЕТ (§3.1). «Признак есть в отчёте прогона» задачу не решает — отчёт платформе не виден.
  • Факт, нужный замеру, обязан ехать в export --json.
  • Факт, нужный ДВОИМ, едет ДВАЖДЫ, и обе копии обязаны производиться из ОДНОГО вычисления. Экземпляры: A5 · A7 · A8.

§2.5 — НЕОБРАТИМОЕ ОТЧИТЫВАЕТСЯ РАНЬШЕ ПОСЛЕДУЮЩЕЙ ОШИБКИ. Совершив необратимое — потратив деньги, записав файл, удалив файл, — движок обязан сначала выдать отчёт об этом и только потом сообщать об ошибке, случившейся позже на том же пути. Ошибка ПОСЛЕ необратимого действия не смеет ехать маршрутом, который отчёт подавляет. Экземпляры: A3а.

Ст. 3. Расширение каналов — ЧЕТЫРЕ класса, и они РАЗНОЙ цены

класс что это кто вправе
НОВОЕ ЧИСЛО в существующей форме ещё одна строка разложения в report, ещё один атрибут лога сессия зоны
НОВОЕ ПОЛЕ в опубликованном документе поле в status --json / кадре потока; аддитивно и обратно-совместимо (encoding/json игнорирует незнакомые), но это КОНТРАКТ ШВА ратификация ОРКЕСТРАТОРА
НОВОЕ СЛОВО в замкнутом словаре значение Outcome · тип кадра · значение flag_reason, пересекающее шов · код выхода ВНЕ полосы (05) ратификация ВЛАДЕЛЬЦА
НОВЫЙ КЛАСС ОТКАЗА ВНУТРИ полосы 1019 новая константа в refusalExit + pipeline.RefusalClass; незнакомый номер у потребителя поглощается ЧЛЕНСТВОМ в полосе и читается «refused», а не «failed» — полоса спроектирована ровно под это ратификация ОРКЕСТРАТОРА (прецедент — код 13)

Коды выхода НЕ валятся в один класс «слово владельца» — ПОЛОСА растёт иначе: platform/internal/ingest/exit.go дословно — «It is a BAND and not a list because the vocabulary is the engine's and it will grow. A consumer looks the number up; a class this build has never heard of still lands inside the band and reads as "refused"», а 17-seam-inbound-law.md называет прецедент: код 13 встал в полосу «без нового решения ВЛАДЕЛЬЦА», через ратификации ОРКЕСТРАТОРА (D39.131 п.2а → D39.132 п.2г → D39.134). Полоса спроектирована расти; заморожена верхняя шестёрка 05. ⚠ Для A2 это ничего не меняет: полоса значит «ничего не потрачено», а фаза идёт после оплаченной черновой волны.

ОГОВОРКА К ДЕШЕВИЗНЕ ПОЛОСЫ — она из находки A15 (§6), и без неё закон разрешает удешевлённо делать то, что сам же называет опасным.

Довод, которым полоса удешевлена, — «незнакомый номер поглощается ЧЛЕНСТВОМ и читается refused, а не failed». Это верно по оси НОМЕРА и только по ней. A15 показала, что разошёлся не номер, а ОБЕЩАНИЕ полосы: движок снял с неё «ничего не записано», платформа этот пункт держит. Поглощение членством спасает от НЕЗНАКОМОГО ЧИСЛА и не спасает от РАСХОДЯЩЕЙСЯ СЕМАНТИКИ: потребитель применит к новому классу ту гарантию, которую держит ОН, а не ту, которую даёт производитель. По PD-196 он применяет её разрушительно.

Норма: рост полосы дёшев по оси номера и НЕБЕЗОПАСЕН по оси обещания, пока копии словаря расходятся. Пока строка бэклога 246 открыта, новый класс вводится в полосу ТОЛЬКО вместе со сверкой обеих копий (движок cmd/tmctl/main.go ↔ платформа internal/ingest/exit.go), и сверка предъявляется в том же паке. Закроется 246 — оговорка снимается вместе с ней, потому что её предмет исчезнет.

⚠ И общее следствие, которое стоит дороже самой оговорки: дешевизна класса расширения — свойство не СЛОВАРЯ, а его КОПИЙ. Любой словарь, у которого копий больше одной и нет машинной сверки между ними, дёшев только на бумаге. Это ревью-вопрос к каждому будущему расширению, а не разовая заплатка.

Правило выбора: бери самый дешёвый класс, которым факт выражается без лжи. A5 — ровно применение этого правила: слово нельзя, число можно, значит число.

Ст. 4. Закон без гейта гниёт

Каждая статья обязана иметь машинную проверку, краснеющую на СЛЕДУЮЩЕМ экземпляре, а не на уже известных. Дизайн гейта — §7. Статья, для которой гейт не построен, объявляется НЕЗАГЕЙЧЕННОЙ вслух: незагейченная норма — это норма, которую следующая сессия унаследует как факт и нарушит молча.


5. Корпус: все десять строк, разобранные законом

ЧИТАТЬ КАК ДИАГНОЗ ДО ПРАВКИ, А НЕ КАК ОПИСАНИЕ СЕГОДНЯШНЕГО КОДА. Все десять применены (D39.181/D39.182), поэтому каждая фраза «Делает: …» ниже описывает состояние ДО лечения и сегодня ложна как утверждение о коде; что стало — MONEY_HONESTY_REPORT.md §9 (по одному экземпляру, с мутацией и её дословным выводом). Секция оставлена целиком по двум причинам: она и есть проверка качества закона на корпусе (§0), и она несёт названные ЛОВУШКИ и опровержения, которые правки учли и которые переоткрывать не надо.

Формат: что движок ЗНАЕТ (с якорем) → какая статья нарушенапотребительканалкласс правки по ст. 3.

A0 — оплаченный хвост неудач невидим

  • Знает: EscalationSpentUSD (internal/store/ledger.go, греп func (s *Store) EscalationSpentUSD), колонки checkpoints.attempt · checkpoints.escalation · request_log.ok · request_log.estimated.
  • Делает: деньги публикуются ИТОГОМ (committed_usd), разложения «за что заплачено впустую» нет ни в status, ни в report.
  • Нарушено: §2.1.
  • Потребитель: ОПЕРАТОР (правит модель/потолок/промпт). Платформа сегодня — НЕТ: она не берёт даже rebill_*, а суммы на экран запрещены (D39.84).
  • Канал: секция report (класс «новое число» — сессия вправе). Поле status --json — класс «новое поле» ⇒ ратификация, и я его НЕ рекомендую первым шагом: у него нет потребителя.
  • И собственная ловушка среза, замеренная мной на живых деньгах: ok=0 AND cost_usd>0 НЕ ЕСТЬ «оплачено впустую». На coldrun-v16 в этих 11 строках ($0.12316089) лежат три РАЗНЫЕ вещи: degraded=cjk_artifact у роли classifier — 3 строки, $0.00918827, мис-вердикт, вызовы удались. ⚠ Обоснование — КОДОМ, а не строкой бэклога 105: её предпосылка «classify_types выключен во всех шиппинг-конфигах» для ЭТОГО прогона ЛОЖНА — он его включал. Механизм ТОГДА: исключение только терминологу; СЕГОДНЯ — обоим банк-ролям (terminologist.go, isBankRole) — исключение из эхо-правила выдано ТОЛЬКО терминологу, хотя формат ответа классификатора — та же двуязычная таблица терминов, а докстринг самого поля объясняет, чем это кончается: «Left on, every healthy call of that role lands in request_log as ok=0/degraded=cjk_artifact — poisoning the one signal that says a provider is misbehaving». ⚠ И ПРЯМАЯ улика на сами эти три строки, а не только форма кода: их ответы лежат в checkpoints.response_text, читаются read-only и здоровы — разбор с числами и содержимым MONEY_HONESTY_REPORT.md §5.4. ⇒ вызовы удались, вердикт ложен. Чинить это здесь НЕЛЬЗЯ: правка применимости эхо-правила двигает classifierVersion, а он — поле снапшота (snapshot.go, греп ClassifierVersion versions the intrinsic) ⇒ ярус B (и ровно это записано ценой в строке бэклога 105); degraded=sanitizer_stripped у редактора — 1 строка, $0.01865424, текст ОТГРУЖЕН; и лишь $0.09531838 действительно выброшено. Наивная поверхность назвала бы оператору на 29.2% больше потерь, чем было ($0.12316089 против истинных $0.09531838) — то есть сама была бы экземпляром §2.2. ⇒ Разложение обязано печатать не «впустую», а КЛАССЫ: отброшено · отгружено-с-флагом · мис-вердикт.

A2 — фаза стартует, зная, что не влезает

  • Знает: суммарную смету всех батчей ДО первого вызова и бюджет в той же области видимости — internal/pipeline/terminologist.go, греп estimate before any call.
  • Делает: печатает estimate_usd и budget_usd рядом, следующий оператор — проверка ошибки ЧТЕНИЯ бюджета, сравнения нет; обрывается на середине пер-батчевым гейтом, оставив оплаченную частичную работу.
  • Нарушено: §2.3. Частичный результат подаётся как результат, без базиса «пасс оборван бюджетом». ⚠ §2.5 здесь НЕ нарушен. §2.5 — правило ПОРЯДКА ОТЧЁТА, а фаза как раз отчитывается: пер-батчевый гейт печатает WARN, НАЗЫВАЮЩИЙ необратимую трату (spent_usd, next_batch_usd, batches_left), и только потом break — ошибка не возвращается, отчёт не подавляется. Похожее на §2.5 здесь — «решение принято позже, чем могло быть», а это ДРУГОЕ правило, и его в законе нет. Заводить его я не стала: одна строка корпуса — недостаточное основание для пятой обязанности, и честнее оставить дыру названной.
  • Потребитель: оператор (поднять бюджет) + деньги.
  • Канал: существующий — план фазы усекается до влезающего ДО первого вызова, и строка называет усечение. Отказ классом Refusal ЗАПРЕЩЁН и это подтверждено дословно: platform/internal/ingest/exit.go о полосе 1019 — «the invocation was turned down BEFORE it did any work of its own — nothing reached a provider, nothing was spent». Фаза идёт ПОСЛЕ оплаченной черновой волны ⇒ отказ этим классом сделал бы движок лжецом о деньгах.
  • Класс правки: новое число (сессия вправе).

A3(а) — build возвращает exit 1 при уже лежащих файлах

  • Знает: полный корректный *BuildReport в памяти и то, что файлы закоммичены.
  • Делает: сбой уборки незапрошенного формата возвращается ГОЛОЙ ошибкой (internal/pipeline/bookbuild.go, греп remove the previous %s copy) ⇒ маппер даёт default: return 1 (backend/cmd/tmctl/main.go, греп func exitCode), отчёт не печатается. Потребитель по контракту полосы решает «инфра-сбой, файлов нет» и списывает прогон.
  • Нарушено: §2.5 — в чистом виде.
  • Потребитель: оператор + платформа (код выхода).
  • Канал: существующий. Отчёт печатается, сбой уборки становится WARN. ⚠ В том же файле уже стоит честный образец для СОСЕДНЕГО сбояRefusalWriteIncomplete с перечислением landed: (греп the copies were prepared and the write did not complete). Правка — распространить его на уборку.
  • Класс правки: новое число (сессия вправе).

A3(б) — book_files печатает путь удалённого файла

  • ВЕРДИКТ: ОПРОВЕРГНУТО как дефект book_files, и подтверждено как ДРУГОЙ дефект.
  • Опровержение — двумя цитатами, обе снятые исполнением: движок, internal/pipeline/status.go (греп names a PLACE, not a presence): «Every path here names a PLACE, not a presence … Publishing "does it exist" instead would be a fact about a moment that has passed by the time the consumer reads it, and would invite the check-then-open race»; и BookFiles«A PLACE like the others — the map is complete whether or not a build has run, and a consumer opens the path and handles not-found». Платформа, platform/internal/ingest/manifest.go:53-56 — та же фраза о bank_export: «The path names a PLACE, not a presence». Плюс платформа book_files не декодирует вовсе. ⇒ контракт написан, обеими сторонами, с доводом (гонка check-then-open). Обманутого нет.
  • Настоящий дефект в том же месте и он НАСТОЯЩИЙ: build --format epub МОЛЧА удаляет соседний .book.txt. os.Remove, вернувший nil, ЗНАЧИТ «файл был и его больше нет» — движок различает это от «файла не было» (fs.ErrNotExist) и выбрасывает различение, не сказав ни строки.
  • Нарушено: §2.1 (и §2.5 — удаление необратимо).
  • Потребитель: оператор. Канал: строка сборки. Класс: новое число.

A4 — снапшот-гард называет причину, которой не было

  • Знает: ОБА payload'а. snapshots хранит payload TEXT NOT NULL на каждый ID (internal/store/migrate.go, греп CREATE TABLE IF NOT EXISTS snapshots), а buildSnapshotID возвращает (id, payload string). ⇒ пофайловый JSON-диф выводим за $0.
  • Делает: internal/pipeline/stagerun.go (греп the config/prompts changed) утверждает причину, которую не сравнивал.
  • ПРЕДЪЯВЛЕНО НА ЖИВЫХ ДАННЫХ ПРОГОНА (read-only, sqlite3 'file:…?mode=ro'): два edit-снапшота f5258345… и c8d74bbaef… различаются РОВНО ОДНИМ ключом — memory_version; prompt_sha256, model, temperature, reasoning и все прочие побайтно равны. Гард сказал бы «конфиг/промпты изменились» — и это ложь, доказанная его же хранилищем.
  • Нарушено: §2.2.
  • Потребитель — ДВОЕ, не «только оператор» (проверено по реестру платформы). PD-422 дословно: движковый джоб-гард останавливает прогон, и платформа видит exit 1failed — то есть ложную причину читает не только человек, причём резолвится она как «инфра-сбой», а не как «нужен --resnapshot». ⚠ Строка помечена там же как сегодня БЕСПРЕДМЕТНАЯ: проводка --max-units на платформе загейчена, и вторая покупка этой формы пока не возникает.
  • Канал: оператору — текст гарда (stderr, класс «новое число»); платформе — отдельный вопрос, и он НЕ этого пака: её половина уже заведена строкой PD-422 в чужой зоне.
  • Ловушка для исполнителя (проверена по коду): готового дифа полей НЕТ. classifySnapshotMove (repin.go) ходит по КАРТЕ и делает return moveOther на ПЕРВОМ несовпавшем ключе — он не собирает МНОЖЕСТВО разошедшихся полей, а какой ключ встретится первым, у Go-мапы недетерминировано. Диф для сообщения обязан быть НОВЫМ и ДЕТЕРМИНИРОВАННЫМ (сортировка ключей), а не этим циклом.
  • Класс правки: новое число.

A5 — остановка по объёму не имеет машинного носителя

  • Знает: целую структуру VolumeStop{MaxUnits · Delivered · Reworked · Flagged · Free · LeftFresh · LeftRework} (internal/pipeline/volume.go, греп LeftFresh is undelivered units).
  • Делает: публикует её ОДНОЙ ПРОЗАИЧЕСКОЙ СТРОКОЙ. Кода выхода у объёмного стопа нет вовсе, а finished.outcome он получает clean либо flagged — ровно те же, что книга, дочитанная до конца (internal/pipeline/events.go, греп func (e \*emitter) terminal).
  • Нарушено: §2.4 — факт платформы едет прозой.
  • ⚠ ДВЕ ПОПРАВКИ К ПРОМТУ, обе проверены исполнением:
    1. Платформа сегодня --max-units НЕ ШЛЁТ ВООБЩЕ (platform/internal/runner/engine.go, греп func TranslateArgs; во всей зоне platform/ вне комментария этого флага нет). ⇒ A5 — дефект БУДУЩЕГО потребителя, не живой инцидент. Срочность падает, дизайн — нет.
    2. Формулировка «неотличима от «что-то тихо сломалось»» слишком сильна: тихий слом даёт 1 или сигнал, а 0/2 по контракту значат «команда СДЕЛАЛА свою работу» (platform/internal/ingest/exit.go, греп func CompletedWithFlags). Неразличимы другие две вещи: «остановился, потому что грант кончился» и «дочитал книгу до конца» — и вместе с ними теряется ВЕСЬ леджер доставки, включая ось LeftFresh/LeftRework, которая и есть «что ещё можно продать» (родня строки 232).
  • Канал — рекомендация с доводом: ЧИСЛА, а не слово. По §3.3 слово в Outcome разварило бы пару «код выхода ↔ кадр», а кода заводить нельзя. Числа словаря не имеют. Форма: кадр finished получает поля леджера доставки (max_units · delivered · reworked · flagged · free · left_fresh · left_rework). ЛОВУШКА ПОТРЕБИТЕЛЯ (проверена по коду). Напрашивающийся предикат delivered+reworked == max_units && left_fresh+left_rework > 0 ЛОЖЕН ровно на том прогоне, ради которого A5 заведён: reconcile() (volume.go) ПОСЛЕ волн переносит оплаченный, но флагнутый юнит ИЗ Delivered/Reworked в Flagged, а улика A5 — это --max-units 3 с одним флагнутым чанком, где delivered+reworked = 2 ≠ 3. Потребитель прочёл бы объёмный стоп как обычный прогон — ровно та путаница, которую A5 убирает. ⇒ правильный предикат считает ОПЛАЧЕННОЕ вместе с флагнутыму движка это уже есть: VolumeStop.Paid() плюс Flagged, а «сколько осталось» — Left(). То есть поле должно ехать так, чтобы потребителю не приходилось складывать его самому: если поля всё равно новые, честнее послать ГОТОВЫЙ признак числом (max_units и left_*), а не заставлять внешнюю сторону воспроизводить внутреннюю арифметику reconcile. Это и есть окончательная рекомендация.
  • Класс правки: НОВОЕ ПОЛЕ ⇒ ратификация оркестратора. Сессия вносить не вправе — это ПИНГ.

A6 — деньги слепы к правке исходника на месте (строка 238)

  • Знает: chunk_status.ContentHash и умеет пере-рендерить текущий контент-хеш — runStage's resume fast-path сверяет cs.ContentHash == contentHash (internal/pipeline/stagerun.go, греп cs.ContentHash == contentHash).
  • Делает: projectRebill в ветке НЕсдвинутого снапшота выходит по if cs.SnapshotID == cur { continue // resumes at $0 } (internal/pipeline/rebill.go) — без сверки контента. Сверка сделана только в bank-only ветке. ⚠ И докстринг функции при этом утверждает: «The projection now models the resume predicate the run actually applies» — утверждение шире кода, что само есть §2.2.
  • Нарушено: §2.1 (смета молчит) + §2.2 (докстринг).
  • Потребитель: оператор (согласие на пере-оплату) + деньги.
  • Канал: СУЩЕСТВУЮЩИЕ поля — rebill_units/rebill_usd/rebill_basis. Носитель уже есть, и это главный довод: правка не расширяет шов вообще.
  • Класс правки: новое число (сессия вправе, после ратификации закона).

A7 — status не видит исчезнувшую стадию, export видит

  • Знает: обе поверхности читают одни и те же chunk_status.
  • Делает: export.go (греп func (r \*Runner) exportConfigDrift) имеет ДВЕ проверки — пер-волновую и «строка несёт стадию, которой в конфиге нет» (греп stored rows carry a stage the current config does not run). status.go (греп checkWave := func(snaps map\[string\]bool) имеет только ПЕРВУЮ.
  • Нарушено: §2.4 — один факт, две поверхности, разные ответы.
  • Потребитель: ОПЕРАТОР и ПОЛИГОН (он решает по ConfigDrift, доверять ли экспорту как измерению). НЕ платформа — она config_drift не декодирует (§3.1).
  • Канал: существующее поле обеих поверхностей. Правило поднимается в общий хелпер. ⚠ Что поднимать НЕЛЬЗЯ: расхождение в свёртке банка между status и export объявлено СОЗНАТЕЛЬНЫМ (export.go, греп symmetry between two surfaces that answer different questions). Общим делается ТОЛЬКО правило осиротевшей стадии.
  • ЛОВУШКА, из-за которой наивная правка A7 СОЗДАЁТ новый экземпляр §2.2: projectRebill пропускает ровно те строки, на которые сработает новое правило — default: continue // a stage the current pipeline does not run is never re-billed (internal/pipeline/rebill.go). ⇒ поставив config_drift=true, поверхность оставит rebill_units=0, а человеческий рендер статуса превращает этот флаг в денежное утверждение о пере-оплате, которой не будет. Правка A7 обязана идти вместе с базисом (§2.3), иначе она чинит одну ложь, заводя вторую.
  • Класс правки: новое число (сессия вправе) — но НЕ в одиночку, см. ловушку выше.

A8 — prompt_version не отслеживает байты промпта

  • Знает: PromptSHA256 рядом с PromptVersion в payload снапшота, и payload durable в таблице snapshots — я это пере-снял: дамп payload'а печатает prompt_sha256 на каждую стадию.
  • Делает: метку не сверяет ни с чем; и ни status, ни report, ни export prompt_version не публикуют вовсе (grep -rn PromptVersion internal/pipeline/{status,export,quality}.go → пусто). Потребитель метки — ЧЕЛОВЕК, читающий конфиг.
  • Нарушено: §2.3 — метка есть базис без гарантии, и её равенство читается как равенство байтов.
  • Потребитель: оператор/замер (сравнимость прогонов). Не платформа, не деньги (деньги защищены PromptSHA256 в снапшоте).
  • И решающий довод: НОРМА УЖЕ ОБЪЯВЛЕНА И УЖЕ НАРУШЕНА. backend/configs/pipeline-c2.yaml:56 дословно: «лейбл обязан следовать за новым SHA файла». Именно это и произошло 01.08 с prompts/zh-ru/editor.md при неизменном v3-discourse-reflow. ⇒ гейт здесь — не новый закон, а МАШИНА под уже написанным правилом, которое проза удержать не смогла. Планка ратификации соответственно ниже.
  • Канал — рекомендация с доводом: репо-гейт, а не рантайм-гейт. Рантайм-сверка по таблице snapshots поймала бы только повтор метки ВНУТРИ одной книги, а исторический инцидент был МЕЖ-КНИЖНЫМ (minirun против coldrun, разные БД) — то есть ровно его она бы и пропустила. Репо-гейт ловит его навсегда: тест над prompts/** + конфигами, пиннящий отображение (пара, роль, prompt_version) → sha256 в testdata; правка промпта без бампа метки — красный тест.
  • Класс правки: новое число (сессия вправе). Третья форма — метка ПРОИЗВОДНАЯ от хеша — СНАПШОТ-ДВИЖУЩАЯ (PromptVersion — снапшотное поле), ярус B, в этом паке не делается.

A10 — деньги банковых ролей вне контура согласия (строка 194)

  • Знает: чекпоинты терминолога и классификатора со стадией terminology, глава 0 — они есть в леджере и в checkpoints.
  • Делает: chunk_status строк не пишут ⇒ не входят ни в число согласия, ни в projected_book_usd; при сдвиге снапшота реально пере-покупаются.
  • Нарушено: §2.1.
  • Потребитель: оператор (согласие) + деньги.
  • Канал — НЕ «существующие projected_book_usd / rebill_*», и это проверено на данных. Обе фигуры считаются ИЗ chunk_status (projectRebill принимает statuses []store.ChunkStatus), а chunk_status этого прогона содержит РОВНО draft(20) + edit(3) строк и НИ ОДНОЙ терминологической, тогда как checkpoints держит 9 терминологических. ⇒ контур $0.04980482 для этих носителей структурно невидим, и положить его туда нельзя, не сменив саму деривацию. Носитель придётся строить от checkpoints ⋈ jobs (там роль и стадия есть) — то есть A10 дороже, чем «новое число», и это надо знать ДО планирования правки.
  • Канал: новая деривация поверх checkpoints ⋈ jobs, публикуемая рядом с projected_book_usd.
  • Класс правки: новое число (сессия вправе). ⚠ Замер этого прогона: терминологический контур — $0.04980482 из $0.43610966 = 11.42% захода, и ни цента из этого в проекцию не входило.
  • ОСЬ, НАЗВАННАЯ В СТРОКЕ 194 («на сдвиге снапшота они реально пере-покупаются»), ОПРОВЕРГНУТА ЖИВЫМИ ДАННЫМИ — и это самое важное здесь. Ось пере-покупки — БАЙТЫ БАТЧА: батчи пере-собираются, когда растёт множество кандидатов, а оно растёт от каждой новой дочерновленной главы; на оси терминологии снапшот за весь прогон не двигался ни разу. Разложение покупок П1/П2/П3 и прямая улика из таблицы jobsMONEY_HONESTY_REPORT.md §5.1 (опровержение ратифицировано D39.181 §3). Чинить по названной в строке оси значит чинить не то.
  • A10 и A9 — ОДИН дефект с двух сторон, и §5 A9 ниже несёт его вторую половину.

6. ОДИННАДЦАТЫЙ ЭКЗЕМПЛЯР — которого в таблице промта нет

Промт: «Найдёшь одиннадцатый экземпляр — это лучший результат фазы 1». Нашла ЧЕТЫРЕ. Главные — первые два: A12 внутри движка и A15 на самом шве.

A12. config_drift — двузначное поле для ТРЁХЗНАЧНОГО факта, на ОБЕИХ поверхностях

false в config_drift сегодня значит две несовместимые вещи: «дрейфа нет» и «проверить не удалось». Обе ветки предъявлены исполнением:

  • internal/pipeline/export.go (греп config-drift check failed) — дословно: "export: config-drift check failed; drift state unknown (reported as none)"сам код признаёт, что рапортует none вместо unknown, и это ЕДИНСТВЕННОЕ место в движке, где фраза «reported as none» стоит там, где у четырёх соседей стоит «reported as unknown, not as none»;
  • internal/pipeline/status.go (греп config-drift check failed for a wave) — return из хелпера, rep.ConfigDrift остаётся false;
  • и вторая, более тихая ветка у статуса: if memBasis != RebillBasisFailed && !rep.SnapshotDrift && … — при провале свёртки памяти проверка дрейфа не запускается вовсе, и поле уезжает false.

Почему это лучший экземпляр, а не одиннадцатый по счёту:

  1. Он вне заказанной десятки — мерка сработала.
  2. Он того же класса (§2.3) и потому подтверждает закон, а не расширяет его.
  3. Он показывает, что лекарство у зоны уже изобретено и не разнесено: у rebill_* базис есть (RebillBasis, четыре значения, включая failed = «ноль, потому что НЕИЗВЕСТНО»), у config_drift — нет. Одна и та же болезнь, соседние поля одного документа, разные исходы.
  4. Его потребитель — не тот, кого называет комментарий движка, и настоящий ХУЖЕ.export.go (греп polygon's extraction, which uses ConfigDrift) утверждает, что потребитель — полигон. Я это проверила и НЕ подтверждаю: grep -rn 'config_drift\|ConfigDrift' eval/ даёт НОЛЬ хитов. Комментарий называет потребителя, которого в дереве нет — сам по себе экземпляр §2.2. Настоящих потребителей два, оба проверены грепом: человеческий рендер (cmd/tmctl/render.go, два места) и — вот это дорого — build: internal/pipeline/bookbuild.go, греп func (r \*Runner) staleUnits, первый оператор тела — if exp.ConfigDrift { return nil, true }. ⇒ тихое false пропагируется В СОСЕДНЮЮ честностную поверхность: staleUnits решает, что конфиг чист, и печатает stale: 0 вместо stale: UNKNOWN — то есть поле БЕЗ базиса портит поле, у которого базис ЕСТЬ. Точнее сформулировать болезнь нельзя.

A15. ДВЕ КОПИИ ОДНОГО РАТИФИЦИРОВАННОГО СЛОВАРЯ РАЗОШЛИСЬ — и обещают РАЗНОЕ

Это второй по силе экземпляр после A12, и он единственный, который лежит НА САМОМ ШВЕ.

Движок (backend/cmd/tmctl/main.go, греп The refusal band) дословно:

«A code in [refusalFirst, refusalLast] means the invocation was TURNED DOWN: nothing reached a provider, nothing was spent, no work needs rolling back and a retry is safe … «Nothing was written» is NOT the band's promise any more, it is a clause of the individual classes: exit 15 (write incomplete) legitimately answers with files on disk».

Платформа (platform/internal/ingest/exit.go, греп The refusal band) дословно:

«the invocation was turned down BEFORE it did any work of its own — nothing reached a provider, nothing was spent, and nothing this process would have written was written».

Потребитель контракта держит гарантию, которую производитель контракта ОТОЗВАЛ. Цена названа в обоих файлах и она разрушительная: по PD-196 интейк платформы действует по полосе РАЗРУШИТЕЛЬНО (удаляет загрузку пользователя), и движок прямо предупреждает — «A consumer keys a destructive action on a CLASS it knows, never on band membership». Платформенная копия учит противоположному.

И это ровно то, что предсказал ратифицированный закон шва. 17-seam-inbound-law.md п.6: «Словарь движка ↔ словарь контракта — одна таблица, два направления, один файл… две копии разъедутся». Предсказание сбылось в ЧИТАЮЩЕМ направлении, которое п.6 считал безопасным.

Вторая половина того же расхождения: движок объявляет exitBookIncomplete = 16 (backend/cmd/tmctl/main.go, греп exitBookIncomplete), у платформы КОНСТАНТЫ с этим номером нет вовсе. Здесь дизайн полосы сработал как задумано (незнакомый номер поглощается членством и читается как «refused»), и это положительная улика ЗА полосу — но перечень у потребителя неполон, и знает об этом только тот, кто сверил оба файла.

Что это НЕ отменяет — и я проверила специально, потому что от этого зависит A2: денежная половина обещания («nothing reached a provider, nothing was spent») цела в ОБЕИХ копиях. Запрет отказывать в A2 классом полосы стоит, и теперь он подпёрт формулировкой САМОГО ДВИЖКА, а не только платформы.

Диспозиция: правка платформенного файла — ЧУЖАЯ ЗОНА, я её не трогаю. Уезжает пингом.

И честная оговорка о его отношении к ЗАКОНУ: A15 — НЕ экземпляр закона. Ст. 1 определяет подотчётный факт как ЗНАЧЕНИЕ, которое движок вычислил или выводит из своих строк; «два рукописных комментария о словаре разъехались» — не значение, не выводится ни из одной строки и не имеет канала (натяжка «экземпляр §2.4 на уровне словаря» снята). A15 остаётся находкой первого класса и НЕ становится доводом за закон. Что из него следует для закона: у Г1 обязана быть сверка с ЗЕРКАЛОМ чужого словаря (§7), иначе следующий разъезд будет так же тихим, — но это требование к гейту, а не подведение факта под статью.

A13. Субстрат A0 сам может молча потерять строки

internal/store/requestlog.go дословно: «a telemetry-write failure is logged and never fails the translation», и LogRequest«insert, WARN on failure, never propagate». ⇒ Любая поверхность, построенная НА request_log (а срез A0 ok=0 AND cost_usd>0 — именно такая), может занижать и ничем этого не помечает. По §2.3 такая поверхность обязана нести базис: сверку числа строк request_log против числа checkpoints за тот же прогон, и говорить «неполно», когда они разошлись. (Замер: на coldrun-v16 расхождения нет — 50 чекпоинтов, и все оплаченные вызовы в логе. Дыра латентная, а не наблюдённая. Помечено PLAUSIBLE честно.)

A14. openEvents — прогон, чей поток мёртв, платформе неотличим от прогона без потока

internal/pipeline/events.go (греп could not open the run-event journal): журнал, который не удалось ОТКРЫТЬ, — ERROR в лог и прогон продолжается. Платформа при этом видит отсутствие потока, что по контракту читается как «процесс умер до первой строки». Факт «поток мёртв, прогон жив» движок ЗНАЕТ и на канал платформы не выводит; резинк через status --json есть, но он не отличает эти два случая. (Диспозиция: назвать в законе как незакрытый экземпляр §2.1; правка — не этого пака.)


7. ГЕЙТ — чем ловится ДВЕНАДЦАТЫЙ

Промт: «из ~818 указателей доков по содержимому проверялись 89, и потому 34 уехали молча». Тот же исход ждёт этот закон без машины. Четыре гейта, ранжированы по несущей способности, и я честно говорю, какой из них ГЛАВНЫЙ.

Г1 — НЕСУЩИЙ. Реестр раскрытия + рефлективный конформанс-тест

Один Go-файл: таблица disclosure — на каждое ПОЛЕ каждого опубликованного документа (StatusReport · ChapterPassport · StatusArtifacts · BookExport · BuildReport · payload'ы runevents) одна запись:

{Doc: "status", Field: "config_drift", Consumers: {Operator, Polygon}, Basis: "config_drift_basis"}
{Doc: "status", Field: "rebill_usd",   Consumers: {Operator},          Basis: "rebill_basis"}
{Doc: "status", Field: "done",         Consumers: {Platform},          Basis: BasisTotal}

Тест ходит по структурам РЕФЛЕКСИЕЙ и валит сборку, если:

  • у поля нет записи (⇒ новое поле нельзя добавить молча — это и есть ловля двенадцатого);
  • запись называет Basis: поле, которого в документе нет;
  • запись объявляет потребителя Platform, а поля нет в аллоулисте платформы (сверка — по зеркальной константе, обновляемой при синке шва; кросс-зонная сверка автоматом невозможна, и это названо ограничением, а не сделано вид, что её нет).

Почему рефлексия, а не анализатор: список полей опубликованного документа — это в точности набор json-тегов его типа, и рефлексия читает его без единой эвристики.

ЧЕГО РЕФЛЕКСИЯ НЕ УМЕЕТ — названо, иначе гейт читается сильнее, чем он есть:

  • рефлексия перечисляет ПОЛЯ названного типа, но не ТИПЫ. Список документов остаётся РУЧНЫМ, а «кто-то забыл дописать тип» — та же тихая дыра, ради которой гейт заводится (первый же перечень этого дока пропустил PhaseProgress, WaveCounter и ManifestChapter);
  • payload'ы потока спрятаны за Envelope.Data json.RawMessage — обход по Envelope не видит НИ ОДНОГО payload'а, а НОВЫЙ тип кадра (ровно туда садится рекомендация A5) невидим полностью; ⇒ честная форма Г1: реестр перечисляет ТИПЫ руками, а рефлексия проверяет ПОЛЯ каждого. Ручная половина закрывается отдельно и дёшево — компиляционным var _ = []any{StatusReport{}, …}, который краснеет при добавлении типа в пакет только если его туда впишут; полной защиты от «забыли тип» у Г1 НЕТ, и это ограничение, а не деталь.

Г2 — «известно, но не опубликовано»: реестр КОЛОНОК схемы

Та же таблица, вторая половина: каждая колонка движковой схемы получает либо PublishedIn: [...], либо Private: "<причина>". Тест сверяет таблицу с ЖИВОЙ схемой (PRAGMA table_info по SchemaHead) и краснеет на любой неучтённой колонке. Это тот гейт, который поймал бы блокер прогона: chunk_status.first_flag_reason приватна, и платформа из пятнадцати значений flag_reason видит одно.

Г3 — «гард называет сравнённое поле»: табличный тест по ПОЛЯМ снапшота

Для каждого поля payload'а buildSnapshotID, КОТОРОЕ ФИКСТУРА МОЖЕТ СДВИНУТЬ: сдвинуть ровно его → снять сообщение гарда → утверждать, что оно НАЗЫВАЕТ это поле. Это машинная форма §2.2.

Оговорка, без которой гейт описан сильнее, чем он есть: «~30 плоских осей» — неверно дважды. Payload — ДЕРЕВО (contextSnap · segmentationSnap · coverageSnap · sanitizerSnap · banknoteSnap · repairSnap · []stageSnap), и заметная часть его листьев — КОДОВЫЕ КОНСТАНТЫ (chunker_version · estimator_version · max_tokens_policy · classifier_version · style_check_version · render_format_version · embedded_version · pipeline_core), которые фикстурой не двигаются вовсе — только правкой исходника, чего пак не делает. ⇒ Г3 покрывает КОНФИГУРИРУЕМОЕ подмножество осей, а не все. Для константных осей единственная честная форма — не тест, а требование к сообщению: гард, не сумевший назвать поле, обязан сказать «поле назвать не смог», а не подставлять причину. Это §2.2 в чистом виде и оно дешевле любого теста.

Г4 — «необратимое до ошибки»: анализатор archguard

Площадка уже есть (internal/archguard, три анализатора, go vet -vettool из make battery). Правило: в теле функции, где ЛЕКСИЧЕСКИ ПОСЛЕ вызова из множества «необратимых» (SettleWithCheckpoint · commit · os.Remove · os.Rename) стоит return с ненулевой ошибкой и НУЛЕВЫМ отчётом, — репорт. ⚠ Ложные срабатывания будут, поэтому предлагается с механизмом исключений по образцу существующего exempt (по импорт-пути + базовому имени) и последним по очереди: сначала Г1Г3, он дороже и слабее.

ГЛАВНОЕ ОГРАНИЧЕНИЕ ГЕЙТА

Гейты покрывают НЕ ТЕ каналы, в которые закон маршрутизирует большинство корпуса (сверка §5 против §7): шесть строк из десяти закон отправляет в канал, за которым не стоит НИ ОДИН из четырёх гейтов — A0 → секция report, A2 → строка об усечении, A3(а) → отчёт + WARN, A3(б) → строка сборки, A4 → текст гарда (stderr), A8 → репо-гейт (свой собственный). Г1/Г2 стерегут JSON-структуры и колонки БД, Г3 — одну ось одного сообщения.

И ЧЕТВЁРТЫЙ ГЕЙТ НАЗЫВАЮ НЕПОСТРОЕННЫМ ВСЛУХ, потому что Ст. 4 этого требует буквально. Г4 (анализатор «необратимое до ошибки») НЕ ПОСТРОЕН — он спроектирован и отложен, и §2.5 остаётся НЕЗАГЕЙЧЕННОЙ статьёй: сегодня её держит одна точечная посадка на build (A3а), а не машина, которая поймала бы СЛЕДУЮЩИЙ экземпляр в другом месте. Статус на 31.08: Г1 не построен · Г2 не построен · Г3 построен точечно (A4, по снапшотной оси) · репо-гейт A8 построен · Г4 не построен.

машинная половина закона достаёт до четырёх строк из десяти — закон загейчен на 40%. Это не довод против закона: статьи §2.1§2.5 остаются ревью-вопросами и без машины, — но это ЧЕСТНАЯ цена, и приёмка обязана её знать. Дозакрыть прозаические каналы можно только гейтом на ТЕКСТ (посадка на сообщение с проверкой красноты при подмене строки, D39.171), и это отдельная работа, которую я НЕ проектировала.

Чего гейт НЕ ловит — говорю вслух

  • Факт, который движок вычисляет и не пишет ни в схему, ни в документ (A5 до правки: VolumeStop живёт в памяти). Г1/Г2 его не видят. Единственная защита — ст. 1 как ревью-вопрос при появлении новой структуры результата.
  • Ложь в ПРОЗЕ вне снапшот-гарда: Г3 закрывает только снапшотную ось.
  • Расхождение с ЖИВОЙ платформой: кросс-зонная сверка требует общего артефакта, которого шов намеренно не имеет (D39.81/85). Г1 сверяет с ЗЕРКАЛОМ, и зеркало может протухнуть — это честная граница, а не закрытая дыра.

8. Что закон ТРЕБОВАЛ на ратификацию — и что с этим стало

Все три пункта ратифицированы 31.08 (D39.181) и построены — перечень в шапке файла, разбор каждого экземпляра — MONEY_HONESTY_REPORT.md §9. ⚠ A10 оркестратор НЕ отнёс к классу «новое поле»: это исправление ДЕРИВАЦИИ существующей фигуры, что закон и предписывает. Условие — «ни одна фигура, которую читает платформа, не смеет сменить смысл» — выполнено и предъявлено тестом (committed_usd остаётся SUM(checkpoints) до микро-доллара).

9. Что я в этом дизайне НЕ проверила

  • Не проверяла, согласится ли платформа декодировать предложенные поля — чужая зона, вопрос уехал пингом вместе с дизайном.
  • Реестр Г1 оценён по составу документов, но не написан; цена «одна запись на поле» — оценка, а не замер.