674 lines
73 KiB
Markdown
674 lines
73 KiB
Markdown
# Закон раскрытия — дизайн на ратификацию (фаза 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.
|
||
> ⚠ **Каждый факт ниже снят ИСПОЛНЕНИЕМ на HEAD `9d27c0b`**, командой, названной рядом.
|
||
|
||
---
|
||
|
||
## 0. Что это и чего это не
|
||
|
||
**Это** — правило вида «факт КЛАССА X, вычисленный движком, обязан достичь потребителя Y по каналу Z»,
|
||
плюс механический гейт на его нарушение.
|
||
|
||
**Это НЕ** — каталог полей, не расширение шва и не новое слово ни в одном ратифицированном словаре.
|
||
Там, где закон ТРЕБУЕТ расширения, он это называет отдельным пунктом на ратификацию (§7).
|
||
|
||
**Проверка качества — на корпусе:** закон обязан объяснить все десять строк таблицы промта и назвать
|
||
канал каждой. §5 это делает. §6 предъявляет **ОДИННАДЦАТЫЙ экземпляр**, которого в таблице нет.
|
||
|
||
---
|
||
|
||
## 1. Болезнь — одним предложением, и почему она не десять багов
|
||
|
||
> **Движок вычисляет факт и не доносит его до того, чьё решение от этого факта зависит** — молча, ложью
|
||
> о причине, или в канал, который этот потребитель не читает.
|
||
|
||
Десять экземпляров — не десять багов, а один отсутствующий закон: **у движка нет дисциплины «что
|
||
потребитель обязан узнать и по какому каналу»**. Чинить поштучно значит заказать одиннадцатый.
|
||
|
||
⚠ **И закон не импортируется извне — он уже наполовину написан В ЭТОМ КОДЕ, просто применён точечно.**
|
||
Две нормы живут в комментариях и не имеют ни имени, ни гейта:
|
||
|
||
1. **«unknown, not zero»** — `internal/pipeline/quality.go:233`=`the UNSIGNED BANK count is unknown, not zero`,
|
||
`internal/pipeline/status.go:768`=`the unsigned-term count is unknown, not zero`,
|
||
`internal/pipeline/status.go:851`=`reported as unknown, not as none`,
|
||
`internal/pipeline/bookbuild.go:227`=`(reported as unknown, not as none)`.
|
||
2. **Фигура едет С БАЗИСОМ** — `RebillBasis` (`internal/pipeline/status.go:275`=`RebillBasis string`),
|
||
четыре значения `pending|stored|none|failed`
|
||
(`internal/pipeline/bankmaterialize.go:360-363`), и её докстринг формулирует ровно закон:
|
||
*«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 — факт платформы едет прозой.
|
||
* **⚠ ДВЕ ПОПРАВКИ К ПРОМТУ, обе проверены исполнением:**
|
||
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 и прямая улика из
|
||
таблицы `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`.
|
||
|
||
**Почему это лучший экземпляр, а не одиннадцатый по счёту:**
|
||
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 оценён по составу документов, но **не написан**; цена «одна запись на поле» — оценка, а не
|
||
замер.
|