73 KiB
Закон раскрытия — дизайн на ратификацию (фаза 1 пака «ДЕНЬГИ И ЧЕСТНОСТЬ ВЫДАЧИ»)
СТАТУС: РАТИФИЦИРОВАН ВЛАДЕЛЬЦЕМ 31.08 — нота D39.181. Норма зоны. Ратифицированы три вещи: сам закон · поля леджера доставки в кадре
finished(A5) ·config_drift_basisвstatus --json(A7/A12). Все десять экземпляров корпуса ПРИМЕНЕНЫ — разбор по пунктам вMONEY_HONESTY_REPORT.md§9. ⚠ Оговорка к Ст. 3 (дешевизна полосы 10–19) дописана ПОСЛЕ ратификации по находке оркестратора и стоит ниже; она сужает полномочие, а не расширяет его. ⚠ Честная цена, названная при ратификации: закон загейчен примерно на 40% — см. §7.Написан бэкенд-сессией
textmachine-main-8a(промт —docs/archive/prompts/BACKEND_MONEY_HONESTY_SESSION_PROMPT.md§1), ратифицирован владельцем через оркестратора №21. ⚠ Каждый факт ниже снят ИСПОЛНЕНИЕМ на HEAD9d27c0b, командой, названной рядом.
0. Что это и чего это не
Это — правило вида «факт КЛАССА X, вычисленный движком, обязан достичь потребителя Y по каналу Z», плюс механический гейт на его нарушение.
Это НЕ — каталог полей, не расширение шва и не новое слово ни в одном ратифицированном словаре. Там, где закон ТРЕБУЕТ расширения, он это называет отдельным пунктом на ратификацию (§7).
Проверка качества — на корпусе: закон обязан объяснить все десять строк таблицы промта и назвать канал каждой. §5 это делает. §6 предъявляет ОДИННАДЦАТЫЙ экземпляр, которого в таблице нет.
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). - Фигура едет С БАЗИСОМ —
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 + ПОЛОСА 10–19; неизвестный код → 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, пересекающее шов · код выхода ВНЕ полосы (0–5) |
ратификация ВЛАДЕЛЬЦА |
| НОВЫЙ КЛАСС ОТКАЗА ВНУТРИ полосы 10–19 | новая константа в 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). Полоса спроектирована расти;
заморожена верхняя шестёрка 0–5. ⚠ Для 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о полосе 10–19 — «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 1⇒failed— то есть ложную причину читает не только человек, причём резолвится она как «инфра-сбой», а не как «нужен--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 — факт платформы едет прозой.
- ⚠ ДВЕ ПОПРАВКИ К ПРОМТУ, обе проверены исполнением:
- Платформа сегодня
--max-unitsНЕ ШЛЁТ ВООБЩЕ (platform/internal/runner/engine.go, грепfunc TranslateArgs; во всей зонеplatform/вне комментария этого флага нет). ⇒ A5 — дефект БУДУЩЕГО потребителя, не живой инцидент. Срочность падает, дизайн — нет. - Формулировка «неотличима от «что-то тихо сломалось»» слишком сильна: тихий слом даёт 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, ниexportprompt_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 и прямая улика из
таблицы
jobs—MONEY_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.
Почему это лучший экземпляр, а не одиннадцатый по счёту:
- Он вне заказанной десятки — мерка сработала.
- Он того же класса (§2.3) и потому подтверждает закон, а не расширяет его.
- Он показывает, что лекарство у зоны уже изобретено и не разнесено: у
rebill_*базис есть (RebillBasis, четыре значения, включаяfailed= «ноль, потому что НЕИЗВЕСТНО»), уconfig_drift— нет. Одна и та же болезнь, соседние поля одного документа, разные исходы. - Его потребитель — не тот, кого называет комментарий движка, и настоящий ХУЖЕ.
⚠
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 оценён по составу документов, но не написан; цена «одна запись на поле» — оценка, а не замер.