textmachine/backend/docs/MONEYSTOP_REPORT.md

871 lines
148 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Отчёт бэкенд-пака «денежный стоп перестаёт быть аварией» (05.09)
> **Промт:** `docs/archive/prompts/BACKEND_MONEYSTOP_SESSION_PROMPT_2026-09-05.md` (сдан и заархивирован 05.09, `D39.206`). **Зона записи:** `backend/` + своя секция
> `docs/PROGRESS.md`. **Дерево НЕ закоммичено, лендинг за оркестратором.**
>
> ⚠ **В рабочем дереве лежат ЧУЖИЕ правки — вся `platform/`** (параллельный платформенный пак
> `textmachine-main-63`: бэкапы, дверь входа, рантбук деплоя). Я их не трогала ни байтом; всё, что ниже,
> — только про `backend/`.
>
> ⛔ **Платного прогона НЕТ. Ни один вызов не ушёл ни к одному провайдеру.** Все три оси ревью
> предъявлены за $0 — фейк-провайдер на `httptest`, голдены, каталог мутаций.
---
## 0. Что стало с деревом — одним абзацем
Отказ резервации больше не убивает книгу. Он защёлкивает волну (новые элементы не начинаются),
допущенные вызовы дорабатываются и сеттлятся, а сам отказ не окончателен, пока в полёте есть чужая
резервация: settle возвращает `оценка факт`, запас растёт, и тот же резерв переспрашивается. Прогон
уходит штатно, с частичным результатом, с `exit 4`/`paused` и — впервые — **называет, сколько не
хватило**. Отдельно и первым пунктом: пользовательская правка банка больше не может писать свои строки
внутрь системного сообщения платного вызова. И движок теперь публикует **цену книги ДО покупки**
по юниту, по главе и по книге, плюс самый крупный неделимый резерв, из-за которого стена и стояла.
---
## 1. Пункт 1 (строка 271) — правки банка перестали быть инструкциями. ЗАКРЫТ
**Что было.** Инъекция банка — отдельное сообщение роли `system`; тело собиралось конкатенацией
`src + " → " + dst`; `dst` приходит с пользовательской двери `POST /books/{id}/bank/corrections` и
получал ровно один `TrimSpace`. Ни предела длины, ни запрета управляющих. ⇒ `dst` с переводом строки
дописывает свои строки внутрь системного блока **каждого** платного вызова книги.
**Что построено — одна граница, ТРИ точки исполнения** (`backend/internal/membank/wirefence.go`):
| точка | что делает | почему там |
|---|---|---|
| дверь (`decisions.go/resolveDecision`) | ОТКАЗЫВАЕТ с индексом решения | человек узнаёт, что его термин не поедет, вместо того чтобы получить молча искажённый |
| загрузчик сида (`memseed.go`) | ОТКАЗЫВАЕТ загрузку | файл оператора — его же и чинить; дроп на пути к проводу оставил бы термин, который он считает действующим, отсутствующим в каждом запросе |
| рендереры (`RenderGlossaryBlock`, `editorLines`) | ПРОПУСКАЕТ строку | единственная точка, которую нельзя обойти писателем, добавленным позже |
**Что именно запрещено и почему НЕ больше.** Управляющие (Cc: сюда попадают LF, CR, TAB, VT, FF, NEL),
разделители строки/абзаца U+2028/U+2029 и **bidi-переопределения/изоляты** U+202AU+202E, U+2066U+2069
(класс Trojan Source, CVE-2021-42574 — они не рвут строку, а переставляют её, и человек, подписывающий
банк глазами, читает не то, что получает модель). ⚠ **Bidi-МАРКИ U+200E/U+200F и ZWJ/ZWNJ РАЗРЕШЕНЫ**:
это обычное содержимое иврита, арабского, деванагари и фарси, а движок обязан работать для пары,
которой в репозитории нет (§0.1). Предел длины — **200 рун** (не байт: байтовый предел был бы разным
пределом для разных письменностей). Довод числа: собственная структурная строка движка — заголовок главы
с подзаголовком — ограничена 60 рунами (`chunk.chapterHeaderMaxRunes`), а при штатном
`glossary_token_budget: 800` одна 200-рунная плотная строка уже забирает четверть всего бюджета
инъекции на чанк.
**ТРЕТЬЕ ЛЕЧЕНИЕ ПРОМТА — «обрамление как ДАННЫХ» — В ТЕКСТОВОЙ ФОРМЕ НЕВОЗМОЖНО, и это находка, а не
пропуск.** Заголовки блоков — целевые wire-тексты и живут только в
`backend/internal/lang/data/injection.txt`. Этот файл **побайтно** входит в `lang.EmbeddedVersion()`
(`embedded.go:22,79`), который едет в `cutInputs.Embedded``cutTag()`**внутрь каждого `unit.id`**
(`manifest.go:168-170,201`) и в `manifestKey` и в снапшот волны. ⇒ добавление ОДНОЙ строки пере-чеканило
бы `unit.id` всех существующих книг и потребовало `--resnapshot` сквозь консент-гейт пере-оплаты — то
есть ровно то, что тот же промт §4(6) запрещает. Обрамление сделано **структурно**: значение, которое не
может покинуть свою строку и не может превысить длину термина, не в состоянии выдать себя ни за что,
кроме правой части своего же отображения.
**Снапшот: чистые книги не двигаются, отравленная двигается.** Фолд `wire-fence-v1` — СКОУПНЫЙ, по
образцу `neuter-directive-v1`: пишется только когда в скоупе ЕСТЬ негодная строка. Предъявлено
**значением, а не доводом**: хеш чистого банка снят сборкой коммита `32be78a` (до фикса) и запинен
константой в тесте.
**Артефакт приёмки — КРАСНЫЙ до, ЗЕЛЁНЫЙ после.** Предъявлено исполнением на дереве HEAD:
```
$ cd <копия HEAD 32be78a>/backend && go test ./internal/membank/ -run 'TestAUserTerm|TestTheDoor|TestTheSeedLoader' -v
--- FAIL: TestAUserTermCannotWriteItsOwnLinesIntoTheSystemBlock
draft glossary block: a user-written term put an INSTRUCTION into the system message
draft glossary block: the value ended its own line and wrote a new one
editor constraint block: a user-written term put an INSTRUCTION into the system message
editor constraint block: the value ended its own line and wrote a new one
--- FAIL: TestTheDoorRefusesATermThatCannotBeRenderedAsData
want exactly the SECOND decision refused, got []
--- FAIL: TestTheSeedLoaderRefusesATermThatCannotBeRenderedAsData
a seed term whose dst ends its own line must not load
```
На рабочем дереве все шесть тестов файла зелёные.
---
## 2. Пункт 2 (строка 277) — отказ перестал гасить допущенное. ЗАКРЫТ
**Форма защёлки** (`waverun.go`): канал `latched`, не флаг. Причина механическая: кормилец БЛОКИРУЕТСЯ
на `idxCh <- i`, когда все воркеры заняты, — а это ровно то состояние, в котором достигается денежный
стоп; флаг был бы прочитан после отправки, которую он должен предотвратить.
**И защёлка спрашивается НЕБЛОКИРУЮЩЕ ПЕРЕД трёхветочным `select`.** Go выбирает среди готовых ветвей
`select` **равномерно случайно**, так что при закрытом `latched` и свободном воркере трёхветочный
`select` раздавал бы индекс примерно в половине случаев — до одного лишнего элемента на простаивающего
воркера. Тот же изъян давно есть у `ctx.Done()` в этой же строке; на денежном пути это разница между
«ничего нового не начато» и «начато ещё несколько».
**Где ждать settle — в точке резерва** (`stagerun.go`), потому что там известна оценка и туда же
возвращается повтор. Общая на прогон структура — `reserveGate` (`reservegate.go`).
**`Reserve` исполняется ПОД мьютексом шлюза, и это требование корректности, а не аккуратность.** С
допуском и счётчиком в двух критических секциях воркер X мог закоммитить резервацию и быть вытеснен до
записи себя «в полёте»; воркер Y, которому отказала резервация X, прочитал бы ноль в полёте и объявил
книгу законченной, пока вызов X идёт и вот-вот вернёт деньги. Стор и так сериализует резервации
единственным пишущим пулом — под ещё одним локом не теряется ничего.
**`done()``defer` СРАЗУ после выхода из цикла допуска, и нигде больше.** Резервация освобождается и
сеттлится на дюжине путей выхода; парная привязка счётчика к ним оставила бы `inFlight > 0` до конца
прогона при одном пропущенном, и каждый последующий отказ ждал бы settle, который не придёт. Счёт до
возврата функции строго консервативен: ждущий ждёт мгновение дольше, чем должен, и никогда не
останавливается там, где должен был ждать.
**Только ОБЯЗАТЕЛЬНЫЕ вызовы ждут** — новый параметр `mandatory` у `runAttempt`, переданный явно (а не
выведенный), потому что вывести его нельзя: `escalation=true` стоит только у хопа, а репайр
(`repair.go:421`) и терминолог (`terminologist.go:687`) передают `false`, хотя тоже деградируют. Ждущий
опциональный вызов превратил бы «не начинаем новое» в «опциональное съедает то, что нужно
обязательному», а хоп держит `escMu` всю свою жизнь и заблокировал бы эскалации всех соседей.
**Ожидание бессмысленно, когда его нечем купить**`settleCannotHelp` (⚠ это ОПТИМИЗАЦИЯ, не несущая гарантия: состояние, в котором она решает, почти недостижимо в однородной волне — §9 п.6): settle возвращает не больше
`оценка факт` и никогда не уменьшает УЖЕ СПИСАННОЕ, так что при `committed + estimate > ceiling` ждать
нечего. Без этой проверки воркер держал бы волну открытой весь самый долгий провайдерский таймаут, чтобы
прийти к отказу, который у него уже был. ⚠ Спрашивается только у КНИЖНОГО потолка: дневной суммирует
все книги стора, и движковые цифры — не его; там ждём консервативно (ожидание всё равно ограничено —
оно кончается, как только полёт пуст).
**Штатное завершение с леджером.** Денежный стоп возвращает `(res, halt)``TranslateBook` уже отдавал
обе половины, а `tmctl` теперь ПЕЧАТАЕТ частичный леджер (раньше при ошибке результат не рендерился
вовсе, и оператор узнавал только что прогон встал).
### 2-бис. Пять рисков зоны — каждый предъявлен
| риск | предъявление |
|---|---|
| **(а)** защёлка на уровне волны, не у воркера | посадка `MS-latch-is-a-cancel` (замена защёлки на `cancel()`) — цвет в §6; тест `TestUnderACeilingTheWaveFinishesWhatItAdmitted` |
| **(б)** учёт объёма (`bound()`/`reconcile`) при частичной волне | ⛔ **НАЙДЕНО И ЗАКРЫТО ОТКАЗОМ ОТ ОТЧЁТА:** `res.Volume` при денежном стопе НЕ прикладывается — см. ниже |
| **(в)** событие `ceiling` и exit 4/`paused` не задержались | `TestTheStoppedRunTellsAConsumerHowMuchWasMissing` читает журнал СО СТОРОНЫ ПОТРЕБИТЕЛЯ (байты NDJSON), `TestACeilingHaltAnnouncesItselfAndNamesWhichCeiling` держит позицию кадра и `outcome: ceiling` |
| **(г)** дневной потолок делит ветку с книжным | `TestSettleCannotHelpRefusesToWaitForMoneyThatIsAlreadySpent` — day-скоуп не смеет объявить отказ окончательным по цифрам одной книги; `TestTheShortfallIs…` — day-скоуп не публикует недостачу; `TestACeilingHaltAnnounces…/the day ceiling` — поле опущено на проводе |
| **(д)** эскалационный хоп не опирается на отмену | проверено чтением: `escalation.go:163` ловит `errors.Is(err, errReserveCeiling)`, а `CeilingHalt.Unwrap` его отдаёт — отмена в этой цепи не участвует. Более того, ЧУЖОЙ `cancel()` хопу МЕШАЛ: `rateGuard.acquire(ctx)`/`Complete(ctx)` падали ctx-ошибкой, которая не матчит сентинел. Снятие отмены хоп только чистит. Пин: `TestRunnerEscalationCeilingDegrades` (зелёный), `TestWaveEscalationBudgetSerializedUnderParallelism` (зелёный) |
**⛔ Риск (б) оказался настоящим, и лечение — молчание, а не исправление счётчиков.** Семь счётчиков
`VolumeStop` — это ПЛАН: `planVolume` классифицирует юниты до волн, `reconcile` правит только те исходы,
которые есть. Денежный стоп обрывает работу, которую план считал сделанной, ⇒ допущенные и не доделанные
юниты остаются посчитанными как `Delivered`/`Reworked`/`Carried`/`Free`, а `LeftFresh`/`LeftRework`
занижены ровно на них; при `Left() == 0` прогон, вставший посреди книги, объявил бы «дошёл до конца
книги и ничего не придержал» и не пригласил бы докупить. **Восьмого бакета «допущен, не доделан» в семи
полях нет**, поэтому денежный стоп получил СВОЙ леджер — `Finished.Money`, считанный по тому, что прогон
РАЗРЕШИЛ, а не по тому, что намеревался.
---
## 3. Пункт 3 (строка 278, событие) — «сколько не хватило» доезжает. ЗАКРЫТ, с ЗАМЕНОЙ ЧИСЛА
**⚠ РАСХОЖДЕНИЕ С ПРОМТОМ, взятое осознанно и согласованное с оркестратором.** §4(3) называет число:
«Число у тебя уже есть и печатается (`denied estimate=$…`)» — и следующей фразой запрещает его класс:
«цены моделей, стадий и вызовов не выходят». `denied estimate` — это **цена одного вызова**, то есть
ровно запрещённое. Наружу едет **недостача** `committed + reserved + estimate ceiling`, поле
`shortfall_micro_usd` (строка 278 называла `next_step_micro_usd` — то же противоречие). Недостача
отвечает на настоящий вопрос платформы («пополни на столько — и вызов пройдёт») и не раскрывает
стоимость ни одной стадии: это расстояние между лимитом, который платформа сама поставила, и суммой,
которую она уже авторизовала. Оркестратор ответ принял и пере-снимет строку 278 под построенное имя.
**Две честные оговорки, обе в доке поля:**
1. **Это ВЕРХНЯЯ граница, а не минимум.** `reserved` включает ещё идущие вызовы, а settle заменяет
резервацию стоимостью, которая никогда не больше. «Пополни на столько — и отказанный вызов пройдёт»
верно всегда; «меньше не хватит» — нет. При пустом полёте (обычный терминальный отказ) они совпадают.
2. **Только book-скоуп.** Дневной потолок суммируется по ВСЕМ книгам стора (`ledger.go`: `WHERE date = ?`
без фильтра по книге), а доступные движку цифры — одной книги. Уверенное число там было бы про другую
сумму. Поле опускается; отсутствие значит «не сказано», не «ничего не не хватало».
⚠ **ЧЕСТНЫЙ ОСТАТОК, названный оркестратором и РАСШИРЕННЫЙ мной: недостача ВОССТАНОВИМА в цену
отказанного вызова.** `estimate = shortfall + ceiling committed reserved`. Потолок платформа знает —
сама поставила; `committed` знает — движок публикует его кумулятивно кадром `spend`; а `reserved` на
обычном терминальном отказе по построению почти нулевой, потому что стоп туда приходит через
`waitNothingInFlight` — то есть при ПУСТОМ полёте. ⇒ обратный счёт работает не только на первом вызове
книги, как было записано изначально, а практически на любом стопе ЭТОГО пути.
**И границу самого этого утверждения тоже надо назвать, иначе оно шире замера** (поправка оркестратора,
купленная его же сегодняшней ошибкой: выборка, предъявленная как доказательство, доказывает ровно себя).
«`reserved` пуст» показано для ОБЯЗАТЕЛЬНОГО пути, который останавливается через `waitNothingInFlight`.
Для стопа через `settleCannotHelp` полёт может быть любым: при пустом этот стоп неотличим от основного
случая (`reserved` ≈ 0, оценка восстановима), при непустом обратный счёт даёт ВЕРХНЮЮ границу. То есть
утечка на этом пути НЕ БОЛЬШЕ, чем на основном — «не больше», а не «строго слабее», как я написала
сначала. Для НЕОБЯЗАТЕЛЬНЫХ вызовов вопрос не встаёт вовсе: они на
потолке деградируют, а не останавливают книгу, и кадра не рождают. Вывод от этого не меняется, а
утверждение перестаёт быть шире того, что проверено.
**ДОВОД «ТА ЖЕ ДВЕРЬ» БЫЛ ЛОЖЕН, И ЭТО ГЛАВНАЯ ПРАВКА ДОФИКСА (F1 приёмки).** Первая редакция этого
абзаца утверждала: «тот же пак ратифицированно публикует цену вызова на том же шве — `step_max_usd`,
значит дверь та же». **Проверено командой, а не рассуждением:**
```
$ git grep -lE "StepMax|step_max" HEAD -- platform/ # ПУСТО
$ git grep -c "StepMax" HEAD -- backend/internal/runevents/ # 0
```
`step_max_usd` живёт только в `manifest.go`, `priceprojection.go` и `render.go`; **потребитель его не
читает и на проводе его нет.**
⛔ **И ЭТА ФОРМУЛИРОВКА, В СВОЮ ОЧЕРЕДЬ, ТОЖЕ ОКАЗАЛАСЬ НЕ ОКОНЧАТЕЛЬНОЙ — 05.09 оркестратор ОТОЗВАЛ
ратификацию до слова владельца, и я проверила его посылки своей рукой прежде, чем править:**
- `docs/product-requirements.md` (ПТ-35): отзывая 05.09 «запрет денег на экране», владелец оставил
остальное дословно — «цены моделей, стоимость стадий **и вызовов**». Сузить это до «пользовательской
поверхности» зона не вправе, и я это сужение приняла.
- `docs/archive/prompts/BACKEND_MONEYSTOP_SESSION_PROMPT_2026-09-05.md:82`=`цены моделей, стадий и вызовов не выходят`
применяет запрет к **ПРОВОДУ**, а не к экрану: «Наружу —
только НЕДОСТАЮЩАЯ сумма; цены моделей, стадий и вызовов не выходят». ⇒ замер верификатора попал не в
реализацию поля, а **в посылку ЗАКАЗА**, а на этот случай норма однозначна: стоп и вопрос владельцу.
- ссылка на ратификацию «D39.106 §2» — овер-атрибуция: `05-decisions-index.md:167` говорит, что D39.106 —
про шов «движок = транзиентный юнит, события = `events.jsonl`», и такой фразы там нет вовсе.
**Что было сделано с кодом: поведение НЕ тронуто, изменилось только то, что код УТВЕРЖДАЛ о своей
легитимности** — шапка поля была помечена `pending owner`. И к вопросу владельца добавлено то, чего не видел
ни оркестратор, ни я: **тензия живёт ВНУТРИ самого заказа.** Пункт (3) промта запрещает цене вызова
выходить наружу, пункт (4) ТРЕБУЕТ опубликовать `step_max_usd` в `manifest --json`а это по построению
оценка ОДНОГО вызова, самой дорогой неделимой резервации книги. То есть дверь открывает не недостача
одна: заказ держит обе половины сразу. ⚠ Уточнение, которое сужает вопрос до честного: ни недостача, ни
`step_max_usd` не раскрывают, сколько вызов СТОИЛ, — обе величины суть до-вызовные ОЦЕНКИ, а не
сеттлмент.
**РАЗРЕШЕНО ВЛАДЕЛЬЦЕМ 05.09, ратифицировано `D39.203`** — проверено по телу ноты, а не по пересказу:
`05-decisions-log.md:2395`, строка реестра `05-decisions-index.md:264`. Дословно: «Можно, в форме
„сколько добавить“». **Различение оценка/стоимость вошло ОПОРОЙ решения, а не примечанием:** наружу идёт
до-вызовная ОЦЕНКА (резервация, которую движок просит ПЕРЕД звонком), ПТ-35 запрещает СТОИМОСТЬ и
остаётся в силе НЕ суженным, сеттлмент наружу не идёт ни одной дверью. Обе половины тензии закрыты
разом — снятие недостачи вопроса бы не закрыло, потому что `step_max_usd` остаётся. `pending owner` с
шапки поля снят, вместо него — указатель на ноту.
**Владелец выбрал ФОРМУ, которой в коде ещё нет:** `max(shortfall, step_max headroom)` — «сколько
добавить, чтобы прошёл ЛЮБОЙ следующий вызов». Это **НОВЫЙ ЗАКАЗ, а не дефект здесь**: построенное поле
не ложно, оно У́ЖЕ — говорит, сколько не хватило на ЭТОТ вызов. Состав работы — тот же список, которым я
отвечала на вопрос о цене: новая величина + ПЕРЕИМЕНОВАНИЕ поля (`shortfall_micro_usd` станет ложным
именем) + минор `StreamVersion` 1.3→1.4 + зеркало словаря у платформы + пере-снятие пина белого списка.
Пак лендится КАК ПОСТРОЕН, форма — строкой бэклога (`D39.203` §56).
⚠ Довод пришёл ко мне от оркестратора, и я приняла его, не проверив командой, — ошибка чужая по
происхождению и МОЯ по принятию: посылку, на которой стоит утверждение в коде, зона обязана проверять
сама, кем бы она ни была принесена.
**И утверждение в самом коде было сильнее правды:** `runevents.go` писал «discloses nothing about what
any one call cost». Верификатор опроверг это ЖИВЫМ ЗОНДОМ: `shortfall + ceiling = $0.007016840` против
собственного текста отказа движка `denied estimate=$0.007016` — дельта в микродоллар округления; со
вторым зондом (ненулевой `committed`, который платформа читает кадром `spend`) — $0.0070166 против тех же
$0.007016. Текст исправлен на измеренную правду.
**Что остаётся закрытым и почему поле всё равно не убирается:** ПТ-33 охраняет СТРУКТУРУ расходов — цену
модели, стоимость стадии, разложение вызова между промптом и генерацией; тотал одного отказанного вызова
не является прайс-листом. Кадр идёт внутреннему потребителю, а не человеку (D39.196 §2а снимает запрет
денег в UI, не трогая ПТ-33), платформенная сессия письменно подтвердила, что показывать его не будет и
не может, а стоп, который не может назвать недостачу, оставляет покупателя с мёртвой книгой и без
следующего шага — то, что и намерено живьём 04.09.
⚠ Ратификационную ноту об этом пишет ОРКЕСТРАТОР — сужение ратифицированной фразы не работа зоны.
**Версия шва: `StreamVersion` 1.2 → 1.3** по правилу минора (добавлены поле и структура). Прибито
тестом, читающим `hello` из журнала, и посадкой на откат константы.
**Событие пишется В МОМЕНТ ОТКАЗА, а не из терминального обработчика.** Причина — PD-113: он закрыт
двумя каналами, которые падают НЕЗАВИСИМО. Раз потолок больше не отменяет соседей, один из них может
упасть по своей причине позже и обогнать halt на выходе — и событие, выведенное только из возвращённой
ошибки, промолчало бы о деньгах, которые уже кончились. Идемпотентно (`emitter.ceilingSaid`).
**Событие не может выстрелить от ОПЦИОНАЛЬНОГО отказа — проверено чтением обеих ветвей.** `Finished.Money`
и кадр `ceiling` условны на `ceilingSaid`, который выставляется только из `fail()` волны. Отказ хопа
гасится в `maybeEscalate` (`escalation.go:163`), репайра — `repair.go:307-311` (`break`, не `return`),
терминолога — `terminologist.go:857-860` (`break`). Ни один до `fail()` не доходит ⇒ прогон, который лишь
деградировал опциональный проход, не публикует ни денежного леджера, ни стопа.
**Ранжирование ошибок: паника > инфра-ошибка > потолок, и инфра-ошибка с потолком НЕ джойнится.** Паника
джойнится, потому что у неё ЕСТЬ ТИП, который `exitCode` и `terminal()` проверяют ПЕРВЫМ, — ранг
переживает джойн. У инфра-ошибки типа нет: `errors.As(join, &CeilingHalt)` был бы истинен, сломавшийся
прогон ушёл бы с exit 4, платформа записала бы `paused`, и оператору вручили бы неверное лекарство
(«добавьте денег»). Пин: `TestAnInfraFailureAfterAMoneyStopDoesNotDepartAsPaused`.
---
## 4. Пункт 4 (строка 278, проекция) — цена книги ДО покупки. ЗАКРЫТ
**⚠ РЕШЕНИЯ, КОТОРЫЕ ПРОМТ ОТДАЛ СЕССИИ — с доводами.**
**(а) `expected_usd` считается по СОБСТВЕННОЙ фертильности и формулой СПИСАНИЯ, а `step_max_usd` — формулой
РЕЗЕРВАЦИИ. Это не две оценки одного, это два разных вопроса.**
Публиковать одно число за оба было бы неверно в обе стороны сразу. Резервация worst-case по построению
(весь промпт мимо кеша или В кеш, генерация до `max_tokens`), так что проекция из резерваций завысила бы
счёт в разы и заставила платформу отказывать в заказах, которые она легко тянет; проекция ожидаемого
списания занизила бы ровно ту величину, которая отказывает вызовам, — а это и есть путь, которым сервис
пришёл к «книгу нельзя перевести никакими деньгами». ⇒ `expected_usd` = `ledger.CostUSD` над
фертильностью пары (те же коэффициенты `est_out`, которыми чанкер уже режет книгу); `step_max_usd` =
`ledger.EstimateUSD` при размере, который считает сам исполнитель.
**(б) По отрендеренному промпту или по сырому тексту — по СЫРОМУ ТЕКСТУ ПЛЮС ШАБЛОН, и вот почему.**
Отрендерить настоящий промпт стадии k>0 нельзя по построению: её вход — ЧЕРНОВИК, которого до прогона
нет, а вопрос задаётся именно до покупки. Зато шаблон загружен (`openRunner` зовёт `loadTemplates()` на
обоих путях, включая read-only) и он НЕ мал — редакторский промпт c1 больше тысячи токенов, и проекция,
которая его игнорирует, занижает промптовую сторону каждого юнита книги на одну и ту же величину: это
смещение, а не шум. Инъекция банка берётся ПО ВЕРХНЕЙ границе (её бюджет), потому что банка на момент
построения манифеста ещё нет.
⚠ **И два уточнения, которых в первой редакции НЕ БЫЛО — оба найдены адверсариальным проходом и оба
двигали число на проценты, а не на копейки** (находки 14 и 15 в §6.3):
· **Исходник едет на КАЖДУЮ стадию, чей шаблон его просит.** Исполнитель рендерит все стадии через
`RenderVars{Text, Draft}`, а редактор билингвален (D30.1). Проекция спрашивает ШАБЛОН — `{{text}}`, тот же
`sourcePlaceholder`, по которому движок уже считает echo-exposure, — а не позицию стадии: монолингвальное
плечо ценится без исходника теми же строками, и пар-специфики в этом нет.
· **Few-shot считается по ТОГЛУ стадии,** через `tpl.SystemFor(fewShotEnabled(st))` — функции самого
исполнителя. У боевого c1-редактора тогл выключен, а блок в шаблоне лежит.
**(в) Какие стадии входят и НА КАКОЙ ГРАНУЛЯРНОСТИ.** В `expected_usd` — все стадии конвейера, по одной
попытке. НЕ входят: ретраи, эскалационный хоп, репайр. Каждый из них УСЛОВЕН, и вложить лекарства плохого
прогона в заголовочную цену значит выдать исключение за правило.
**И считается это на ТОЙ ЖЕ гранулярности, на какой исполнитель делает вызовы** (находка 22): черновые
стадии — ПО ЧЛЕНАМ юнита (`runDraftChunk` — один рендер и один вызов на чанк), остальные — ПО ЮНИТУ
(`runEditUnit`). Иначе смещение идёт сразу в две стороны: шаблон с инъекцией недосчитываются, а
`step_max_usd` завышается в число членов.
**(г) Ретраи и эскалации — в `step_max_usd` входят РЕТРАИ и не входит всё остальное, и различитель —
деградирует ли отказ.** `maxTokensForAttempt` УДВАИВАЕТ выходной бюджет на каждой регенерации, а отказ
РЕТРАЯ **не деградирует** — он останавливает книгу. Потолок, посчитанный по attempt 0, допускает первый
вызов юнита и навсегда отказывает его лекарству. Хоп, репайр и терминология ловят `errReserveCeiling` и
продолжают — потолок, малый для них, стоит качества, а не движения, и включать их значило бы поднять
минимальную покупку ради работы, от которой прогон готов отказаться.
**(д) `book_once_usd` — это ГРАНИЦА, а не прогноз, и назвать её иначе было бы ложью.** Вход терминолога
и его классификатора — список кандидатов, который производит майнер ИЗ ЧЕРНОВИКОВ, а черновиков на момент
публикации числа нет. Честно движок может сказать только то, чего он не превысит: обе стадии гейтятся
собственными бюджетами, и план обрезается под них до первого вызова. Для вопроса «какой потолок нужен
этой книге» — а он и есть вопрос — граница и является полезной цифрой. Ноль, когда стадии не настроены.
**(е) `structure` — по ПРОВЕНАНСУ, с одним исключением, которое тоже про провенанс.**
`declared` — границу нарисовал ФОРМАТ (spine EPUB; перевод страницы U+000C в txt — ASCII-разрыв
страницы, столь же явный). `detected` — движок ВЫВЕЛ её, сопоставив строки-заголовки в прозе
(`splitTextChapters`); он прав на корпусе, ради которого построен, и он догадка: абзац, открывающийся
«第三节…» в пределах длины, неотличим от заголовка. `none` — одна глава: границу, которую никто не
пересёк, никто и не проверял, каким бы путём она ни получилась.
⚠ **И ЧЕСТНАЯ ГРАНИЦА, которую я сначала записала как достоинство и исправляю: `detected` СЕГОДНЯ
CJK-ONLY.** Сопоставление заголовков идёт по `lang.DefaultCJKSection()` — это КОНСТАНТА (章节節回 плюс
класс CJK-числительных), а не данные пары. ⇒ книга в любой другой письменности вернёт `none`, как бы
явно её главы ни были размечены. **Асимметрию я не вношу — она уже есть и уже названа**: движок читает
данные пары для РЕНДЕРА заголовка (`configs/langpacks/*/heading.txt`), а РАЗРЕЗ на главы остаётся
захардкоженным CJK, и строка бэклога **283** называет именно этот раскол работой. Что добавляет поле — у
асимметрии появляется ИМЯ на проводе вместо того, чтобы прятаться в числе глав: не-CJK книга, сказавшая
`none`, говорит правду о том, что этот движок умеет, и потребитель откажется предлагать заказ в главах
вместо того, чтобы предложить его против разреза, которого никто не делал. Сама классификация
пар-агностична — она спрашивает, КАКОЙ ПУТЬ провёл границы, — и скажет `detected` без единой правки Go в
тот день, когда разрез научится читать данные пары.
**(ж) Форма леджера в `finished`.** `Finished.Money{units_resolved, units_deferred}`, считанный из тех же
по-волновых счётчиков, из которых публикуется `progress`, — так текущий канал и терминальный не могут
назвать разные числа. ⚠ **`resolved`, а НЕ `delivered`**: счётчик инкрементируется на юните, который
дошёл до вердикта, включая флагованный с пустым текстом. Назвать это «доставлено» — ровно та ложь, ради
прекращения которой объёмный леджер разделили на `Delivered`/`Flagged`, и ложь, по которой покупатель
стал бы действовать.
**⛔ И ЧИТАТЕЛЬ С ПИСАТЕЛЕМ ПРИБИТЫ ОДНИМ АКТОМ — по норме, которую оркестратор передал в ходе пака.**
Две пары:
1. **Размер вызова.** `baseMaxTokensFor` вынесен из `runStage` в ОДНУ функцию, которой пользуются и
исполнитель, и проекция. Проекция, считающая размер по-своему, разъехалась бы с резервациями, которые
прогон реально делает, — и разъезд был бы невидим, обе стороны выглядят как числа.
2. **Цена на двух поверхностях.****Полная батарея поймала у меня ровно этот класс:**
`TestManifestServesTheReadModelsIdentically` покраснел — `status` отвечал ценой при наличии сайдкара
и `nil` без него. Манифест — АКСЕЛЕРАТОР, а акселератор, меняющий ОТВЕТ, это второй источник истины.
Лечение: `readModelPrice` отвечает на обоих путях, и обе ветки кончаются в одной `projectBook`.
---
## 5. Секция, которую требует §5-тер(а): ГДЕ ПРОМТ НАПИСАН НЕ ТАК
Читаю промт последней и называю расхождения — не как вежливость, а как заказ владельца.
1.**§4(3) внутренне противоречив.** Он называет число, которое должно ехать наружу
(`denied estimate=$…`), и следующей фразой запрещает его класс («цены моделей, стадий и вызовов не
выходят»). Исполнить дословно значило бы нарушить соседнее предложение. Разрешено заменой на
недостачу; согласовано.
2.**§4(1) заказывает «обрамление как ДАННЫХ», которое в текстовой форме нельзя выполнить, не нарушив
§4(6).** Единственный дом для такой фразы — `lang/data/injection.txt`, а он побайтно входит в
`EmbeddedVersion()``cutTag``unit.id` каждой книги. Промт этого не знал; сделано структурно.
3.**§3 п.1 карты чтения указывает на секцию, которой уже нет — но это НЕ дефект промта, и правку
вношу по указанию оркестратора.** «`15-money-path.md` §«ОТКРЫТАЯ РАЗВИЛКА 04.09»» переименована в
«РАЗБОР 04.09: КНИГУ НЕЛЬЗЯ ДОЧИТАТЬ…» — и переименовал её оркестратор УЖЕ ПОСЛЕ выдачи промта. То
есть на момент выдачи карта была верна, а сломал её последующий редакторский акт. Разница не
формальная: «промт написан не так» лечится редактурой промтов, а «карта сломана после выдачи» —
дисциплиной того, кто правит доки под работающей сессией. Секцию нашла грепом по заголовкам.
4.**§4(2) «защёлка на уровне ВОЛНЫ» обоснована доводом, который больше не работает.** Промт пишет:
«Без защёлки W воркеров дадут W путей ошибки вместо одной остановки». Довод снят самой конструкцией:
`haltErr` — отдельный слот с первым-побеждает, путь ошибки один при любом числе воркеров. Защёлка
ОСТАВЛЕНА (оркестратор подтвердил другим доводом — продуктовым: книгу читают подряд, и непрерывный
префикс из шести глав лучше решета из восьми), но её цена названа: она читает «этот ОДИН элемент не
влез» как «денег нет ни на что», а при разбросе резервов внутри волны 2.976× (замер разбора 04.09) и
удвоении бюджета на ретрае это ложно — юниты, которые ВЛЕЗАЛИ, не начинаются. Заведено строкой
бэклога.
5.**§4-тер риск (б) назван мягче, чем он есть.** «частичная волна под потолком — НОВАЯ форма для этих
счётчиков» — на деле счётчики не просто новую форму получают, они СОВРУТ по шести полям из семи, и
лечение не в поправке, а в отказе их публиковать (см. §2-бис).
6.**§5 «модель называй имени» — опечатка** («по имени»). Названо: помощник — **Fable 5**, один агент,
долгий разговор, два круга.
7.**§4(6) с `StreamVersion` исправлен верно** — прежняя редакция запрещала бамп и заказывала поле; в
выданной мне редакции противоречие снято, и это тот случай, когда правка промта сработала.
---
## 6. Самопроверка ИСПОЛНЕНИЕМ
### 6.1. Батарея — ЗЕЛЁНАЯ
```
$ cd backend && make battery # go build · go vet ·+tags live ·+tmvet · golangci-lint · go test -race
0 issues. # golangci-lint
ok textmachine/backend/cmd/tmctl 42.422s
ok textmachine/backend/internal/archguard 58.448s
ok textmachine/backend/internal/chunk 1.658s
ok textmachine/backend/internal/membank 31.507s
ok textmachine/backend/internal/pipeline 246.730s
ok textmachine/backend/internal/store 56.399s
…19 пакетов с тестами, ни одного FAIL…
MAKE_EXIT=0
--- did NOT run (no stand data; see battery-stand) ---
--- SKIP: TestMinerFullBookParity · TestHelperEventsRun · TestHelperKillLoop
```
**Это прогон на СДАВАЕМОМ дереве, вместе с дофиксом приёмки — не на приземлённом паке.** Батарея
гонялась заново после КАЖДОЙ волны правок: прежнее «зелено» после правки не значит ничего. Последний
прогон — на ЗАМОРОЖЕННОЙ копии финального дерева, в одном заходе с каталогом; предыдущий пришлось
выбросить целиком, потому что дерево изменилось под ним (записи 5051), и результат о дереве, которого
больше нет, — не результат.
**19 пакетов ok · 0 упавших тестов · 0 issues линтера · 3 скипа (все — стендовые, требуют данных вне
репозитория; ровно те же три, что и до пака).** Сюда входит `go vet ./...`, `go vet -tags live`, оба
прохода через СОБСТВЕННЫЙ анализатор зоны (`bin/tmvet`) и `gofmt -l` — именно `tmvet` и поймал у меня
хенд-билд `config.Stage` (§6.3 находка 23).
⚠ **ГОНЯЛОСЬ ЦИКЛОМ ДО ЧИСТОТЫ, А НЕ ОДНИМ ПРОХОДОМ (D39.197: утверждение о проверке живёт ровно до
следующей правки), и число прогонов само по себе факт: ШЕСТЬ.** Первые пять были остановлены или красны:
`MAKE_EXIT=2` на собственном анализаторе зоны · `--- FAIL` под `-race` на моём же тесте ожидания (он
полагался на везение, а не на состояние) · три прогона убиты мной, когда очередной собственный проход
находил дефект и делал результат утверждением о дереве, которого больше нет. Шестой — на замороженном
дереве, после того как ВСЕ находки первого полного прогона каталога были закрыты.
`internal/pipeline` шёл **272 с** (в пятом прогоне — 450 с) против ~95 с на тихой машине: параллельные
сессии зоны на той же машине, разброс, о котором предупреждает сам `Makefile`. Ни одного упавшего
утверждения ни в одном из зелёных прогонов.
### 6.2. Дифф `^func Test` — ИСПОЛНЕНИЕМ, не по памяти
```
$ git diff -U0 -- backend | grep -E '^-func Test' # пусто: ни одна функция не удалена и не переименована
$ git grep -h '^func Test' <до пака> -- backend | wc -l # 1172
$ grep -rh '^func Test' backend --include=*_test.go | wc -l # 1213
```
**+41 тестовая функция: 39 в трёх новых файлах** (`wirefence_test.go` 7 · `moneystop_test.go` 17 ·
`priceprojection_test.go` 15 — сумма ровно 39) **и две дописаны в существующие**:
`cmd/tmctl/render_test.go` (запись 41) и `internal/chunk/ingest_test.go` (запись 54). Обе ДОБАВЛЯЮТ
функцию, не трогая ни одной существующей. Ни одна существующая функция не удалена и не
переименована — вывод грепа по `-func Test` пуст, а два протухших теста починены ВНУТРИ тела (§7).
**Дофикс приёмки добавил четыре из этих 40** — их видно тем же диффом против уже приземлённого пака
(`git diff -U0 -- backend | grep -E '^\+func Test'`): `TestASidecarFromAnOlderBuildStillYieldsAPrice`
(запись 44) · `TestAnEngineWrittenDocumentDropsTheRowInsteadOfKillingTheRun` (запись 42) ·
`TestTheMoneyLedgerFollowsTheCeilingAndNotTheOutcome` (запись 40) ·
`TestTheHumanManifestCallsTheBookLevelFigureACeiling` (запись 41) ·
`TestTXTStructureIsDeclaredOnlyWhenTheFormatDrewEVERYBoundary` (запись 54 — дописан ПОСЛЕ полного
прогона каталога, потому что его отсутствие предъявила выжившая посадка).
### 6.3. Что я нашла В СВОЕЙ ЖЕ РАБОТЕ (адверсариальный проход, помощник Fable 5)
Плотность находок на собственной работе не ниже, чем на чужой (D39.197) — подтвердилось:
| # | находка | статус |
|---|---|---|
| 1 | ⛔ `waitForSettle` возвращал `false` и на «нечего ждать», и на `ctx.Done()`, а вызывающий обе трактовал как ФИНАЛЬНЫЙ отказ ⇒ прогон, остановленный Ctrl-C или отменённый инфра-падением соседа, публиковал `ceiling` с недостачей, и платформа записывала `paused`, требуя денег за стоп, к деньгам отношения не имевший | ЗАКРЫТО: три исхода (`waitSettled`/`waitNothingInFlight`/`waitAborted`), пин `TestACancelledWaitIsNotAMoneyStop`, посадка `MS-cancelled-wait-is-not-money` |
| 2 | ⛔ Новая ветка `terminal()` («ошибка + потолок был») стояла ВЫШЕ `sigStop` и `context.Canceled` ⇒ банк-стоп на книге, коснувшейся потолка, отчитывался `failed` при exit 3, а пойманный SIGTERM — `failed` при exit 5. Два канала противоречат друг другу об одном прогоне | ЗАКРЫТО: ветка перенесена последней, порядок объявлен утверждением в комментарии |
| 3 | ⛔ Гонка «Reserve закоммичен — `enter()` ещё нет» давала ЛОЖНЫЙ финальный стоп | ЗАКРЫТО: `Reserve` под мьютексом шлюза |
| 4 | ⛔ Денежный стоп в ЧЕРНОВОЙ волне пускал волну РЕДАКТУРЫ: `draftByKey` строится по ВСЕМ чанкам, поэтому не начатый член находится (`ok == true`) с нулевым результатом — не флагован, текст пуст — и редактор ОПЛАЧИВАЛСЯ за юнит с дырой, который потом отгружался | ЗАКРЫТО: ранний возврат + `drafted []bool`, пин `TestAMoneyStopInTheDraftWaveNeverStartsTheEditWave`, посадка `MS-draft-stop-does-not-edit` |
| 5 | ⛔ Кормилец продолжал раздавать индексы после защёлки примерно в половине случаев (равномерный выбор `select` среди готовых ветвей) | ЗАКРЫТО: неблокирующая проверка `latched` перед `select` |
| 6 | ⛔ `MoneyLedger.UnitsDelivered` считал РАЗРЕШЁННЫЕ юниты, включая флагованные с пустым текстом, а док поля обещал «книга, которую покупатель может читать сейчас» | ЗАКРЫТО до выхода 1.3 на провод: поле переименовано в `units_resolved`, ложь снята |
| 7 | ⛔ Мой собственный тест фолда версии был ВЫРОЖДЕННЫМ: сравнивал два разных набора строк и доказывал только что строки разные — посадка `WF-version-fold-scope` его ПЕРЕЖИЛА | ЗАКРЫТО: тест сделан ВРЕМЕННЫМ (пины хешей, снятых сборкой `32be78a`), посадка краснеет |
| 8 | ⛔ Моя посадка `WF-length-bound` не собиралась (`1 << 30` рун в `strings.Repeat` съедал память) — каталог показал `NOTHING`, а не `RED` | ЗАКРЫТО: посадка снимает ПРОВЕРКУ, а не двигает константу |
| 9 | ⛔ `waitForGate` звал `t.Fatalf` ИЗ ПОРОЖДЁННОЙ ГОРУТИНЫ — в Go это `runtime.Goexit` не на той горутине: утверждение теряется, а прогон висит до таймаута пакета. Под посадкой я получила бы необъяснимый таймаут вместо КРАСНОГО | ЗАКРЫТО: возврат `bool`, проверка в тестовой горутине |
| 10 | ⛔ Посадка `PP-step-max-ignores-retries` пережила бы ВХОЛОСТУЮ: единственное утверждение про `step_max` сравнивало его с резервом attempt 0 | ЗАКРЫТО: `TestStepMaxCountsTheRetryThatDoesNotDegrade` |
| 11 | ⛔ Полная батарея: `status` отвечал ценой при наличии сайдкара и `nil` без него | ЗАКРЫТО: `readModelPrice` отвечает на обоих путях, обе ветки кончаются в одной `projectBook` |
| 12 | ⛔ Проекция цены считала цену юнита ДВАЖДЫ — в цикле манифеста и заново внутри книжной свёртки. Две деривации одного числа, и семь проходов по тексту книги там, где хватает трёх; на 23-мегабайтной книге манифест — акселератор, купленный секундами | ЗАКРЫТО: один проход `projectBook`, `unitPrice` несёт `PromptTokens` |
| 13 | ⛔ `WireUnfit` материализовал `[]rune(s)` на КАЖДОЙ строке банка КАЖДОЙ инъекции КАЖДОГО вызова — аллокация на горячем пути ради вопроса, которому она не нужна | ЗАКРЫТО: проход по строке со счётчиком рун |
| 14 | ⛔ **Проекция роняла ИСХОДНИК из промпта каждой стадии после первой.** Исполнитель рендерит ВСЕ стадии через `RenderVars{Text: ch.Text, Draft: prev}` (`stagerun.go:73`), а редактор БИЛИНГВАЛЕН по ратифицированному решению (D30.1) — он получает исходник рядом с черновиком, и это единственная причина, по которой он умеет привязывать канон к исходному термину. Проекция давала поздним стадиям только черновик ⇒ **≈ ⅙ ожидаемого счёта юнита, в сторону ЗАНИЖЕНИЯ** — той самой, в которой платформа продаёт книгу дешевле себестоимости | ЗАКРЫТО: `promptTokens` спрашивает ШАБЛОН (`{{text}}`, тот же `sourcePlaceholder`, по которому движок уже считает echo-exposure), а не позицию стадии — монолингвальное плечо ценится без исходника теми же строками. Пин `TestTheProjectionChargesTheSourceToEveryStageThatAsksForIt`, две посадки |
| 15 | ⛔ **Проекция считала few-shot безусловно, а исполнитель шлёт его по тоглу.** `SystemFor(fewShotEnabled(st))` (`render.go:159-170`) добавляет блок только когда `few_shot` включён, а у боевого c1-редактора он ВЫКЛЮЧЕН при том, что блок в шаблоне лежит — больше тысячи байт. Сумма полей завышала КАЖДЫЙ edit-вызов КАЖДОЙ книги на этом плече, и невидимо: цена на 20 % выше выглядит ровно как цена | ЗАКРЫТО: спрашиваются функции исполнителя. Пин `TestTheProjectionChargesTheFewShotBlockOnlyWhenTheStageSendsIt`, посадка |
| 16 | ⛔ **`TestWaitingForHeadroomDeliversTheWholeBook` доказывал не то, что заявлял — предъявлено ИСПОЛНЕНИЕМ.** В логе `-v` оба черновика допущены ОДНОВРЕМЕННО, строки «reservation refused … waiting» нет ни одной: книга доставлена потолком, который ни разу не отказал. То есть **положительная половина строки 277 не исполнялась ни одним тестом**, и посадка `MS-wait-for-settle` этот тест бы пережила | ЗАКРЫТО: у шлюза появился счётчик СРАБАТЫВАНИЙ (`waitsSoFar`), тест требует `waits > 0` ДО всего остального, потолок пере-подобран (1.85 оценки). Предъявлено: `waits_this_run=1` в логе |
| 17 | ⛔ **Фикстурный потолок был завышен ×1.114**: `callCeilingUSD` клал в промпт `plan.inject` = 800 токенов, а безглоссарная фикстура инъекцию не шлёт вовсе. Из-за этого множитель 1.9 давал ≥ 2 резерваций — окно «по одному за раз» превращалось в «влезают оба», что и породило находку 16 | ЗАКРЫТО: аллоуанс убран, оценка теперь совпадает с реальной побайтно (0.007016 в логе исполнителя и в фикстуре) |
| 18 | ⛔ **Два утверждения `TestResumeAfterAMoneyStopRePaysNothing` НЕ МОГЛИ упасть:** «committed упал» невозможно (committed монотонен), а `secondPass >= firstPass + rec.count()` арифметически недостижимо, поскольку `secondPass := rec.count() - firstPass`. Оба читались как проверки и ни одна не краснела | ЗАКРЫТО: утверждение о ЧИСЛЕ вызовов через оба прогона (`3 юнита × 2 стадии == 6`) — пере-покупка ловится именно им |
| 19 | Лог-строка ожидания печаталась ПОСЛЕ пробуждения и читала счётчики тогда: оператор видел слова «still in flight … waiting» рядом с `in_flight=0 queued=0` — фраза, противоречащая собственным числам | ЗАКРЫТО: строка говорит «settle освободил запас, переспрашиваем», счётчики названы `*_now` |
| 20 | Якорь посадки `PP-price-reaches-status` умер, когда `status.go` перешёл на `readModelPrice` — каталог дал бы `ROTTED`, то есть громко, но запись бы ничего не утверждала | ЗАКРЫТО: якорь пере-навешен на нынешнее выражение того же свойства |
| 21 | Пере-утверждение в собственном комментарии и отчёте: «`structure` пар-агностичен по построению». На деле `detected` сегодня CJK-only (`lang.DefaultCJKSection()`КОНСТАНТА), и это известная асимметрия строки бэклога **283** | ЗАКРЫТО: формулировка исправлена в обоих местах, граница названа прямо |
| 22 | ⛔ **Проекция ценила ЧЕРНОВИК ПО ЮНИТУ, а исполнитель зовёт его ПО ЧАНКУ** (`runDraftChunk` — один рендер и один вызов на члена; `runEditUnit` — на юнит). Смещение сразу в две стороны: шаблон и инъекция едут в КАЖДОМ черновом вызове, значит счёт занижался; а выходной бюджет черновика сайзится от ОДНОГО чанка, значит `step_max_usd` — число, по которому платформа ставит минимальную покупку, — завышался в число членов. На боевом плече второе прячет модельный флор, первое — проценты; на плече без флора не прячется ничто | ЗАКРЫТО: `projectUnit` и `stepMaxForUnit` ходят по членам для черновых стадий и по юниту для остальных. Пин `TestTheDraftIsPricedPerMemberChunkAndTheEditPerUnit` (та же книга одним чанком и двумя: цена ОБЯЗАНА вырасти, шаг ОБЯЗАН не вырасти), посадка |
| 23 | ⛔ Мой новый тест строил `config.Stage` литералом — и **собственный vet-анализатор проекта его отказал** (`archguard.StageSeam`, D39.87: литерал молча роняет reasoning и temperature). В списке исключений есть файл того же класса, и добавить туда свой было бы ослаблением гейта ради удобства | ЗАКРЫТО БЕЗ ПРАВКИ ГЕЙТА: фикстура строит стадию через сам шов `config.InternalCall{…}.Stage()`, а `few_shot` ставится после — это и есть поле под тестом, которое шов намеренно оставляет нулевым |
| 24 | ⛔ **Черновые стадии не ЦЕПЛЯЛИСЬ друг за друга.** `runStageSequence` прогоняет чанк через КАЖДУЮ черновую стадию по порядку, каждая читает выход предыдущей; ничто в загрузчике не запрещает вторую translator-стадию (`escalate_to` ограничен ролью, число стадий — нет). Плоской суммой по стадиям вторая ценилась бы против ИСХОДНИКА, а черновик юнита вышел бы ВДВОЕ больше правды — и редактор получил бы вход, которого не бывает | ЗАКРЫТО: член — внешний цикл, стадии цепляются внутри него, как в `runStageSequence` |
| 25 | ⛔ **ЦЕЛЫЙ СПИСОК УТВЕРЖДЕНИЙ ОТЧЁТА НЕ ПОДКРЕПЛЯЛСЯ НИ ОДНИМ АРТЕФАКТОМ** — четвёртый круг адверсариального прохода искал именно это, и нашёл двенадцать. Главные: **(а)** всё поведение параметра `mandatory` было доводом в комментарии — каждая фикстура, где отказывали опциональному вызову, последовательна, поэтому `inFlight == 0` давал мгновенный стоп при ЛЮБОМ значении флага, и переворот всех трёх опциональных сайтов в `true` оставил бы батарею зелёной; **(б)** порядок ветвей `terminal()` — регрессия, случившаяся В ЭТОЙ СМЕНЕ, — держался комментарием; **(в)** идемпотентность кадра `ceiling` не проверял никто (единственный тест, читающий журнал при стопе, шёл на одном воркере, то есть на ОДНОМ отказе); **(г)** проводка `settleCannotHelp` (предикат запинен, `if` — нет; ⚠ НЕ ЗАКРЫТА — см. §9 п.7, состояние почти недостижимо и я это доказываю, а не обхожу); **(д)** `book_once_usd` проверялся только на собственном нуле — терминология выключена во всех фикстурах, `return 0` пережил бы всё | ЗАКРЫТО ПЯТЬЮ НОВЫМИ ТЕСТАМИ И ШЕСТЬЮ ПОСАДКАМИ. Правило «кто может ждать» ПЕРЕЕХАЛО из `if` у вызывающего В САМ ШЛЮЗ (`waitForSettle(ctx, mandatory)`, четвёртый исход `waitNotAllowed`, счётчик `optionalTurnedAway`) — именно чтобы оно оставляло после себя ФАКТ, а не комментарий |
| 26 | ⛔ **Черновые стадии не цеплялись и фолбэк «нет translator-стадии» врал по выходу.** Две translator-стадии структурно загружаются (валидация проверяет РОЛИ и не считает их); исполнитель ведёт чанк через все по очереди, каждая читает выход предыдущей. Плоской суммой вторая ценилась бы против ИСХОДНИКА, а черновик юнита вышел бы вдвое. Отдельно: конвейер БЕЗ translator-стадии — его первая стадия сама переводит, значит её выход это фертильность пары, а не число токенов исходника (занижение на весь коэффициент) | ЗАКРЫТО: `walkDraftCalls` — ОДИН обход, которым пользуются обе арифметики; два разных фолбэка для входа и выхода. Пин `TestAPipelineWithNoTranslatorStagePricesItsFirstStageAsTheTranslation` (та же книга, две калибровки фертильности — цена ОБЯЗАНА разойтись), посадка |
| 27 | ⛔ **И ПРОДОЛЖЕНИЕ НАХОДКИ 16, которое стоит отдельной строки: после того как утверждение о механизме появилось, тест стал ПАДАТЬ — под `-race`.** Он полагался на то, что два воркера успеют зарезервировать ОДНОВРЕМЕННО, а это не состояние, а везение: в логе батареи chunk 0 сеттлился раньше, чем chunk 1 вообще запрашивал допуск, — потолок не может отказать в том, о чём не спросили параллельно. ⚠ **Это ровно тот класс «краснота держится на постороннем обстоятельстве», который мне было велено искать у себя, — и поймала его НЕ я, а батарея под `-race`, потому что мои точечные прогоны шли без него.** Отдельно стоит сказать, что утверждение сработало как задумано: тест УПАЛ громко вместо того, чтобы пройти вхолостую | ЗАКРЫТО: столкновение сделано СОСТОЯНИЕМ — первый вызов удерживается на фейк-провайдере, пока шлюз не покажет очередь, и только потом отпускается. Предъявлено: `-race -count=3`, три прохода |
| 28 | ⛔ **ШЕСТАЯ ЛОЖНАЯ ЗЕЛЕНЬ, И ЭТУ Я НАШЛА САМА, ЗАПОДОЗРИВ АНОМАЛИЮ ВРЕМЕНИ.** Мой тест «футильный отказ не держит волну открытой» проходил за 5,6 с там, где по его собственной арифметике должен был упереться в 30-секундный таймаут. Разбор трассы: удерживаемый на фейк-провайдере вызов НЕ висел — он упирался в `attempt_timeout`, транспорт РЕТРАИЛ, и ретрай отвечал мгновенно (`latency_ms=5389` у вызова, который тест считал замороженным). Прогон кончался потому, что вызов завершился, три отказанных воркера УСПЕЛИ подождать и проснуться, а утверждение было про ситуацию, которой фикстура не строила | ТЕСТ УДАЛЁН, а не подкручен. Артефакт, проверяющий не то, что заявляет, ХУЖЕ отсутствующего: он читается как покрытие. На его месте — абзац, объясняющий, почему состояние почти недостижимо, и запись в §9 |
| 29 | ⛔ **ДВА МОИХ ЯКОРЯ ПРОТУХЛИ ОТ МОИХ ЖЕ ПОЗДНИХ РЕФАКТОРОВ**`MS-wait-for-settle` (условие ожидания переехало из `if` в шлюз) и `PP-draft-priced-per-unit-not-per-chunk` (цикл по членам уехал в `walkDraftCalls`). Каталог дал бы `ROTTED`: громко, но запись при этом не утверждает НИЧЕГО, а список посадок читается как покрытие. Ровно тот класс, который приёмка прошлого пака поймала у четырёх записей | ЗАКРЫТО ПРОВЕРКОЙ, А НЕ ГЛАЗОМ: скриптом сверила все 33 своих якоря на «резолвится РОВНО ОДИН раз» в текущем дереве — два не резолвились, пере-навешены на нынешние выражения тех же свойств, остальные 31 чисты. Проверку прогнала ПОСЛЕ каждого последующего касания кода |
| 30 | ⛔ **ПЕРВЫЙ ПОЛНЫЙ ПРОГОН КАТАЛОГА ВЕРНУЛ ЧЕТЫРЕ НЕОЖИДАННЫХ ИСХОДА, И ВСЕ ЧЕТЫРЕ — МОИ.** `183 RED · 3 SURVIVED · 1 NOTHING · 0 ROTTED`. Среди выживших — **`MS-latch-is-a-cancel`, красноту которой промт назвал ОБЯЗАТЕЛЬНЫМ УСЛОВИЕМ ПРИЁМКИ.** Скажи я «сдано» по зелёной батарее, пак ушёл бы в лендинг с невыполненным named-артефактом | ЗАКРЫТО ЧЕТЫРЬМЯ РАЗНЫМИ ПОЧИНКАМИ, каждая воспроизведена руками, а не принята с вывода инструмента — разбор ниже, строки 3134 |
| 31 | ⛔ **`MS-latch-is-a-cancel` выжила, потому что мой тест открывал ОКНО, КОТОРОГО В СЛОМАННОМ МИРЕ НЕ СУЩЕСТВУЕТ.** Он отпускал удерживаемый вызов, когда воркер ВСТАЛ В ОЧЕРЕДЬ, — но в очередь встают только в ПОЧИНЕННОМ мире; в сломанном отказ убивает волну сразу и в очередь не встаёт никто, так что триггер просто не срабатывал и утверждение читалось в обоих мирах одинаково. ⚠ **И сама посадка была неверной: починка из ДВУХ частей требует посадки из ДВУХ частей.** С ожиданием на месте одинокий `cancel()` почти безвреден — к моменту защёлки в полёте уже никого, — то есть посадка воспроизводила не до-паковый мир, а третий, которого никогда не было | ЗАКРЫТО: триггер перевешен на РЕФУЗ (`refusals ≥ 1 && inFlight ≥ 1`) — окно, существующее в ОБОИХ мирах, различает которые то, что прогон с ним ДЕЛАЕТ; посадка стала ПАРОЙ (снять ожидание И вернуть `cancel`), то есть буквально до-паковым поведением |
| 32 | ⛔ **`MS-volume-ledger-on-a-money-stop` выжила ДВАЖДЫ, по двум разным причинам, и вторая тоньше первой.** (а) Утверждение было ВЫРОЖДЕННЫМ: фикстура не давала объёмного гранта вовсе, поэтому `res.Volume` был nil при любом коде. (б) Даже с грантом посадка выжила СНОВА: `res.Volume` прикладывается МЕЖДУ волнами (`waverun.go:200-201`), а мой потолок останавливал прогон внутри ЧЕРНОВОЙ волны — путь, который его вообще не прикладывает, так что удержание было no-op и там | ЗАКРЫТО: отдельный тест `TestASpendStopReportsNoVolumeLedger` с НАСТОЯЩИМ грантом (`MaxUnits = 3`), одним воркером и потолком, рассчитанным оплатить ВСЕ черновики гранта и кончиться на ПЕРВОЙ редактуре. Предъявлено исполнением: под посадкой тест падает текстом «stopped on the VOLUME ceiling … 3 NEW unit(s) delivered» — леджер заявляет ТРИ доставленных для прогона, у которого третья редактура отказана |
| 33 | ⛔ **`PP-no-translator-stage-completion` выжила — и ВЫЖИВАНИЕ БЫЛО НАХОДКОЙ: она нашла МЁРТВЫЙ КОД.** `walkDraftCalls` возвращал размер черновика ДАЖЕ когда ни одной черновой стадии не отработало ⇒ фолбэк для конвейера без translator-стадии был недостижим, и первая стадия такого конвейера ценилась с входом-черновиком, которого она не получает. Мутация не могла изменить то, до чего не доходят, — и именно это её выживание и означало. **Каталог сработал не как гейт, а как ПРИБОР** | ЗАКРЫТО: `walkDraftCalls` возвращает 0, когда ни одна стадия не отработала; ветка стала живой |
| 34 | **`MS-optional-call-queues-for-headroom` дала `NOTHING`** — посадка удаляла блок целиком, `mandatory` становился неиспользуемым, сборка падала | ЗАКРЫТО: правка на компилирующуюся форму (`if false && !mandatory`) |
| 35 | ⛔ **И дедлок, который я сама себе устроила и сама сняла — одна нить с находкой 28, а не отдельный эпизод.** Ветка `r.Context().Done()` у фейк-провайдера и была причиной того, что «удерживаемый» вызов падал по `attempt_timeout`, а его ретрай отвечал мгновенно. Сняв её, я получила зависание `srv.Close()`, который ЖДЁТ незавершённые запросы: `defer`-ы LIFO, и `close(release)` стоял в очереди ЗА `Close()`. Пакет ВИС вместо того чтобы упасть — снова отказ в форме таймаута | ЗАКРЫТО: `defer close(release)` объявлен ПОСЛЕ `defer srv.Close()`, чтобы выполниться ПЕРЕД ним; причина записана в комментарий теста |
| 36 | Именной тест `TestUnderACeilingTheWaveFinishesWhatItAdmitted` под ПАРНОЙ посадкой падает текстом `the stop must be a ceiling halt, got … context canceled` — то есть ровно симптомом до-пакового дефекта: допущенный вызов убит отменой. Проверено прямым исполнением, а не по строке вывода инструмента | ПРЕДЪЯВЛЕНО |
| 37 | ⛔ **`MS-optional-call-queues-for-headroom` дала `NOTHING` ВТОРОЙ раз, и теперь по другой причине: посадка краснела ЗАВИСАНИЕМ.** Первая причина была сборкой; после починки посадка собиралась, но под ней опциональный вызов ПАРКОВАЛСЯ на settle, которого в юнит-тесте не бывает, — пакет упирался в таймаут, и каталог печатал «the planting did not build, or the run died», что неотличимо от нагрузки машины. **Восьмой случай того же класса за смену, и снова мой** | ЗАКРЫТО: у ожидания появилась ГРАНИЦА (`context.WithTimeout` 2 с). Предъявлено: под посадкой тест падает за 2,00 с с текстом об опциональном вызове, занявшем деньги обязательного, — вместо таймаута пакета |
| 38 | Течь резервации на ветке `json.Marshal(resp.Usage)` (возврат без release и без settle) | ПОЧИНЕНО — см. §8 |
| 39 | ⛔ **Довод «та же дверь» и утверждение в коде «discloses nothing about what any one call cost» — ОБА ЛОЖНЫ.** Довод пришёл от оркестратора и был принят мной без проверки командой; утверждение опровергнуто ЖИВЫМ ЗОНДОМ, а не рассуждением (`shortfall + ceiling committed reserved` восстанавливает оценку отказанного вызова с точностью до микродоллара округления) | ПОЧИНЕНО — §3; норма записана: **посылку, на которой стоит утверждение в коде, зона проверяет САМА, кем бы она ни была принесена** (F1 приёмки) |
| 40 | ⛔ **`Finished.Money` документирован правилом присутствия, которого нет:** «present only on `outcome: ceiling`». Движок вешает леджер на ПЯТИ терминальных ветвях — потолок больше не гасит соседей, и прогон, защёлкнувшийся на деньгах, уходит `failed`/`stopped`/`bank_stop`. Потребитель этого файла построил бы ровно обратное: леджер отсутствовал бы именно в тех выходах, ради которых он и нужен | ПОЧИНЕНО — правило переписано в обе стороны (`ceiling` тоже НЕ влечёт поле). ⚠ Нашли ДВА верификатора приёмки НЕЗАВИСИМО — сильнейший сигнал, какой может получить неверная фраза |
| 41 | ⛔ **Книжная часть цены печаталась как ПРОГНОЗ:** «$X of it is book-level and paid once». Терминологические проходы ограничены бюджетами, их вход не существует, пока нет черновиков ⇒ фигура — это ПОТОЛОК, который прогон не превысит. На книге в несколько глав она доминирует, и оператор читал бы короткую книгу как более дорогую за главу, чем длинную | ПОЧИНЕНО — `render.go` называет её ceiling отдельной строкой и печатает только когда она есть (F3 приёмки) |
| 42 | ⛔ **Забор убивал ОПЛАЧЕННЫЙ прогон об СОБСТВЕННЫЙ вывод движка:** намайненная дельта и авто-банк пишутся из ОТВЕТА МОДЕЛИ, а грузились строгим `ParseBankSeed` — одна негодная руна в модельном термине, и `loadMinedDelta`/`loadAutoBank` отдают ошибку наверх, материализация банка превращает её в мёртвый прогон, а человека просят руками править файл, которого он не писал | ПОЧИНЕНО — расщепление по АВТОРСТВУ: `ParseEngineBankSeed` роняет РЯД, `ParseBankSeed` по-прежнему отказывает ДОКУМЕНТУ оператора. Пин `TestAnEngineWrittenDocumentDropsTheRowInsteadOfKillingTheRun`, посадки `DF-input-identity-is-stated-not-counted` сосед. ⚠ Приёмка назвала дельту; авто-банк — тот же класс и ВЕРОЯТНЕЕ (пишется из модели КАЖДЫЙ прогон), поэтому тест утверждает правило, а не вызывающего (F8 приёмки) |
| 43 | ⛔ **История в шапке забора льстила прошлому:** «инвариант держался ПО ПОСТРОЕНИЮ, пока у банка не появился внешний писатель». Ложно: намайненный термин — не текст постороннего, но и не наш, это ВЫВОД МОДЕЛИ, и он попадает в тот же system-блок. Недоверенный писатель пришёл с майнингом, а дверь коррекций лишь сделала его ЧЕЛОВЕКОМ | ПОЧИНЕНО в тексте (код покрывает оба по построению). ⚠ Ценность правки не в коде: история, говорящая «раньше это было невозможно», зовёт следующего читателя доверять НЕ ТОЙ границе (F9 приёмки) |
| 44 | ⛔ **У сайдкара ТРИ состояния, а комментарий и код знали ДВА:** отсутствует · протух · АКТУАЛЕН И СТАРШЕ ЭТИХ ПОЛЕЙ. `price`/`structure` аддитивны, версия документа намеренно не двигалась ⇒ файл прежней сборки проходит версию, `selfConsistent` и ключ валидности, возвращается «актуальным» и цены не несёт. Ветка выше отдавала `nil`, фолбэк — тот самый, который комментарий называет инвариантом, — НЕ РАБОТАЛ, и книга молча отчитывалась без цены ровно на поверхности, которую платформа читает ПЕРЕД покупкой | ПОЧИНЕНО — живость аддитивного поля спрашивается О ПОЛЕ, а не о документе (V2-3 приёмки) |
| 45 | ⛔ **Книга платила за проход, которого не бывает:** `bookOnceUSD` складывал `classify_budget_usd` безусловно, тогда как `classify_types` гейтит саму фазу, а загрузчик требует положительный бюджет ТОЛЬКО при включённом тумблере — на поставляемой ветке это ПОЛОВИНА книжной фигуры ($1.00 из $2.00). ⚠ **И ТЕСТ ЭТОЙ ФУНКЦИИ ПИНИЛ НЕВЕРНОЕ ЧИСЛО** — гейт, защищающий дефект: ревью, читающее зелень, такого не ловит НИКОГДА | ПОЧИНЕНО, тест пере-пинен (V2-5 приёмки) |
| 46 | ⛔ **«Формат сам провёл границы» значило ВСЕ границы, а спрашивалось про ОДНУ:** структура называлась `declared`, если в файле встретился хоть один form feed — книга с пятьюдесятью главами, найденными по заголовкам, и одним случайным `\f` отчитывалась словом файла о сорока девяти ДОГАДКАХ | ПОЧИНЕНО — честная проверка в том, добавил ли путь заголовков хоть что-то: `len(parts) > 1 && len(chapters) == len(parts)` (V2-7 приёмки) |
| 47 | ⛔ **Тождество выводилось из РАВЕНСТВА ЧИСЕЛ:** `promptTokens` спрашивал `inputTokens != sourceTokens`, чтобы не зарядить исходник дважды. Два счётчика совпадают, не будучи одним текстом — пара с фертильностью около 1.0 даёт черновик размером с исходник, и стадия, которая исходник ПОЛУЧАЕТ, молча переставала за него платить | ПОЧИНЕНО — идентичность ОБЪЯВЛЯЕТ вызывающий (`inputIsSource bool`), арифметика её не знает; посадка `DF-input-identity-is-stated-not-counted` (V2-8 приёмки) |
| 48 | Ссылка на идентификатор `SourceEchoExpected`, которого в Go больше нет, и на причину, которая с тех пор изменилась (эхо-исключение сегодня покрывает ОБА банк-роля) — три носителя: `paidtail.go`, `MONEY_HONESTY_REPORT.md`, `DISCLOSURE_LAW_DESIGN.md` | ПОЧИНЕНО с НУЛЕВЫМ сдвигом строк (в эти файлы целятся ЧУЖИЕ якоря по номерам — сдвинь я строку, починил бы два своих носителя, сломав пять чужих якорей). ⚠ Носитель в `paidtail.go` якорным линтом не ловится и не поймается впредь |
| 49 | ⛔ **ЧЕТЫРЕ ПОСАДКИ ПРОТУХЛИ ОТ МОЕГО ЖЕ ДОФИКСА** (`promptTokens` получил параметр, колбэк `walkDraftCalls` — пятый аргумент, `bookOnceUSD` — гейт): `PP-source-rides-every-stage-that-asks`, `MS-source-double-charged`, `PP-draft-priced-per-unit-not-per-chunk`, `PP-book-once-charge`. Ни один гейт этого не ловит: нерезолвящийся якорь — не красная посадка, а ОТСУТСТВУЮЩАЯ | ПОЧИНЕНО — пере-нацелены; проверка гоняется после КАЖДОГО касания кода. Это уже ЧЕТВЁРТЫЙ раз за смену, и он подтверждает: класс не в невнимательности, а в том, что каталог — код БЕЗ КОМПИЛЯТОРА |
| 50 | ⛔ **«ЭТО НЕЧЕМ ПОКРЫТЬ» — И ЭТО БЫЛА ОТГОВОРКА.** Правку F3 (`render.go`) я СНАЧАЛА ОБЪЯВИЛА в отчёте непокрываемой: «носитель — текст, красить нечем, проверено чтением». Держалось ровно до попытки: `renderManifest` принимает `io.Writer` и манифест, тест пишется в двадцать строк без стенда и без денег — а на эту функцию не было ни одного теста ВООБЩЕ | ПОЧИНЕНО — `TestTheHumanManifestCallsTheBookLevelFigureACeiling` + две посадки (сказать неправду · сказать лишнее). Класс записан как есть: **«нечем покрыть» — самая дешёвая из неправд, какие смена пишет о себе, и от честной её отличает ОДНА ПОПЫТКА** |
| 51 | ⛔ **МОЙ СОБСТВЕННЫЙ ДОФИКС ПРОМЕНЯЛ ГРОМКУЮ СМЕРТЬ НА ТИХОЕ ИСЧЕЗНОВЕНИЕ.** Расщепление по авторству (запись 42) роняет негодный ряд вместо отказа документу — и роняло МОЛЧА: намайненный термин пропадал из всех запросов без следа, а оператор, видящий его в файле и не видящий в переводе, не имел ЧЕГО прочитать. Это ровно тот класс, о котором предупреждает шапка CLAUDE.md: обход тихо становится нормой | ПОЧИНЕНО — `BankSeed.Dropped` выносит имена наверх (загрузчик логгера не имеет и не должен), `Runner.warnDroppedRows` говорит рядом с уже существующим предупреждением о ДЕКЛИНЕ; пин расширен, посадка `DF-a-dropped-row-is-not-silent`. ⚠ Нашла своим же чтением дофикса, а не приёмкой: **починка, у которой обе половины не проверены, — половина починки** |
| 52 | ⛔ **ЧИСЛО В КОММЕНТАРИИ, КОТОРОГО НИКТО НЕ МЕРИЛ.** Комментарий записи 45 утверждал: «на поставляемой ветке это ПОЛОВИНА книжной фигуры ($1.00 из $2.00)». Проверено командой: `configs/pipeline-c1.yaml:170` = `classify_types: true` ⇒ на поставляемом конфиге фаза ИСПОЛНЯЕТСЯ, бюджет тратится по-настоящему, и дефект там не проявляется вовсе | ПОЧИНЕНО — фраза снята, вместо неё сказано, ГДЕ дефект кусает: конфиг с ВЫКЛЮЧЕННЫМ тоглом и оставшимся от прежней редакции бюджетом (загрузчик перестаёт валидировать ключ ровно тогда, когда фаза выключена). Дефект настоящий, ветка — нет. ⚠ Пришло от оркестратора через внешнего ревьюера; проверила своей рукой прежде, чем править (норма записи 39) |
| 53 | ⛔ **РАТИФИКАЦИЯ, НА КОТОРУЮ ОПИРАЛОСЬ УТВЕРЖДЕНИЕ В КОДЕ, ОТОЗВАНАа само утверждение было сужением ЧУЖОГО решения.** ПТ-35 — решение владельца, и 05.09, снимая «деньги на экране», он оставил дословно «цены моделей, стоимость стадий И ВЫЗОВОВ»; промт пака применяет запрет к ПРОВОДУ (строка 78), а не к экрану ⇒ живой замер верификатора попал в ПОСЫЛКУ ЗАКАЗА, а не в реализацию. Плюс овер-атрибуция: цитируемой ратификации «D39.106 §2» в журнале нет (D39.106 — про транзиентный юнит и `events.jsonl`) | ЗАКРЫТО: сперва помечено `pending owner` (поведение не тронуто), затем РАТИФИЦИРОВАНО владельцем — `D39.203`, «Можно, в форме „сколько добавить“»; шапка поля указывает на ноту, различение оценка/стоимость стало опорой решения. К вопросу владельца добавлено то, чего не видели ни оркестратор, ни я: **тензия внутри самого заказа** — п.(3) запрещает цене вызова выходить, п.(4) требует опубликовать `step_max_usd`, оценку САМОГО ДОРОГОГО одиночного вызова. ⚠ И обе величины — до-вызовные ОЦЕНКИ, а не сеттлмент: что вызов СТОИЛ, не раскрывает ни одна |
| 54 | ⛔ **ПОЧИНКУ V2-7 НЕ ДЕРЖАЛ НИ ОДИН ТЕСТ — предъявила ВЫЖИВШАЯ ПОСАДКА, а не чтение.** В `internal/chunk/*_test.go` слова `Structure` не было вовсе; два случая в `priceprojection_test.go:144-146` — КРАЙНИЕ (только form feed → `declared`, только заголовки → `detected`), а СМЕШАННОГО — одного случайного `\f` среди найденных заголовками глав — не держал никто. Ровно в нём дефект и жил | ПОЧИНЕНО — `TestTXTStructureIsDeclaredOnlyWhenTheFormatDrewEVERYBoundary` (вся линейка из трёх), посадка красна именно подтестом `one_stray_form_feed_among_matched_headers`. ⚠ Класс: **починка, у которой есть посадка, но нет теста, выглядит как покрытая ровно до полного прогона каталога** |
| 55 | ⛔ **МОЯ СОБСТВЕННАЯ ПРОВЕРКА «отличаются только комментариями» ДАЛА ЛОЖНУЮ ЗЕЛЕНЬ.** Утилита-стриппер не собралась (`error obtaining VCS status`), оба выхода вышли ПУСТЫМИ, `diff` сравнил пустоту с пустотой и напечатал «ИДЕНТИЧНО» — и я чуть не оперлась на это, доказывая законность склейки двух прогонов | ПОЧИНЕНО — размер выхода печатается рядом с вердиктом, пустой выход стал отдельной красной ветвью; результат пере-снят и верен (5020/5020 и 2710/2710 байт). ⚠ Тот же класс, что §6.4, но теперь на ИНСТРУМЕНТЕ ПРОВЕРКИ: **проверка, которая не умеет провалиться, ничего не проверяет** |
| 56 | ⛔ **ДВА КОММЕНТАРИЯ ОДНОГО КОММИТА ПРОТИВОРЕЧИЛИ ДРУГ ДРУГУ О ФОЛБЭКЕ ЦЕНЫ (F7 приёмки).** `manifest.go` объявлял фолбэк ИНВАРИАНТОМ («без него книга без сайдкара молча отчитывается без цены»), а `status.go` в шапке того же поля утверждал обратное: «absent when there is no current manifest — у фолбэка есть текст, но нет гарантии сайдкара, что он описывает ЭТУ резку». Второе описывало код ровно до того момента, как ТОТ ЖЕ коммит добавил фолбэк и сюда не вернулся. Потребитель построил бы «нет сайдкара ⇒ нет цены» и показал бы покупателю пустоту на той самой поверхности, с которой тот и спрашивает | ПОЧИНЕНО — шапка `StatusReport.Price` пере-написана: цена есть и БЕЗ сайдкара; совпадение двух поверхностей держится тем, что оба конца гоняют ОДНУ деривацию (`readModelPrice``projectBook`), а не тем, что один отказывается отвечать; и фолбэк описывает эту резку ПО ПОСТРОЕНИЮ, а не по гарантии. ⚠ Правка комментарная, предъявлено машинно: `status.go` без комментариев побайтно идентичен (20012 vs 20012) |
| 57 | ⛔ **ЯКОРНЫЙ ЛИНТ ДОКОВ Я НЕ ГОНЯЛА НИ РАЗУ ЗА СМЕНУ, а свой — после каждого касания кода.** Проверяла `mutations.json` собственным скриптом и не заметила, что у зоны есть ВТОРОЙ корпус якорей и готовый гейт на него (`docs/scripts/counts.py --lint`, зовётся пре-коммит-хуком при ЛЮБОМ коммите с непустым индексом). На дереве он показывал **6 битых якорей в `backend/docs/`**, из них 5 — долг зоны, старше моего пака (замерено: `RebillBasis string` стоял на 275 ещё в `81a89e9^`, а якорь звал 249), и 1 — свежая, от переноса промта в архив | ПОЧИНЕНО — все шесть пере-нацелены по СОДЕРЖИМОМУ, линт `0 проблемных якорей в 113 живых доках`, сверяемых по токену стало 194 → 200. ⚠ Класс: **у зоны было ДВА множества якорей и одна привычка** — та, которую я завела сама, а не та, под которую в репозитории уже стоял гейт. Собственная дисциплина вытеснила существующую |
| 58 | ⛔ **МОЯ ФОРМУЛИРОВКА ЛЕЧЕНИЯ КЛИНА НАЗЫВАЛА СМЕНОЙ ТО, ЧТО ЕЮ НЕ ЯВЛЯЕТСЯ.** §9 п.1 предлагал «деградацию отказа резервации на attempt 0 (**смена D2.3**)» — а `D2` УЖЕ кончается флагом («ретрай с бо́льшим `max_tokens` до капа, потом флаг»), и денежная ветка до него просто не доходит: ошибка уходит наверх из цикла попыток раньше. То есть это ДОСТРОЙКА существующей диспозиции, а не правка ратифицированного — разница решает, нужна ли ратификация вообще | ПОЧИНЕНО — сверено по телу ноты, формулировка заменена, добавлено требование оркестратора: **причина флага обязана ОТЛИЧАТЬСЯ от исчерпания регенераций**, иначе пометка врёт о причине (`D39.204`, строка бэклога 291). ⚠ Поймал оркестратор чтением журнала; я предлагала правку ратифицированной ноты, не открыв её тела |
### 6.4. ⛔ ЧЕТВЁРТЫЙ КЛАСС ЛОЖНОЙ ЗЕЛЕНИ, найденный в этой смене
Выношу отдельно, потому что он не про мой пак, а про инструмент, и следующая смена наступит на него так
же.
**Посадка, чей отказ выглядит как ТАЙМАУТ, неотличима от нагрузки — и потому не является посадкой.**
**`SURVIVED`НЕ ОДИН ВЕРДИКТ, А ТРИ РАЗНЫХ, и различать их обязательно** (разведение оркестратора,
которое моя формулировка «выживание ЕСТЬ сообщение о недостижимости» и позволила):
1. **посадка эквивалентна оригиналу** — дефект ПОСАДКИ, гарантия цела;
2. **посадка не воспроизводит прежний мир ЦЕЛИКОМ** — починка из двух частей, а вернули одну (см. 31);
3. **посадка недостижима, потому что код МЁРТВ** — находка о КОДЕ, добытая прибором (см. 33).
Только третий род приносит дефект; первые два его прячут. Читать зелёное как «гарантия держит» нельзя ни
в одном из трёх.
**И шестая форма, найденная последним прогоном: ТРИГГЕР, СУЩЕСТВУЮЩИЙ ТОЛЬКО В ПОЧИНЕННОМ МИРЕ.** Тест,
который ждёт состояния, чтобы что-то проверить, обязан ждать состояния, ДОСТИЖИМОГО И В СЛОМАННОМ МИРЕ —
иначе он не отличает миры, а просто не запускается в одном из них. Различать их должно то, что прогон с
этим состоянием ДЕЛАЕТ. Подкласс того же: **починка из двух частей требует посадки из двух частей**
вернув одну половину, воспроизводишь не прежний мир, а третий, которого никогда не было. ⚠ Этот подкласс
сформулирован ОРКЕСТРАТОРОМ 04.09 на его собственной ошибке («oldest wins») и ПОДТВЕРЖДЁН здесь вторым
независимым случаем — `MS-latch-is-a-cancel`. Атрибуция важна не для заслуг: со вторым случаем это
перестало быть анекдотом и стало классом, и следующий читатель должен видеть, сколькими случаями норма
куплена.
Механика: `t.Fatalf`, вызванный из ПОРОЖДЁННОЙ горутины, делает `runtime.Goexit` не на той горутине.
Утверждение не записывается, помощник молча умирает, а прогон, который он должен был разблокировать,
висит до таймаута ПАКЕТА. Мой `waitForGate` был написан именно так. Под посадкой, делающей ожидаемое
состояние недостижимым (`MS-wait-for-settle`, `MS-latch-is-a-cancel`), каталог получил бы не `RED`, а
`NOTHING` или таймаут — и в этой смене я списала бы его на нагрузку машины с полным основанием: `Makefile`
зоны сам документирует разброс `internal/pipeline` от ~95 с на тихом хосте до ~560 с при трёх параллельных
агент-сессиях, и таймауты здесь уже были средой.
⚠ **И его близнец, пойманный в этой же смене на этом же тесте: посадка, чья КРАСНОТА держится на
везении.** Стоп-утверждение «ожидание сработало» я добавила, и тест стал зелёным — на моих точечных
прогонах без `-race`. Батарея под `-race` его уронила: два воркера успевали резервировать одновременно не
всегда, а потолок не может отказать в том, о чём не спросили параллельно. Утверждение отработало верно
(упало громко), но фикстура строила НАДЕЖДУ, а не состояние. Лечение то же, что и у первой находки:
удерживать вызов на фейк-провайдере, пока шлюз не покажет очередь.
**И ПЯТАЯ ФОРМА, найденная в этой же смене у меня же: посадка, чей ЯКОРЬ не резолвится.**
`ROTTED` — вердикт громкий, но он звучит только когда каталог прогнан ЦЕЛИКОМ. До этого запись
существует и **не утверждает ничего**, а ДЛИНА СПИСКА посадок читается как покрытие — то есть дефект
прячется ровно там, куда смотрят, оценивая полноту. У меня протухли два якоря, и оба сломала я сама
поздними рефакторами. Норма, которую это купило: **посадка, чей якорь не резолвится, — не красная
посадка, а отсутствующая; проверка «каждый якорь резолвится РОВНО ОДИН раз» гоняется после КАЖДОГО
касания кода, а не перед сдачей.** Это уже третий носитель класса: приёмка прошлого пака нашла тем же
вопросом четыре протухшие записи.
Это рядом с тремя уже известными формами: **упавшая сборка** (`WF-length-bound` в первой редакции: константа
`1 << 30` съедала память в `strings.Repeat` тестовой фикстуры — каталог сказал `NOTHING`), **вырожденное
утверждение** (`WF-version-fold-scope`: тест сравнивал два разных набора строк и доказывал лишь, что они
разные) и **эквивалентная посадка**. Общее у всех четырёх: цвет каталога перестаёт быть свидетельством о
коде.
**Практическое следствие, которое стоит держать:** прежде чем поверить `RED`, стоит проверить, что тест,
который покраснел, краснеет ПО ТОЙ ПРИЧИНЕ; прежде чем поверить `SURVIVED` — что посадка вообще меняет
поведение; а `NOTHING` и таймаут читать как «инструмент не ответил», а не как «дефекта нет».
### 6.5. Каталог мутаций — ПРОГНАН ЦЕЛИКОМ, И НЕ СОШЁЛСЯ С ПЕРВОГО РАЗА
```
$ cp -a backend /tmp/mutF/backend && cd /tmp/mutF/backend
$ GOFLAGS=-buildvcs=false go run ./cmd/tmmutate -root .
201 mutation(s) run, 1 unexpected outcome(s)
tmmutate: not what the catalogue records: DF-declared-means-every-boundary
MUT_EXIT=1
```
**201 запись · 196 RED · 4 записанных `survives` · 1 НЕОЖИДАННЫЙ ВЫЖИВШИЙ · 0 NOTHING · 0 ROTTED.**
Арифметика сходится: 196 + 4 + 1 = 201.
**ВЫЖИЛА ПОСАДКА `DF-declared-means-every-boundary` — И ЭТО НАСТОЯЩАЯ НАХОДКА, А НЕ ШУМ.** Она
восстанавливает дефект `V2-7` (структура зовётся `declared`, если form feed просто ВСТРЕТИЛСЯ), и её
выживание означает ровно одно: **починку V2-7 не держал НИ ОДИН тест.** В `internal/chunk/*_test.go` слово
`Structure` не встречалось вовсе, а два случая, которые есть в `priceprojection_test.go:144-146`, —
крайние (только form feed → `declared`, только заголовки → `detected`). **СМЕШАННОГО случая — одного
случайного `\f` среди найденных заголовками глав — не держал никто, а он и есть тот, в котором дефект
жил.** Дописан `TestTXTStructureIsDeclaredOnlyWhenTheFormatDrewEVERYBoundary` (три случая, вся линейка), и
посадка предъявлена КРАСНОЙ на исправленном дереве — краснеет именно подтест
`one_stray_form_feed_among_matched_headers`.
**На сдаваемом дереве: 201 запись · 197 RED · 4 записанных выживших · 0 неожиданных.**
**И ЭТО СКЛЕЙКА ИЗ ДВУХ ПРОГОНОВ — говорю прямо, потому что «прогнано целиком» звучит сильнее.**
Полный проход шёл на замороженной копии; после него дерево изменилось трижды (тест выше плюс две
КОММЕНТАРНЫЕ правки по отозванной ратификации, записи 5253). Что склейка законна, **предъявлено
механически, а не рассуждением**:
```
$ diff -rq /tmp/mutF/backend backend # различаются РОВНО четыре файла:
docs/MONEYSTOP_REPORT.md · internal/chunk/ingest_test.go
internal/pipeline/priceprojection.go · internal/runevents/runevents.go
# каждый .go пере-печатан из AST, разобранного БЕЗ ParseComments:
$ strip <frozen>/internal/pipeline/priceprojection.go | diff - <(strip backend/…) # ИДЕНТИЧНО (5020 vs 5020 байт)
$ strip <frozen>/internal/runevents/runevents.go | diff - <(strip backend/…) # ИДЕНТИЧНО (2710 vs 2710 байт)
```
⇒ два файла отличаются ТОЛЬКО комментариями (исполняемого не изменилось ничего), отчёт не компилируется,
а новый тест живёт в `./internal/chunk/`, где посадка ровно ОДНА — та самая, что и пере-прогнана. То есть
пере-считано в точности то множество, чей вердикт мог измениться, и ни одной записи не унаследовано от
дерева, которого больше нет.
**И проверка «отличаются только комментариями» с первого раза была ЛОЖНО ЗЕЛЁНОЙ:** утилита не
собралась (`error obtaining VCS status`), оба выхода вышли ПУСТЫМИ, и `diff` сравнил пустоту с пустотой,
напечатав «ИДЕНТИЧНО». Поймано тем, что размер выхода печатается рядом с вердиктом; в переделанной
проверке пустой выход — отдельная красная ветка. **Проверка, которая не умеет провалиться, ничего не
проверяет** — тот же класс, что и §6.4, теперь на моём собственном инструменте проверки.
Записанные выжившие — четверо, и каждый с аргументом в `why`: `G-byte-gate` и
`WB62-innocent-const-must-not-be-accused` (чужие, аргументированы прежними паками) плюс мои два:
· **`MS-reserve-under-the-gate`** — гонка «Reserve закоммичен, счётчик ещё нет» структурна, но
воспроизводима только с хуком в планировщик, которого в репозитории нет; в `why` записано ПРЯМО, чего
эта зелень НЕ значит: «не „гарантия держит“, а „инструментом не проверяемо“».
· **`MS-reservation-released-on-marshal-failure`** — ветка недостижима по контракту `encoding/json`
(структура из `int` не может не смаршалиться), и покраснить её нечем без подмены стандартной
библиотеки.
**Моих записей 43, из них 41 RED и 2 аргументированных выживших.**
**И ЭТО ТРЕТИЙ ПОЛНЫЙ ПРОГОН ЗА СМЕНУ. ПЕРВЫЙ БЫЛ ЧЕСТНЫМ, ПОЛНЫМ — И НЕ СОШЁЛСЯ:** `183 RED ·
3 SURVIVED · 1 NOTHING`, четыре неожиданных исхода, все мои, включая посадку, красноту которой промт
назвал ОБЯЗАТЕЛЬНЫМ УСЛОВИЕМ ПРИЁМКИ (записи 3034, 37). Второй сошёлся (191/187). Третий, вот этот, —
снова НЕТ. Норма, которую смена купила трижды: **«каталог прогнан целиком» и «каталог прогнан целиком И
СОШЁЛСЯ» — разные утверждения, и первое звучит как второе.**
---
### 6.6. Файлы
**Новые:** `internal/membank/{wirefence,wirefence_test}.go` · `internal/pipeline/{reservegate,priceprojection,priceprojection_test,moneystop_test}.go` · `docs/MONEYSTOP_REPORT.md`.
**Изменены:** `internal/membank/{memory,decisions,memseed,memvoice}.go` · `internal/chunk/ingest.go` ·
`internal/pipeline/{waverun,stagerun,escalation,repair,terminologist,events,manifest,status,runner,bookrun,mining,bankdecisions,paidtail}.go` ·
`internal/runevents/runevents.go` · `cmd/tmctl/{main,render}.go` · `cmd/tmmutate/mutations.json` (158 → 201) ·
тесты `internal/pipeline/{runevents_test,wavepanic_test,volumepanic_test,contractblockers_test}.go` и
`cmd/tmctl/render_test.go` и `internal/chunk/ingest_test.go` (два существующих тестовых файла, в каждый
ДОБАВЛЕНА функция — §6.2) ·
доки зоны `docs/{MONEY_HONESTY_REPORT,DISCLOSURE_LAW_DESIGN}.md` (снятая ссылка, запись 48).
**`git status` этого дерева показывает ещё файлы ВНЕ моей зоны, и это АРТЕФАКТ ОТЦЕПЛЕННОГО HEAD, а не
чья-то незакоммиченная работа.** Верификатор приёмки сделал `checkout` в общем дереве, поэтому HEAD стоит
на `9cac062`, а ветка `main` ушла вперёд. Следствие: `docs/architecture/{15-money-path,17-seam-inbound-law}.md`
(пере-нацеливание якорей оркестратора — `stagerun.go:496→495`, `525→559`, `status.go:835→858`,
`events.go:404→409`, уехавших от моего же диффа) числятся «изменёнными» относительно отцепленной линии,
хотя их содержимое уже РАВНО `main` — проверено: `git diff --stat main -- docs/architecture/` пусто.
⚠ И `docs/PROGRESS.md` в этом дереве, наоборот, СТАРШЕ `main` на две строки — то есть коммит этого пути
отсюда откатил бы чужую правку. Не трогала ни одного из них; лендить их из этого дерева НЕЛЬЗЯ.
**Моя работа не затронута:** `backend/` между `81a89e9` и `main` идентичен (`git diff --stat 81a89e9 main -- backend` пусто),
поэтому все диффы этого отчёта против `HEAD` для `backend/` верны.
### 6.7. Команды приёмки — каждое число выше получено одной из них
```
cd backend && make battery
go test ./internal/membank/ -run 'TestAUserTerm|TestTheDoor|TestTheSeedLoader|TestTheLengthBound|TestTheFenceStops|TestTheFenceMoves' -v
go test ./internal/pipeline/ -run 'TestUnderACeiling|TestARefusalWithNothing|TestTheGateWaits|TestSettleCannotHelp|TestTheShortfallIs|TestTheStoppedRunTells|TestResumeAfter|TestAMoneyStopInTheDraft|TestNothingLeaks|TestWaitingForHeadroom|TestACancelledWait|TestAnInfraFailure' -v
go test ./internal/pipeline/ -run 'TestTheManifestPrices|TestTheProjection|TestTheCutSays|TestTheStoredManifest|TestARunRewrites|TestStepMaxCounts|TestManifestServesTheReadModels' -v
go test ./internal/pipeline/ -run 'TestACeilingHaltAnnounces|TestWaveWorkerPanic|TestACrashOverACaughtCeiling|TestRunWaveSurfacesParentCancellation|TestRunnerEscalationCeilingDegrades|TestWaveParallelWorkersMoneyConserved' -v
# КРАСНЫЙ до починки — на копии дерева HEAD 32be78a (пункт 1)
git archive HEAD backend | tar -x -C /tmp/headtree && cp backend/internal/membank/wirefence_test.go /tmp/headtree/backend/internal/membank/
cd /tmp/headtree/backend && GOFLAGS=-buildvcs=false go test ./internal/membank/ -run 'TestAUserTerm|TestTheDoor|TestTheSeedLoader' -v
# каталог мутаций ЦЕЛИКОМ, на копии
cp -a backend /tmp/mut/backend && cd /tmp/mut/backend && GOFLAGS=-buildvcs=false go run ./cmd/tmmutate -root .
# дифф тестов ИСПОЛНЕНИЕМ
git diff -U0 -- backend | grep -E '^-func Test' # пусто: ничего не удалено и не переименовано
grep -rh '^func Test' backend --include=*_test.go | wc -l # 1213 (+41 к 1172 до пака)
python3 -c "import json;print(len(json.load(open('backend/cmd/tmmutate/mutations.json'))))" # 201 (158 + 43)
# посадки дофикса (пункт 1 приёмки + записи 42/47)
go test ./internal/membank/ -run 'TestAnEngineWrittenDocument' -v
go test ./internal/pipeline/ -run 'TestASidecarFromAnOlderBuild|TestTheMoneyLedgerFollowsTheCeiling' -v
go test ./cmd/tmctl/ -run 'TestTheHumanManifestCallsTheBookLevelFigureACeiling' -v
go test ./internal/chunk/ -run 'TestTXTStructureIsDeclaredOnly' -v
# ⛔ ЯКОРЯ ПОСАДОК — после КАЖДОГО касания кода, а не перед сдачей (запись 49):
# каждая пара (file, find) обязана встречаться в дереве РОВНО ОДИН раз; иначе посадка не красная, а отсутствующая
```
## 7. Правки существующих тестов — объявление по D39.183
Все три вызваны сменой поведения, ЗАКАЗАННОЙ паком, ни одна не сделана «чтобы прошло».
**(1) `runevents_test.go` — пин «the ceiling event must carry the fact and no figures».**
· *Что изменилось в поведении:* кадр `ceiling` теперь несёт `shortfall_micro_usd` (заказ §4(3) и строки 278).
· *Что описывал тест:* ратифицированную норму «деньги не выходят из движка иначе как в `spend`»
(D39.106 §2/ПТ-33).
· *Куда уехала гарантия:* она СУЖЕНА, не снята. Кадр проверяется по БЕЛОМУ СПИСКУ ключей
(`halted|scope|shortfall_micro_usd`) — любое новое денежное поле краснеет; запрещён любой отрендеренный
`$`; и добавлено новое утверждение, которого не было: book-стоп ОБЯЗАН назвать недостачу, day-стоп
обязан её НЕ называть. ⚠ Оркестратор берёт ноту о сужении на себя (акт приёмки): сужение
ратифицированной фразы — ратификационное действие, а не обслуживание.
**(2) `wavepanic_test.go` и (3) `volumepanic_test.go`оба ВИСЛИ.**
· *Предъявление, а не утверждение:* `panic: test timed out after 1m30s`, `FAIL … 90.048s`.
· *Что изменилось:* оба парковали падающий воркер на `<-ctx.Done()` и рассчитывали, что отмену вызовет
ПОТОЛОК. Потолок больше не отменяет — это и есть заказ пака §4(2).
· *Куда уехала гарантия:* никуда. Утверждения не ослаблены — краш и потолок по-прежнему в РАЗНЫХ слотах
и оба выходят из волны; ушёл только ритуал упорядочивания, который с двумя слотами ничего не решает.
Упорядочивание, которое ещё решает — краш против ИНФРА-ошибки, она отменяет, — пинится там же через
`ctx.Done()` **и предъявлено КРАСНЫМ** посадкой `MS-crash-outranks-the-wave-error` (§6.5).
· *Попутно:* при первой переписке `volumepanic` тест ПРОВАЛИЛСЯ, потому что под защёлкой кормилец не
успевал раздать второй элемент — падающий воркер обязан ДЕРЖАТЬ свой элемент до возврата halt. Это
поведение защёлки, а не дефект; в тест внесена явная синхронизация и комментарий.
---
## 8. Правка ВНЕ ЗАКАЗА, взятая по своему усмотрению
**Течь резервации на ветке `json.Marshal(resp.Usage)`** (`stagerun.go`): ветка возвращалась без
`releaseReservation` и без settle, оставляя оценку в `reserved_usd` до следующего `store.Open`.
· *Довод:* это денежный путь, и у него появился ВТОРОЙ читатель — утёкшая резервация завышает недостачу,
которую прогон теперь публикует, то есть движок попросил бы у человека больше, чем ему нужно.
· *Честная граница:* ветка практически недостижима — `json.Marshal` структуры из `int` не может упасть.
Поэтому посадка `MS-reservation-released-on-marshal-failure` занесена в каталог **как аргументированный
выживший**, а не как находка: покраснить её без подмены стандартной библиотеки нечем, и я не стала
выдумывать доказательство. Недостижимость — то, что перестаёт быть правдой от одной правки соседа.
---
## 9. Obstacle reporting — что НЕ удалось, что не проверено, что осталось гипотезой
1.**Клин юнита НЕ починен, и это признанный долг, а не забытое.** Отказ РЕЗЕРВАЦИИ на ретрае не
деградирует И не оставляет следа: он уходит наверх из цикла попыток (`stagerun.go:164-167`) РАНЬШЕ
`UpsertChunkStatus` (`stagerun.go:285`), так что чекпойнт attempt 0 есть (реплей за $0), а строки
`chunk_status` нет — читающие модели видят `pending`. Если потолок покупки не вмещает attempt 1 с
удвоенным бюджетом, юнит не доделается НИКОГДА, а прогон уходит exit 4 при полностью доставленных
купленных юнитах. Мой `step_max_usd` это НАЗЫВАЕТ (ретраи в нём именно поэтому), `Finished.Money`
называет доставленное и отложенное — но клин остаётся. Лечение: (а) деградация отказа ретрая во флаг
attempt 0 — ⚠ **и это НЕ смена D2, как я записала сперва:** тело ноты
`D2` («ретрай с бо́льшим `max_tokens` до капа, ПОТОМ ФЛАГ») уже кончается флагом, а денежная
ветка до него не доходит — ошибка уходит наверх из цикла попыток раньше. Значит это ДОСТРОЙКА
существующей диспозиции, а не её правка, и требование к ней одно: **причина флага обязана
ОТЛИЧАТЬСЯ от исчерпания регенераций**, иначе пометка врёт о причине (`D39.204`, строка 291) — либо (б) минимальная покупка платформы ≥ `step_max_usd`. Выбор не мой и не
оркестратора; строка бэклога с ОБОИМИ путями — в моей секции `docs/PROGRESS.md`.
2.**⭐-уточнение оркестратора («защёлка на свежем, продолжение на ретрае») ОТКЛОНЕНО, и не по цене.**
Его посылка — «текст у юнита ЕСТЬ, он лишь флагован» — ложна: см. п. 1, строки `chunk_status` нет,
для читателя это такой же ПРОБЕЛ. Различитель дёшев (номер попытки есть прямо в точке отказа), но
покупает не то, ради чего предлагался.
3.**Цена защёлки НЕ ЗАМЕРЕНА, только выведена.** Что юниты, которые влезали, не начинаются — следует
из удвоения бюджета ретрая и из замеренного разбором 04.09 разброса резервов 2.976× внутри волны. На
реальной книге я это не мерила: пак не включает платного прогона.
4.**Числа проекции не сверены с фактом на реальной паре.** `expected_usd` строится из фертильности
`zh-ru` и цен `models.yaml`; насколько он попадает в настоящий счёт — вопрос замера, а не чтения. То,
что предъявлено: он не константа, растёт с длиной главы, катится в сумму по главе и книге, и
`step_max_usd` не меньше резервации, которую исполнитель делает для первого вызова.
5.**Порог «сеттлить ли поздно отменённый вызов оценкой» НЕ выставлен, и это сознательный отказ.**
Промт разрешал латентность кандидатом при условии «порог обязан быть обоснован ЗАМЕРОМ, а не
назначен». Замера нет, платный прогон паком не выдан ⇒ порог назначить нечем, и я его не назначала.
6. ⛔ **ПРОВОДКА `settleCannotHelp` НЕ ПРОВЕРЕНА, и я показываю, ПОЧЕМУ она почти ничего не решает, а не
отговариваюсь.** Предикат запинен своим тестом; `if`, который его спрашивает, — нет.
⚠ **И здесь надо развести ДОСТИЖИМОСТЬ и ЭФФЕКТ — первую редакцию этого абзаца я написала неверно, и
поправил меня оркестратор по строке кода.** Предикат стоит ПЕРЕД входом в ожидание
(`stagerun.go:530-531`), поэтому когда он ИСТИНЕН, полёт не спрашивают вовсе: путь достижим при любом
состоянии полёта, включая пустое. Утверждать «достижим только при непустом» — значит утверждать
недостижимость состояния, которого код не держит, и следующая сессия построила бы на этом пин.
Верно другое: **он что-то МЕНЯЕТ только при непустом полёте** — при пустом книгу и без него
останавливает `waitNothingInFlight`. А состояние «отказ, который никакой settle не поднимет, ПРИ
непустом полёте» почти недостижимо. Но вызов в полёте сам был допущен при том же `committed` и сопоставимой оценке — если
место нашлось ему, оно найдётся и этому: два условия противоречат друг другу, пока оценки не разойдутся
существенно (разнородные размеры чанков, либо черновик и редактура в одной волне — а они не встречаются
никогда). А когда полёт пустеет, книгу останавливает `waitNothingInFlight` и без предиката. ⇒ он
ОПТИМИЗАЦИЯ (не держать волну открытой весь провайдерский таймаут ради известного ответа), а не
несущая гарантия, и в §2 он теперь описан так.
7.**ЧТО ТРИ ОПЦИОНАЛЬНЫХ САЙТА ПЕРЕДАЮТ ИМЕННО `false` — не прибито ничем, и я это НАЗЫВАЮ, а не заявляю.**
Правило «кто может ждать» теперь живёт в шлюзе и запинено тестом с обеих сторон (опциональный
отворачивается со счётчиком, обязательный ждёт и просыпается), плюс посадкой. Но АРГУМЕНТ, который
`escalation.go`, `repair.go` и `terminologist.go` передают в этот шлюз, проверяется только компилятором
и комментарием у каждого вызова. Недостающий артефакт назван точно: интеграционная фикстура, в которой
ОПЦИОНАЛЬНЫЙ вызов отказан, ПОКА сосед в полёте. Собрать её дёшево не вышло: у эскалации хоп случается
после сеттла своего же черновика, а потолок, впускающий два параллельных черновика, впускает и хоп;
репайр живёт внутри воркера редакторской волны и требует дефекта; терминолог идёт между волнами, где в
полёте по построению никого. Выдумывать доказательство через `sleep` я не стала — назвать дыру честнее.
8.**Гонка `Reserve`-вне-мьютекса структурна, но не воспроизводима.** Посадка
`MS-reserve-under-the-gate` — см. §6.5; если она пережила, это НЕ значит, что дефекта нет, а значит,
что без хука в планировщик его нечем поймать. Выдумывать «доказательство» через `sleep` я не стала.
9.**Платформенная сторона не проверена ни на чём.** Читает ли платформа `shortfall_micro_usd`, не
считает ли её sweep живой юнит у `paused`-прогона аномалией (что убило бы «доделать допущенное» на её
стороне) — чужая зона, я туда не ходила. Передано пингом.
10.**`status` теперь читает манифест ДВАЖДЫ, и я это не лечу.** `readModelChunks` загружает сайдкар,
и `readModelPrice` загружает его снова — а `loadManifest` валидирует ключ через `sourceSHA256` по
ВСЕМУ исходнику, то есть на 23-мегабайтной книге это второе полное чтение файла и второй хеш на каждый
`status`. Не ingest (те 1.4 с), но и не $0. Лечение — вернуть загруженный документ из
`readModelChunks` вместо второй загрузки — трогает сигнатуру, которой пользуются четыре вызывающих, и
шире этого пака. ⚠ Туда же уходит TOCTOU между двумя загрузками: конкурентный `translate`, записавший
сайдкар между ними, даёт согласованный ответ по СОВПАДЕНИЮ источника, а не по построению.
11.**Две величины в `expected_usd` НЕ смоделированы, и обе названы в доке, а не спрятаны.** (а) Доля
РАЗМЫШЛЕНИЯ в completion: на провайдере с subset-биллингом рассуждение входит в completion, но датума
о его доле в конфиге нет — считается текст без него, то есть в сторону занижения. Именно из-за этого
у редакторской модели существует флор 16000 (`models.yaml`), так что величина не мала. (б) На стадии
k>0 размер входа — токены провайдера (`est_out`) против `EstimateTokens` (знаки/3) у исполнителя;
совпадают только при ~3 знаках на токен. На боевом плече обе глушит модельный флор; на плече без
флора — нет.
12.**Взаимодействие с `--max-units` под денежным стопом проверено только чтением.** `res.Volume` при
денежном стопе не прикладывается, `scope.reconcile` не зовётся; что объёмный грант и денежный потолок
в одном прогоне ведут себя корректно во ВСЕХ комбинациях — не предъявлено прогоном.
14.**ПРАВКА V2-7 НЕ ДОТЯГИВАЕТСЯ ДО УЖЕ ЗАПИСАННОГО САЙДКАРА, и я это называю, а не чиню.**
`structure` вычисляется при РЕЗКЕ и складывается в сайдкар; правило, по которому `declared` теперь
требует, чтобы формат провёл ВСЕ границы, применяется к новым резкам. Файл, нарезанный сборкой
между `81a89e9` и дофиксом, несёт `declared`, посчитанный СТАРЫМ правилом, проходит версию (поле
аддитивно, версия намеренно не двигалась — см. запись 44) и отдаётся читателю как есть. Лечение —
двинуть `manifestVersion`, то есть выбросить каждый сохранённый сайдкар и пере-резать каждую книгу;
цена несоразмерна окну в несколько часов на движке, который ещё не отгружен. ⚠ Но правило общее и
его стоит держать: **аддитивное поле, чьё ПРАВИЛО потом меняется, перестаёт быть аддитивным**с
этого момента у него есть версия, просто ненаписанная.
15.**Что предупреждение о дропнутом ряде (запись 51) кто-то ПРОЧИТАЕТ — не предъявлено.** Оно уходит
в `r.Log.Warn` рядом с предупреждением о ДЕКЛИНЕ, то есть ровно туда же, куда движок уже говорит о
похожем событии; но доходит ли этот уровень до оператора платформы или тонет в логе прогона —
вопрос чужой зоны, и я туда не ходила.
13.**Каталог мутаций гонялся ЦЕЛИКОМ только на финальном дереве.** Промежуточные прогоны я дважды
останавливала САМА, обнаружив, что копия устарела от собственных правок: результат о дереве, которого
больше нет, — это ровно тот класс, за который смена расплачивалась («утверждение о дереве живёт до
следующего коммита»). Цвета в §6.5 — с последнего, замороженного прогона.
---
## 10. Дофикс приёмки — дельта против уже приземлённого пака
Пак приземлён двумя коммитами (`81a89e9` — дерево, `9cac062` — правка довода в шапке поля). Всё
перечисленное ниже лежит В ДЕРЕВЕ ПОВЕРХ них и не закоммичено: лендит оркестратор.
| приёмка | что было не так | где починено |
|---|---|---|
| F1 | довод «та же дверь» ложен, и утверждение в коде сильнее правды | `runevents.go`, §3, запись 39 |
| — | `Finished.Money` документирован правилом присутствия, которого нет (нашли ДВА верификатора независимо) | `runevents.go`, пин `TestTheMoneyLedgerFollowsTheCeilingAndNotTheOutcome`, запись 40 |
| F3 | книжная фигура печаталась как прогноз, а она ПОТОЛОК | `cmd/tmctl/render.go`, пин `TestTheHumanManifestCallsTheBookLevelFigureACeiling`, запись 41 |
| F8 | забор убивал оплаченный прогон об собственный вывод движка | расщепление по авторству: `memseed.go`/`memvoice.go`/`mining.go`/`bankdecisions.go`/`decisions.go`, пин `TestAnEngineWrittenDocumentDropsTheRowInsteadOfKillingTheRun`, запись 42 |
| F9 | история в шапке забора льстила прошлому | `wirefence.go`, запись 43 |
| V2-3 | у сайдкара ТРИ состояния, код знал два | `manifest.go`, пин `TestASidecarFromAnOlderBuildStillYieldsAPrice`, запись 44 |
| V2-5 | книга платила за проход, которого не бывает, **и тест пинил неверное число** | `priceprojection.go` + пере-пин, запись 45 |
| V2-7 | один случайный form feed объявлял сорок девять догадок словом файла | `chunk/ingest.go`, запись 46 |
| V2-8 | тождество выводилось из равенства чисел | `priceprojection.go`, запись 47 |
| — | ссылка на идентификатор, которого в Go больше нет (три носителя) | `paidtail.go` + два дока зоны, запись 48 |
| — | **ДОФИКС F8 РОНЯЛ РЯД МОЛЧА** — найдено не приёмкой, а собственным чтением дофикса | `memvoice.go`/`memseed.go`/`mining.go`, запись 51 |
| F7 | два комментария ОДНОГО коммита противоречили о фолбэке цены; у находки не было владельца до 05.09 | `status.go`, запись 56 |
**Каталог: 191 → 201.** Десять новых посадок держат дофикс, и это ровно десять ИМЁН:
`DF-engine-written-document-must-not-kill-the-run` и `DF-operator-seed-keeps-its-refusal` (расщепление по
авторству держится с ОБЕИХ сторон: если бы посадка была одна, «уроню всё» и «откажу всему» прошли бы
поодиночке) · `DF-old-sidecar-must-not-silence-the-price` · `DF-classifier-budget-only-when-it-runs` ·
`DF-input-identity-is-stated-not-counted` · `DF-declared-means-every-boundary` ·
`DF-money-follows-the-ceiling-not-the-outcome` · `DF-book-level-figure-is-a-forecast` и
`DF-no-book-level-spend-says-nothing` (F3 — тоже с обеих сторон: сказать неправду и сказать лишнее) ·
`DF-a-dropped-row-is-not-silent` (запись 51). Плюс четыре ПЕРЕ-НАЦЕЛЕННЫЕ (запись 49).
**Первая редакция этого абзаца объявляла F3 непокрываемым** — «носитель правки текст, покрасить
нечем, проверено чтением вывода». Это была ОТГОВОРКА, и она держалась ровно до попытки: `renderManifest`
принимает `io.Writer` и `*BookManifest`, тест пишется в двадцать строк без стенда и без денег, а
`cmd/tmctl/render.go` до сих пор не имел на эту функцию НИ ОДНОГО теста. Записано как есть, потому что
«нечем покрыть» — самая дешёвая из неправд, какие пишет о себе смена, и от честной она отличается
одной попыткой.
**Что дофикс НЕ трогал:** ни одного теста не удалено и не переименовано (§6.2), ни одной строки в
чужой зоне, ни одного платного вызова. Батарея и каталог прогнаны на дереве ПОСЛЕ дофикса, не до.