textmachine/backend/docs/MONEYSTOP_REPORT.md

148 KiB
Raw Blame History

Отчёт бэкенд-пака «денежный стоп перестаёт быть аварией» (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.EmbeddedcutTag()внутрь каждого 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()cutTagunit.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 пере-написана: цена есть и БЕЗ сайдкара; совпадение двух поверхностей держится тем, что оба конца гоняют ОДНУ деривацию (readModelPriceprojectBook), а не тем, что один отказывается отвечать; и фолбэк описывает эту резку ПО ПОСТРОЕНИЮ, а не по гарантии. ⚠ Правка комментарная, предъявлено машинно: 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 не зовётся; что объёмный грант и денежный потолок в одном прогоне ведут себя корректно во ВСЕХ комбинациях — не предъявлено прогоном.
  13. ПРАВКА V2-7 НЕ ДОТЯГИВАЕТСЯ ДО УЖЕ ЗАПИСАННОГО САЙДКАРА, и я это называю, а не чиню. structure вычисляется при РЕЗКЕ и складывается в сайдкар; правило, по которому declared теперь требует, чтобы формат провёл ВСЕ границы, применяется к новым резкам. Файл, нарезанный сборкой между 81a89e9 и дофиксом, несёт declared, посчитанный СТАРЫМ правилом, проходит версию (поле аддитивно, версия намеренно не двигалась — см. запись 44) и отдаётся читателю как есть. Лечение — двинуть manifestVersion, то есть выбросить каждый сохранённый сайдкар и пере-резать каждую книгу; цена несоразмерна окну в несколько часов на движке, который ещё не отгружен. ⚠ Но правило общее и его стоит держать: аддитивное поле, чьё ПРАВИЛО потом меняется, перестаёт быть аддитивнымс этого момента у него есть версия, просто ненаписанная.
  14. Что предупреждение о дропнутом ряде (запись 51) кто-то ПРОЧИТАЕТ — не предъявлено. Оно уходит в r.Log.Warn рядом с предупреждением о ДЕКЛИНЕ, то есть ровно туда же, куда движок уже говорит о похожем событии; но доходит ли этот уровень до оператора платформы или тонет в логе прогона — вопрос чужой зоны, и я туда не ходила.
  15. Каталог мутаций гонялся ЦЕЛИКОМ только на финальном дереве. Промежуточные прогоны я дважды останавливала САМА, обнаружив, что копия устарела от собственных правок: результат о дереве, которого больше нет, — это ровно тот класс, за который смена расплачивалась («утверждение о дереве живёт до следующего коммита»). Цвета в §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), ни одной строки в чужой зоне, ни одного платного вызова. Батарея и каталог прогнаны на дереве ПОСЛЕ дофикса, не до.