Turn the order from shipping a withheld text into naming the hole, keep the money fact visible, let a raised ceiling finish the unit, and rebuild the two pins that redden by luck

This commit is contained in:
heaven 2026-09-11 21:54:58 +03:00
parent c38cb1ad33
commit 14dc3d5f23

View file

@ -60,12 +60,42 @@
ошибка наверх;
- у флага **СВОЯ причина**, отличная от исчерпания регенераций: читатель и оператор обязаны отличать «мы
перепробовали всё» от «денег не хватило на вторую попытку»;
- текст попытки 0 **отгружается**;
- волна **идёт дальше**.
**Решаешь сам и аргументируешь:** имя причины · где именно ставится деградация (в цикле `runStage` или
внутри `runAttempt`у обоих есть довод, назови свой) · как при этом НЕ тронуть попытку 0: её отказ
по-прежнему ОСТАНАВЛИВАЕТ книгу, это ратифицировано и менять нельзя (`stagerun.go`, греп `mandatory`).
**ЧЕГО В ЭТОМ СПИСКЕ БОЛЬШЕ НЕТ И ПОЧЕМУ — читай, это главное место пака.** Первая редакция промта
требовала, вслед за `D39.204` п.4, чтобы «текст попытки 0 **отгружался**». **Это невыполнимо как написано,
и поймал это опровергатель, а не я.** В цикл ретрая попадают ТОЛЬКО флагованные попытки ретраебельных
причин — `length`/`empty` плюс эхо-реролл (`backend/internal/pipeline/disposition.go`, греп `retryable`).
Значит «оплаченный текст попытки 0» **по построению** обрезан, пуст либо является эхом исходника. А
флагованная диспозиция (кроме `FlagSanitizerStripped`) оставляет пустой финальный текст, следующая стадия
скипается, и экспорт не отдаёт ничего — `backend/internal/pipeline/holes.go` называет это `withheld` прямо:
«contaminated output never ships» (`D2`).
⇒ **настоящий выигрыш пака формулируется так, и он не меньше прежнего, а честнее:**
**невидимая дыра `pending`** (строки нет · юнит заклинен · `exit 4`) **→ НАЗВАННАЯ дыра `withheld`**
(строка есть · причина денежная и названа · волна идёт · прогон кончается штатно).
**Отгрузка обрезанного текста — ОТДЕЛЬНОЕ продуктовое решение, и я задал его владельцу.** Придёт «да» —
доедет релеем отдельным сообщением, и ты подтвердишь его эхом. Сам не строй.
**И ВТОРОЕ, без чего пак сделает читателю хуже: ДЕНЬГИ ОБЯЗАНЫ ОСТАТЬСЯ ВИДНЫ.** Сегодня отказ ретрая
публикует денежный факт: событие `ceiling` + защёлка + `exit 4`, и платформа пишет прогону `paused` и
просит пополнить. После деградации этот путь исчезает: терминальные ветки `flagged` и `clean` несут только
объём, а `Money` висит на ошибочных (`backend/internal/pipeline/events.go`, греп `terminal`). **Читателю
кончились деньги — и никто ему этого не говорит.** ⇒ **делай РОВНО так:** прогон, отказавший только РЕТРАЮ,
**по-прежнему публикует денежный факт** — либо явно НЕ публикует, и тогда довод записан в отчёте и в коде.
⚠ И рядом живёт комментарий, который твой пак сделает ложным, если факт пропадёт: `runevents.go`, греп
`ITS PRESENCE RULE` — «правило присутствия — „потолок был достигнут“, а не „исход = ceiling“».
**Решаешь сам и аргументируешь:** имя причины · где ставится деградация. ⛔ **И вот ШТАТНЫЙ носитель,
который первая редакция промта пропустила:** у `runAttempt` уже есть параметр **`mandatory`** — «позволено
ли отказанной резервации ЖДАТЬ освободившихся денег»; волна зовёт его с `true`, а эскалационный хоп,
под-шаг ремонта и терминология зовут с `false` и потому деградируют (`backend/internal/pipeline/stagerun.go`,
греп `mandatory`; правило — `reservegate.go`, греп `waitNotAllowed`). Это ровно тот класс, под который
механизм построен. ⚠ **Но у него есть цена, и её надо назвать прежде, чем брать:** `mandatory=false`
отнимает не только остановку, но и ПРАВО ЖДАТЬ — ретрай деградирует даже там, где деньги освободились бы
через миллисекунду сеттлом соседа. **Остановка и ожидание — два решения на одном флаге.**
**Попытку 0 не трогать:** её отказ по-прежнему ОСТАНАВЛИВАЕТ книгу — ратифицировано, менять нельзя.
**Гипотеза, которую надо ПРОВЕРИТЬ, а не принять:** деградация сама по себе закрывает и следствие (2) —
у юнита появляется статус, значит дыры в непрерывном префиксе нет. Похоже на правду; предъяви замером.
@ -73,10 +103,29 @@
### 4.2 Пин, ради которого пак существует
**«Купленное доставлено ⇒ не `exit 4`».** Прогон, где каждый купленный юнит доставлен, а один из них
доставлен ФЛАГОВАННЫМ из-за нехватки денег на ретрай, **обязан кончиться успехом**, а не кодом 4.
доставлен ФЛАГОВАННЫМ из-за нехватки денег на ретрай, **обязан кончиться `exit 2`** (флаги есть, но прогон
завершён), **а не `exit 4`**. ⚠ Не «успехом»: `exit 0` требует, чтобы флагов не было вовсе, а флаг здесь
есть по построению — это и есть честный исход.
⛔ И **пин на саму незаклиниваемость**: после такого прогона резюм не делает НИ ОДНОГО свежего платного
вызова по этому юниту и не умирает. Сегодня он умирает вечно.
**И назови СУЖЕНИЕ, иначе будешь ловить недостижимое:** утверждение верно в ОКНЕ, когда остатка потолка
хватает на попытку 0 каждого следующего юнита. Кончился потолок совсем — попытка 0 следующего юнита
обязательна, отказ по ней штатно останавливает книгу, и `exit 4` возвращается ЗАКОННО. Фикстуру строй
именно на этой развилке.
⛔ **ПИН НА РЕЗЮМ — И ЗДЕСЬ ПЕРВАЯ РЕДАКЦИЯ ПРОМТА ЗАПИНИЛА БЫ ОТМЕНУ РАТИФИЦИРОВАННОГО, не заметив.**
Я требовал «после такого прогона резюм не делает НИ ОДНОГО свежего платного вызова». Но денежный флаг —
единственная причина, которую ЛЕЧАТ ДЕНЬГАМИ: сегодня «поднял потолок → резюм → читатель получил хороший
перевод» ратифицировано (`backend/internal/pipeline/escalation.go`, греп `durably pauses and resumes`,
`D4`). Мой пин сделал бы юнит навсегда деградированным, лечимым только редрайвом.
**делай РОВНО так: денежная причина ПЕРЕ-АТАКУЕМА на резюме** — как `FlagCancelled`, единственная
причина, у которой `resolvedForResume` ложен именно потому, что это «оплачено, но не сделано»
(`backend/internal/pipeline/cutcall.go`, греп `resolvedForResume`). Пинь ДВЕ вещи: **без поднятия потолка
резюм не делает свежих платных вызовов и не умирает** · **после поднятия потолка резюм ДОДЕЛЫВАЕТ юнит**.
**Два гейта поймают тебя сами — назову, чтобы не терять круг:** `TestEveryFlagReasonIsRanked` потребует
завести новую причину в `flagSeverity` (`backend/internal/pipeline/status.go`), а
`TestEveryOperatorMessageIsCatalogued` — внести новое сообщение в
`backend/internal/pipeline/testdata/operator-messages.txt`.
### 4.3 Платформенная половина — **твоё дело назвать, не построить**
@ -89,20 +138,47 @@
записать прогону, который кончился флагом вместо `exit 4` · какой её пин это стережёт. Я передам это
платформенной сессии, и **акт будет один на две половины**.
### 4.4 Приложение — фикстура, краснеющая по УДАЧЕ (ряд 379(б))
### 4.4 Приложение — ДВА пина, краснеющих по УДАЧЕ (ряд 379, ОБЕ половины)
Отдельная, маленькая и не связанная с §4.1 работа; делай её ПОСЛЕ основной.
Отдельная от §4.1 работа; делай её ПОСЛЕ основной. ⛔ **И мой диагноз здесь был НЕВЕРЕН — опровергнут
замером, а не мнением. Читай, чем именно, иначе повторишь мою ошибку.**
Запись каталога `CUTCALL-a-refusal-pays-when-the-reply-outruns-the-write` стережёт самую дорогую половину
денежного бита `delivered()` (`backend/internal/llm/attemptcut.go:189`=`answered && t.firstByte.Load()`). **Замер оркестратора 11.09:** на
чистом дереве пин `TestARefusalIsNotAPurchaseEvenWhenTheReplyOutrunsTheWrite` даёт **8 зелёных из 8**, на
своей посадке — **7 КРАСНЫХ ИЗ 8, один ЗЕЛЁНЫЙ**. Текст падения верный и про предмет; дыра в ФИКСТУРЕ:
она шлёт ~6 МБ тела, сервер читает строку запроса, отвечает `401` и рвёт связь `SetLinger(0)` — и успеет ли
транспорт записать `GotFirstResponseByte` раньше, чем `Do` вернёт ошибку записи, **решает гонка, а не
построение**. ⇒ прежние RED этой записи были удачей.
**Половина (б).** Запись каталога `CUTCALL-a-refusal-pays-when-the-reply-outruns-the-write` стережёт самую
дорогую половину денежного бита `delivered()`
(`backend/internal/llm/attemptcut.go:189`=`answered && t.firstByte.Load()`): провайдер ОТКАЗЫВАЕТ и рвёт
коннект, пока наше тело ещё пишется — байт ответа есть, статуса в руках нет, и чтение «байт = доставка»
оплачивает сметой каждый отказ (замер прежнего пака: 22 отказа из 25, один на $0.80).
**Задача:** перестроить фикстуру так, чтобы приход байта ответа был НЕИЗБЕЖЕН до обрыва, и предъявить
**8 из 8 красных на посадке при 8 из 8 зелёных на дереве**. Способ выбираешь сам.
**Два независимых замера на посадке (снятие `answered &&`), и они расходятся:** мой — **7 красных из 8**
(снят на нагруженной машине, рядом шла батарея); опровергателя на спокойной — **8 из 8**, а на сорока
процессах **39 FAIL / 1 PASS**. ⇒ пин краснеет по УДАЧЕ, это подтверждено обоими; частота зависит от
нагрузки, и поэтому «восемь красных подряд» **не является критерием приёмки**: сегодня, без единой правки,
восьмёрка выпадает примерно в 82 случаях из 100.
**А вот ПРИЧИНА не та, что я написал.** Я заказывал «сделать приход байта ответа НЕИЗБЕЖНЫМ до обрыва».
Зонд опровергателя (40 итераций, печать того, что реально получил клиент) дал **39 × обрыв с отказом
записи и 1 × ПОЛНЫЙ `401`**, и ни разу — «ни куска, ни статуса». То есть байт приходит практически всегда,
а мутант выживает в ДРУГОМ случае: клиент успевает вычитать `401` ЦЕЛИКОМ, `Do` возвращает не ошибку
записи, а ОТВЕТ, и `delivered()` не спрашивают вовсе — ошибка уходит статусной веткой. **Моё лекарство
било по половине, которая и так держится, и увеличивало вероятность ровно того исхода, в котором мутант
выживает.** (И «~6 МБ тела» тоже неверно: 11.0 МБ — Go не эскейпит кириллицу в JSON.)
**Задача — делай РОВНО так в части ЧТО, способ твой:**
1. **Тест утверждает СВОЮ ПОСЫЛКУ и печатает её:** что `Do` вернул ошибку ЗАПИСИ и что трасса видела
первый байт. **Сценарий не состоялся ⇒ тест КРАСНЫЙ**, а не зелёный «мимо предмета». Сегодня он зелен и
на полном `401`, то есть на другом предмете.
2. **Прямой табличный пин на сам `delivered()`** — он убивает посадку `answered &&` детерминированно, без
гонки вообще.
3. ⚠ У фикстуры `MaxAttempts: 3` — до трёх бросков кости на один `Complete`; **границу утверждай ПО
ПОПЫТКЕ**, а не по итогу вызова.
**Половина (а) — её первая редакция промта не заказывала вовсе.** Тот же ряд 379 несёт вторую половину:
две строки таблицы девяти в `backend/internal/pipeline/cutcall_test.go:402`=`time.Sleep(50 * time.Millisecond)`
разводятся ОКНОМ СТЕННЫХ ЧАСОВ, и по какую сторону границы заголовков сел `cancel()`, не утверждается
ничем — замер приёмки дал **2 красных из 8**. **Ряд закрывается ТОЛЬКО вместе с этой половиной.**
**Критерий приёмки берётся из самого ряда 379, он сильнее моего:** «каждый из двух пинов **ПЕЧАТАЕТ
величину, доказывающую пройденную границу, и ПАДАЕТ, когда сценарий не состоялся»**.
## 5. Где этот пак мягкий — четыре места, назвал я, веер и глубину выбираешь ты