diff --git a/docs/BACKEND_BOUGHT_MEANS_DELIVERED_SESSION_PROMPT.md b/docs/BACKEND_BOUGHT_MEANS_DELIVERED_SESSION_PROMPT.md index 4ff795eb..aa99a11d 100644 --- a/docs/BACKEND_BOUGHT_MEANS_DELIVERED_SESSION_PROMPT.md +++ b/docs/BACKEND_BOUGHT_MEANS_DELIVERED_SESSION_PROMPT.md @@ -56,110 +56,80 @@ ## 4. Состав -### 4.1 Деградация — **делай РОВНО так в ЧАСТИ РЕШЕНИЯ, сам решай КАК** +### 4.1 ⛔ КОНСТРУКЦИЯ ПЕРЕ-РЕШЕНА ВЛАДЕЛЬЦЕМ 11.09 — читай это ПЕРЕД кодом -**Ровно так (решено владельцем, отступление = пинг):** -- отказ резервации ⇒ **флаг**, а не ошибка наверх — ⛔ **но предикат НЕ «индекс попытки ≥1»: эта - формулировка была моей, и она теряла бы юниты НАВСЕГДА** (поймала исполняющая сессия, я пере-проверил - кодом). Индекс попытки ПЕРЕ-ПРИСВАИВАЕТСЯ прогулкой по сожжённым ключам - (`backend/internal/pipeline/stagerun.go:201`=`attempt = att.attempt`; правило — - `stagerun.go:519`=`BURNED KEYS ARE WALKED OVER HERE`), поэтому после остановленного прогона ПЕРВАЯ - свежая покупка позиции уже идёт под индексом ≥1, а в руках нет ни текста, ни классификации. Флаг там - написал бы вердикт юниту, которого ни разу не перевели, — и поскольку `resolvedForResume` истинна для - любого флага, кроме `cancelled` (`cutcall.go:293`=`FlagReason(cs.FlagReason) != FlagCancelled`), юнит - стал бы терминальным вместе со сожжёнными деньгами. ⇒ **предикат: «в руках есть ОПЛАЧЕННАЯ - КЛАССИФИЦИРОВАННАЯ попытка»** (есть `judged`/`last`), а не «индекс ≥1»; -- у флага **СВОЯ причина**, отличная от исчерпания регенераций: читатель и оператор обязаны отличать «мы - перепробовали всё» от «денег не хватило на вторую попытку»; -- волна **идёт дальше**. +**Слово владельца дословно:** «Надо о таком просто сообщать в виде метаданты, которая сейчас и пишется +предупреждением, и иметь возможность поднять просто потолок и возобновить прогон, так как **это ситуация +всё же СТАНДАРТНАЯ СО СТОПОМ, просто он произошёл по какой-то другой причине**». -⛔ **ЧЕГО В ЭТОМ СПИСКЕ БОЛЬШЕ НЕТ И ПОЧЕМУ — читай, это главное место пака.** Первая редакция промта -требовала, вслед за `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`). +⛔ **Это отменяет обе половины прежнего заказа — и моего, и `D39.204` п.4.** Ни «деградировать во флаг», ни +«волна идёт дальше» больше НЕ строится. Прежний план заводил новый исход; новый — **возвращает ситуацию в +УЖЕ ПОСТРОЕННЫЙ штатный путь остановки**, из которого она сегодня выпадает. -⇒ **настоящий выигрыш пака формулируется так, и он не меньше прежнего, а честнее:** -**невидимая дыра `pending`** (строки нет · юнит заклинен · `exit 4`) **→ НАЗВАННАЯ дыра `withheld`** -(строка есть · причина денежная и названа · волна идёт · прогон кончается штатно). -⛔ **ОТВЕТ ВЛАДЕЛЬЦА ПОЛУЧЕН 11.09, и он снимает половину вопроса: «пока платит за всё ЮЗЕР, потом будем -решать эту проблему».** ⇒ **ТВОЙ ПАК НЕ МЕНЯЕТ, КОМУ ЧТО ВЫСТАВЛЯЕТСЯ, НИ В КАКУЮ СТОРОНУ.** Никакого -механизма «платит продукт» не строить; существующее биллинговое поведение не трогать даже там, где оно -выглядит непоследовательным. Предмет пака — **ВИДИМОСТЬ дыры, а не её цена.** +**Что это значит механически (моё чтение; спорь, если код скажет иначе):** -⚠ И назови это в отчёте отдельной строкой, чтобы следующая смена не открывала заново: объёмный леджер -СЕГОДНЯ считает флагованный юнит НЕ доставленным, то есть грант за него не съеден — это **расходится** со -словом «платит за всё юзер» и существует ДО твоего пака. **Не чини, не оптимизируй, просто НАЗОВИ**: -владелец объявил вопрос отложенным. +1. **Прогон ОСТАНАВЛИВАЕТСЯ, как при любом отказе по потолку.** Не продолжается с флагом. ⭐ И это заодно + снимает то, чем ряд 291 пугал: «продолжить волну нельзя без ДЫРЫ в непрерывном префиксе» — при остановке + дыры не возникает вовсе. +2. **Отказ ретраю публикует ту же метадату, что и любая остановка по потолку** — кадр + `Ceiling{Halted, Scope, ShortfallMicroUSD}` (`backend/internal/runevents/runevents.go`, греп + `ShortfallMicroUSD`). ⭐ **Он уже несёт ровно то, что владелец просит:** «пополни хотя бы на столько, и + тот же вызов будет допущен» — и это **ратифицировано им же, `D39.203`**. Строить новый носитель не надо. + ⚠ **И твоя находка №2 этим РАЗРЕШАЕТСЯ, а не отменяется:** ты была права, что `ceilingReached` + идемпотентен по прогонному флагу и съел бы событие настоящей остановки — но теперь съедать НЕЧЕГО: эта + остановка И ЕСТЬ настоящая. Публикуй через штатный путь. +3. ⛔ **НАСТОЯЩИЙ ДЕФЕКТ — УЖЕ, чем я написал, и он назван в самом дереве.** Механизм «отметить + остановленную позицию» построен ИМЕННО против невидимых дыр: + `backend/internal/pipeline/stagerun.go:142`=`THE MARK FOR A STOPPED POSITION IS ATTACHED TO EVERY EXIT`, + и его комментарий приводит замер прежнего инцидента — «прогон, остановленный на эскалационном хопе, + рассчитал деньги и не оставил строки вовсе: `committed=0.001176`, `chunk_status_rows=0`». **Но метка — + no-op для всего, что не «доставленный вызов, который остановил человек»** (`recordCancelledStage`). + Отказ по потолку под это не подпадает ⇒ юнит остаётся без строки. **Вот дыра, и вот что ты закрываешь.** +4. **Поднял потолок → резюм ДОДЕЛЫВАЕТ юнит.** Значит строка, которую ты пишешь, обязана быть + НЕ-ТЕРМИНАЛЬНОЙ для резюма — по образцу `FlagCancelled`, единственной причины, у которой + `resolvedForResume` ложен (`backend/internal/pipeline/cutcall.go:293`=`FlagReason(cs.FlagReason) != FlagCancelled`), + и ложен именно потому, что это «оплачено, но не сделано». Твой случай той же природы. -⚠ Отгрузка обрезанного текста остаётся невыполнимой по `D2` независимо от того, кто платит. Сам не строй. +**Делай РОВНО так:** остановка штатная · метадата через уже построенный кадр с недостачей · **юнит получает +строку, а не пустоту** · строка НЕ терминальна для резюма · после пополнения резюм доделывает. -⛔ **И ВТОРОЕ, без чего пак сделает читателю хуже: ДЕНЬГИ ОБЯЗАНЫ ОСТАТЬСЯ ВИДНЫ.** Сегодня отказ ретрая -публикует денежный факт: событие `ceiling` + защёлка + `exit 4`, и платформа пишет прогону `paused` и -просит пополнить. После деградации этот путь исчезает: терминальные ветки `flagged` и `clean` несут только -объём, а `Money` висит на ошибочных (`backend/internal/pipeline/events.go`, греп `terminal`). **Читателю -кончились деньги — и никто ему этого не говорит.** ⛔ **НО НЕ ЧЕРЕЗ `ceilingReached` — развилку разрешила исполняющая сессия, и её довод сильнее моего -первоначального «публикуй».** Я пере-проверил кодом: событие идемпотентно по ПРОГОННОМУ флагу -(`backend/internal/pipeline/events.go:454`=`already := e.ceilingSaid`), поэтому отказ ретраю **СЪЕЛ БЫ -событие настоящей остановки** позже в том же прогоне, и платформа осталась бы с остановкой без причины. -Плюс `Halted: true` переводит идущий прогон в паузу НА ХОДУ. ⇒ **делай РОВНО так: денежный факт виден -НОВОЙ ПРИЧИНОЙ на проводе юнита плюс операторской строкой с недостачей (`shortfall`); событие -`ceiling`-остановки НЕ публикуется.** Довод записывается и в код, и в отчёт. +**Решаешь сам и аргументируешь:** как назвать причину в строке юнита (или взять существующую) · где ставится +метка · как отличить «остановлено на ретрае» от «остановлено на попытке 0» в носителе, и надо ли отличать. -⚠ **И зеркало, которое твой пак делает ложным В СТОРОНУ ОТСУТСТВИЯ:** `backend/internal/runevents/runevents.go:289`=`ITS PRESENCE RULE IS` -говорит «правило присутствия — „потолок был ДОСТИГНУТ“, а не „исход = ceiling“». После пака потолок бывает -достигнут, а `Money` нет. ⇒ сузь прозу до «ОСТАНОВИЛ прогон» **в своей зоне** и **назови мне зеркала вне -её** (платформенный приёмник событий, канон 14) — я отнесу. Чужое не правь. +⛔ **Попытку 0 не трогать:** её отказ и сегодня останавливает книгу штатно — ратифицировано. -**Решаешь сам и аргументируешь:** имя причины · где ставится деградация. ⛔ **И вот ШТАТНЫЙ носитель, -который первая редакция промта пропустила:** у `runAttempt` уже есть параметр **`mandatory`** — «позволено -ли отказанной резервации ЖДАТЬ освободившихся денег»; волна зовёт его с `true`, а эскалационный хоп, -под-шаг ремонта и терминология зовут с `false` и потому деградируют (`backend/internal/pipeline/stagerun.go`, -греп `mandatory`; правило — `reservegate.go`, греп `waitNotAllowed`). Это ровно тот класс, под который -механизм построен. ⚠ **Но у него есть цена, и её надо назвать прежде, чем брать:** `mandatory=false` -отнимает не только остановку, но и ПРАВО ЖДАТЬ — ретрай деградирует даже там, где деньги освободились бы -через миллисекунду сеттлом соседа. **Остановка и ожидание — два решения на одном флаге.** +⛔ **КТО ПЛАТИТ — НЕ ПРЕДМЕТ ЭТОГО ПАКА.** Слово владельца 11.09: «пока платит за всё ЮЗЕР, потом будем +решать эту проблему». Механизм «платит продукт» не строить; существующее биллинговое поведение не трогать +ни в какую сторону, даже где оно выглядит непоследовательным. ⚠ И отдельной строкой отчёта НАЗОВИ (не чини): +объёмный леджер сегодня считает флагованный юнит НЕ доставленным, то есть грант за него не съеден — это +расходится со словом владельца и существует ДО твоего пака. Он объявил вопрос отложенным. -⛔ **Попытку 0 не трогать:** её отказ по-прежнему ОСТАНАВЛИВАЕТ книгу — ратифицировано, менять нельзя. - -⚠ **Гипотеза, которую надо ПРОВЕРИТЬ, а не принять:** деградация сама по себе закрывает и следствие (2) — -у юнита появляется статус, значит дыры в непрерывном префиксе нет. Похоже на правду; предъяви замером. +⚠ **Отгрузка обрезанного текста не строится** — `D2` запрещает отгружать загрязнённое, и это не зависит от +того, кто платит. ### 4.2 Пин, ради которого пак существует -**«Купленное доставлено ⇒ не `exit 4`».** Прогон, где каждый купленный юнит доставлен, а один из них -доставлен ФЛАГОВАННЫМ из-за нехватки денег на ретрай, **обязан кончиться `exit 2`** (флаги есть, но прогон -завершён), **а не `exit 4`**. ⚠ Не «успехом»: `exit 0` требует, чтобы флагов не было вовсе, а флаг здесь -есть по построению — это и есть честный исход. +**«ОСТАНОВКА, КОТОРАЯ ЛЕЧИТСЯ ПОПОЛНЕНИЕМ».** Три утверждения, и они про разное: -⭐ **И заточка, которую дала исполняющая сессия, — она идёт В АКТ:** лечение клина НЕ зависит от того, -кончился прогон кодом 2 или 4. Даже когда потолок исчерпан совсем и попытка 0 следующего юнита законно -останавливает книгу, у деградированного юнита **всё равно появляется строка** — то есть следствие (1) -закрыто и в окне `exit 4`. **Пин ниже стережёт более узкое окно, чем сам выигрыш**, и это надо сказать, а -не выдать окно пина за границу лечения. +1. **Юнит, на ретрае которого отказал потолок, ПОЛУЧАЕТ СТРОКУ.** Сегодня её нет — это и есть невидимая + дыра. Пин обязан печатать величину, доказывающую, что деньги были потрачены, а строка появилась: пустой + `chunk_status` рядом с ненулевым `committed` — ровно тот замер, которым дерево описывает прошлый + инцидент. +2. **Остановка несёт НЕДОСТАЧУ.** `ShortfallMicroUSD` ненулевой и таков, что пополнение на него допускает + тот же вызов. ⚠ Пин на «событие вообще есть» вырожден — утверждай ЧИСЛО. +3. **Пополнил потолок → резюм ДОДЕЛАЛ юнит.** Два прогона в одном тесте: без пополнения резюм не делает + свежих платных вызовов и не уходит в бесконечный цикл; после пополнения — доделывает и отдаёт текст. + ⛔ Это и есть главный пин пака: сегодня второй прогон умирает вечно. -⛔ **И назови СУЖЕНИЕ, иначе будешь ловить недостижимое:** утверждение верно в ОКНЕ, когда остатка потолка -хватает на попытку 0 каждого следующего юнита. Кончился потолок совсем — попытка 0 следующего юнита -обязательна, отказ по ней штатно останавливает книгу, и `exit 4` возвращается ЗАКОННО. Фикстуру строй -именно на этой развилке. - -⛔ **ПИН НА РЕЗЮМ — И ЗДЕСЬ ПЕРВАЯ РЕДАКЦИЯ ПРОМТА ЗАПИНИЛА БЫ ОТМЕНУ РАТИФИЦИРОВАННОГО, не заметив.** -Я требовал «после такого прогона резюм не делает НИ ОДНОГО свежего платного вызова». Но денежный флаг — -единственная причина, которую ЛЕЧАТ ДЕНЬГАМИ: сегодня «поднял потолок → резюм → читатель получил хороший -перевод» ратифицировано (`backend/internal/pipeline/escalation.go`, греп `durably pauses and resumes`, -`D4`). Мой пин сделал бы юнит навсегда деградированным, лечимым только редрайвом. -⇒ **делай РОВНО так: денежная причина ПЕРЕ-АТАКУЕМА на резюме** — как `FlagCancelled`, единственная -причина, у которой `resolvedForResume` ложен именно потому, что это «оплачено, но не сделано» -(`backend/internal/pipeline/cutcall.go`, греп `resolvedForResume`). Пинь ДВЕ вещи: **без поднятия потолка -резюм не делает свежих платных вызовов и не умирает** · **после поднятия потолка резюм ДОДЕЛЫВАЕТ юнит**. +⚠ **Про коды выхода.** Прежняя редакция промта требовала `exit 2` вместо `exit 4`. **Снято:** при штатной +остановке код остаётся тем, каким он и был для остановки по потолку. Предмет пака — не смена кода выхода, а +то, что за ним стоит: **строка вместо пустоты и лечимость пополнением.** ⚠ **Два гейта поймают тебя сами — назову, чтобы не терять круг:** `TestEveryFlagReasonIsRanked` потребует -завести новую причину в `flagSeverity` (`backend/internal/pipeline/status.go`), а +завести новую причину в `flagSeverity` (`backend/internal/pipeline/status.go`), если ты её заводишь, а `TestEveryOperatorMessageIsCatalogued` — внести новое сообщение в -`backend/internal/pipeline/testdata/operator-messages.txt`. +`backend/internal/pipeline/testdata/operator-messages.txt` (сегодня в каталоге 134 строки, из них о потолке +уже говорят четыре — посмотри их прежде, чем писать пятую). ### 4.3 ⛔ ДЕФЕКТ, КОТОРЫЙ ТВОЙ ПАК НАКОРМИТ — и он НЕ латентный. Чинить ЗДЕСЬ