diff --git a/docs/BACKEND_SOFT_STOP_SESSION_PROMPT.md b/docs/BACKEND_SOFT_STOP_SESSION_PROMPT.md index 599bf0ae..3a8939d5 100644 --- a/docs/BACKEND_SOFT_STOP_SESSION_PROMPT.md +++ b/docs/BACKEND_SOFT_STOP_SESSION_PROMPT.md @@ -1,7 +1,7 @@ > ⚠ **ЭТО ХЕНДОФФ-ПРОМТ ДВИЖКОВОЙ СЕССИИ. Он живой и исполняется.** Прочитай `CLAUDE.md`, потом этот > файл целиком, потом — его карту чтения (§3) и больше ничего, пока не появится вопрос. > ⛔ **ПАК ПАРНЫЙ.** Твоя половина не лендится в одиночку: у платформы есть своя, и они принимаются -> вместе (`D39.234` п.2). Что это значит практически — §2 и §13. +> вместе (`D39.234` п.2). Что это значит практически — §2 и §12. # Пак бэкенда: «ДВЕ ОСТАНОВКИ» @@ -41,10 +41,11 @@ again», а платформа в собственном коде пишет, ч зона оркестратора». ⛔ **Пара.** Платформенная половина (`mode` у `/stop`, грейс, путь второго сигнала, чтение оценочных -строк) делается параллельно другой сессией. Ты её кода не пишешь и не правишь. Но твой кадр событий — -её вход, поэтому **форму кадра ты обязан объявить в отчёте раньше, чем закончишь**: оркестратор сведёт -её с платформенной половиной. Если тебе нужно от платформы поведение, которого нет, — это вопрос -оркестратору, не правка. +строк) делается параллельно другой сессией. Ты её кода не пишешь и не правишь. + +⛔ **ФОРМУ КАДРА НЕ ПРОЕКТИРУЕШЬ — она уже ЗАФИКСИРОВАНА нотой `D39.235` п.1**, до начала работ и именно +затем, чтобы две зоны не решили по-разному. Читаешь её и делаешь РОВНО так (§4.7). Если считаешь форму +неверной — это вопрос оркестратору ДО кода, а не правка. ## 3. Карта чтения — ЗАКОН, дальше только по её ссылкам @@ -58,6 +59,9 @@ again», а платформа в собственном коде пишет, ч `exitCode` (~115–130); и `backend/internal/pipeline/events.go` — `terminal()` (начало ~479, ветки исходов ~505–530: их ПОРЯДОК там сам объявлен утверждением, читай с начала функции). 5. `backend/internal/runevents/runevents.go` — кадр `Finished` (~255+) и **комментарий к полю `Volume`**: он объясняет, ПОЧЕМУ у объёмного потолка нет своего `outcome`, и это твой прецедент. +6. ⛔ **Тело ноты `D39.235` п.1** — `docs/architecture/05-decisions-log.md`, греп `^## D39.235`. Это ФОРМА + КАДРА, зафиксированная до начала работ, и она обязательна к исполнению буквой. Шестая позиция карты + намеренно: без неё ты спроектируешь то, что уже решено, и разойдёшься с платформенной половиной. Всё остальное — грепом по мере вопросов. Тела нот: `docs/architecture/05-decisions-log.md`, номер грепается `^## D<номер>`; статус одним хопом — реестр `docs/architecture/05-decisions-index.md`. @@ -75,7 +79,7 @@ run reporting empty translations as exit-0 success»). ⇒ заведи сентинел (имя твоё, `*StopRequested` — предложение) со **своим слотом** в `fail()` волны, рядом с тем, что уже есть у `*CeilingHalt`. Ранжирование при столкновении — твоё решение, но объясни его в коде: паника старше остановки, остановка старше обычной ошибки — или иначе, если у тебя есть -довод. +довод. ⚠ Один случай ранжирования решён за тебя и лежит в §4.15(г) — прочти его прежде, чем выбирать. ### 4.2 Где именно мягкая остановка отказывает — **делай РОВНО так** @@ -138,8 +142,9 @@ worker already holding an item runs it to completion under a context nobody canc ⇒ **решение, которое принимаю я, а не ты:** партии, уже купленные к моменту мягкой остановки, **НЕ применяются**, пас выходит остановкой ДО применения. Довод тот же, что в §4.4: пере-сев банка из половины двигает снапшот редакторской волны для всех последующих прогонов, а половина паса — это -половина банка. Деньги за купленные партии уже потрачены и остаются в леджере; терять их обидно, но -тихо сдвинутый снапшот дороже. +половина банка. ⚠ И ничего не теряется: уже купленные партии лежат чекпойнтами и следующий прогон реплеит их за $0 +(`backend/internal/pipeline/terminologist.go`, греп `Already-paid batches cost nothing`). Беречь +частичный результат применением — не бережливость, а тихий сдвиг снапшота. У паса есть СВОЙ попартийный приём (`terminologist.go:940–970`), где он уже умеет решать «сколько партий беру» и говорить об этом вслух — это и есть место, где мягкая остановка говорит «новых партий @@ -175,9 +180,21 @@ a ratified dictionary»), и на 5 завязана логика платфор в потоке, заставило бы два канала сообщать РАЗНЫЙ исход одного прогона. **У чисел словаря нет, поэтому факт едет числами** — присутствие поля и есть признак. -⇒ различие «мягко / оборвано» едет ПОЛЕМ с числами. Форму предложи сам и объяви её в отчёте ДО того, -как закончишь (§2): оркестратору её сводить с платформенной половиной. Мой набросок, спорь с ним — -`stop: {mode: soft|hard, in_flight_finished: N, in_flight_cut: M, cut_estimated_micro_usd: X}`. +⇒ различие «мягко / оборвано» едет ПОЛЕМ С ЧИСЛАМИ, и **форма зафиксирована `D39.235` п.1 — делай РОВНО так:** + +- `Finished.Stop{ mode: "soft"|"hard", in_flight_finished, in_flight_cut, cut_estimated_micro_usd }`. + ⛔ **Правило присутствия — «есть, если остановку ЗАПРОСИЛИ», а НЕ «если исход stopped».** Прецедент + дословно у соседнего поля: `Money` present iff a ceiling was REACHED, not iff the outcome is ceiling. + Это важно ровно в случае §4.15(г): остановку запросили, а прогон уехал `failed` из-за соседа — поле + обязано БЫТЬ. +- `estimated{ rows, micro_usd }` едет **на ОБОИХ кадрах** — и на кумулятивном `spend`, и в `Money` + терминального. Довод: платформа отвечает пользователю «не больше Y» ВО ВРЕМЯ прогона, а число, + появляющееся только в конце, на этот вопрос не отвечает. +- `Money` на исходе `stopped` едет **всегда, когда счётчики есть**. Сегодня он `nil` не только без + потолка, но и когда волн ещё не было (`backend/internal/pipeline/events.go`, греп `moneyLedger`): + стоп до волн — ингест, сид — счётчиков не имеет, и это законная пустота, а не умолчание. +- **Минор словаря потока `1.3 → 1.4`** (`backend/internal/runevents/runevents.go`, греп `StreamVersion`). + ⚠ Платформа поднимает СВОЮ константу сама — это её половина, не твоя. ### 4.8 Строка **377** — оплаченный обрыв, за которым ретрай УСПЕЛ, исчезает бесследно @@ -201,7 +218,7 @@ a ratified dictionary»), и на 5 завязана логика платфор `backend/internal/pipeline/export.go:307`) и сообщает читателю «из единицы пропал текст», тогда как пропала лишь незаконченная работа. -⛔ **Простого фильтра НЕДОСТАТОЧНО.** ⚠ Испр. до выдачи: прежняя редакция называла это «замерено движковой зоной» — носителя такого замера НЕТ (наряд ставит по этой позиции «пинг», в `docs/PROGRESS.md` следов нет). Это рассуждение, а не замер, и доказать сценарий фикстурой — твоя работа. Если применить +⛔ **Простого фильтра НЕДОСТАТОЧНО, ЕСЛИ сценарий ниже достижим — и доказать это твоя работа.** ⚠ Испр. до выдачи: прежняя редакция называла это «замерено движковой зоной» — носителя такого замера НЕТ (наряд ставит по этой позиции «пинг», в `docs/PROGRESS.md` следов нет). Это рассуждение, а не замер, и доказать сценарий фикстурой — твоя работа. Если применить `resolvedForResume` (`cutcall.go:253`) и на этом остановиться, единица уйдёт `ok`, МОЛЧА не досчитавшись текста члена: `DroppedMembers` = 0, `DroppedReason` пуст. Это обмен ЛЖИ на МОЛЧАНИЕ, и верно ни то ни другое. ⇒ нужен ТРЕТИЙ признак: «единица неполна ИЗ-ЗА ОСТАНОВКИ». @@ -242,8 +259,9 @@ a ratified dictionary»), и на 5 завязана логика платфор Платформа её не читает и прямо просит: `platform/internal/pgstore/credits.go:235` — «publish the count and sum of estimated-price rows beside committed_usd, which is what would let this side say "at most Y"». -⇒ **твоя половина — довезти счёт и сумму оценочных строк до кадра.** Чтение — платформенная половина. -Форму согласуй тем же объявлением, что и §4.7: это один шов, и два поля на нём не должны спорить. +⇒ **твоя половина — довезти счёт и сумму оценочных строк до кадров.** Чтение — платформенная половина. +Форма — та же `D39.235` п.1, что в §4.7: `estimated{rows, micro_usd}` на `spend` И в `Money`. Ничего не +согласовывай отдельно: это один шов, и он уже описан нотой. ### 4.12 Строка **379**(а) — пин, решаемый окном стенных часов — **бери, раз всё равно трогаешь** @@ -275,7 +293,7 @@ provider on a call in flight» (`stagerun.go:990`) уже знает и числ **(а) ⛔ ГРАНИЦЫ ФАЗ, а не только волны.** §4.4 покрывает остановку, пришедшую ВО ВРЕМЯ волны. Пришедшая МЕЖДУ фазами уводит прогон платить за редактуру: источник обязаны спрашивать переходы — после черновой волны → перед майнинг-стопом → перед подписным стопом → перед редакторской волной, включая -авто-продолжение с пере-севом (`backend/internal/pipeline/mining.go`, греп `resnapshot`/пере-сев). +авто-продолжение с пере-севом (`backend/internal/pipeline/mining.go`, греп `bank-mining/auto-continue`). **(б) `res.Volume = nil` на мягкой остановке** — как это делает потолок. Плановые счётчики после остановки посреди работы врут; довод дословно — в комментарии к полю `Finished.Volume`. @@ -382,8 +400,8 @@ provider on a call in flight» (`stagerun.go:990`) уже знает и числ 2. Прогон с недоделанными единицами не может уехать как успех. 3. Ожидание мягкой остановки ограничено ОДНОЙ летящей попыткой, и это ограничение запинено. 4. Второй сигнал даёт сегодняшнюю жёсткую остановку, **третий — дефолт процесса** (проверяется, а не подразумевается). -5. Кадр `finished` несёт числами, мягко или жёстко всё кончилось, и счёт с суммой оценочных строк. - Форма объявлена в отчёте ДО сдачи. +5. Кадры несут числами, мягко или жёстко всё кончилось, и счёт с суммой оценочных строк — **в форме + `D39.235` п.1, буква в букву**, включая правило присутствия `Stop` и минор `1.3 → 1.4`. 6. Строки **377**, **375**, **372**, **379**(а) закрыты, каждая с предъявленной уликой (377 и 375 — вторым лендингом, §7). 6-бис. Резюм после мягкой остановки делает ноль платных вызовов по отвеченным стадиям — запинено (§4.15д). 7. Батарея зелёная целиком (все пакеты, скипы названы), `counts.py --check` зелёный, мутационный прогон diff --git a/docs/PLATFORM_SOFT_STOP_SESSION_PROMPT.md b/docs/PLATFORM_SOFT_STOP_SESSION_PROMPT.md index 3d01026f..1f4c4e18 100644 --- a/docs/PLATFORM_SOFT_STOP_SESSION_PROMPT.md +++ b/docs/PLATFORM_SOFT_STOP_SESSION_PROMPT.md @@ -30,10 +30,14 @@ so it can finish the chunk it is paying for». Сегодня это ЛОЖНО, только оркестратор.** Итоги, вопросы и расхождения — ЗОННЫЙ журнал `platform/docs/platform-PROGRESS.md` (в `docs/PROGRESS.md` платформа не пишет). -⛔ **Пара.** Форму кадра событий («мягко или жёстко» + счёт и сумма оценочных строк) объявляет ДВИЖКОВАЯ -половина. Ты её потребитель. **Не проектируй кадр в одиночку**: если тебе нужно поле, которого движок не -объявил, — это вопрос оркестратору, он сведёт обе половины. Читай пинг №23 в своём журнале: там четыре -адреса, снятые оркестратором, и они — вход в эту работу. +⛔ **Пара.** Форма кадра событий **ЗАФИКСИРОВАНА нотой `D39.235` п.1 до начала работ** — именно затем, чтобы +две зоны не решили по-разному. Ни ты, ни движковая сессия её не проектируете: обе читают ноту и делают +РОВНО так. Что там: `Finished.Stop{mode, in_flight_finished, in_flight_cut, cut_estimated_micro_usd}` с +правилом присутствия «есть, если остановку ЗАПРОСИЛИ, независимо от исхода»; `estimated{rows, micro_usd}` +на ОБОИХ кадрах — на кумулятивном `spend` И в `Money` терминального; `Money` на `stopped` всегда, когда +счётчики есть; минор словаря потока `1.3 → 1.4`. Считаешь форму неверной — вопрос оркестратору ДО кода. + +Читай также пинг №23 в своём журнале: там четыре адреса, снятые оркестратором, и они — вход в эту работу. ## 3. Карта чтения — ЗАКОН, дальше только по её ссылкам @@ -47,6 +51,8 @@ so it can finish the chunk it is paying for». Сегодня это ЛОЖНО, 4. `platform/internal/ingest/exit.go` — словарь кодов выхода, `ExitStopped = 5` (~31–34). 5. `docs/architecture/14-api-contract/` — `/runs/{runId}/stop` (`openapi.yaml:754+`) и его описание в README контракта. +6. ⛔ **Тело ноты `D39.235` п.1** — `docs/architecture/05-decisions-log.md`, греп `^## D39.235`: ФОРМА КАДРА, + которую ты читаешь, а не проектируешь. Шестая позиция карты намеренно. Тела нот: `docs/architecture/05-decisions-log.md`, номер грепается `^## D<номер>`; статус одним хопом — `docs/architecture/05-decisions-index.md`. ⚠ ПОДНОМЕР (`D39.234` п.1) своего тела не имеет — это ПУНКТ @@ -83,6 +89,21 @@ so it can finish the chunk it is paying for». Сегодня это ЛОЖНО, деньги. ⚠ Контракт — ратифицированный носитель; правку контракта вносит оркестратор, не твоя рука: пришли точную формулировку описания в отчёте, я её ратифицирую минором. +### 4.2-бис ⛔ РЕЖИМ ОБЯЗАН ЖИТЬ В ЗАПИСИ НАМЕРЕНИЯ, а не только в запросе — **делай РОВНО так** + +`RequestStop` (`platform/internal/pgstore/runs.go`, греп `func (s *Store) RequestStop`) идемпотентен +СХЛОПЫВАНИЕМ: «pressing stop twice is one intent with the FIRST timestamp». Пока остановка одна, это +верно и красиво. С двумя — ломается: второе нажатие с `mode: hard` в базе неотличимо от первого, и +платформа, перезапущенная между двумя нажатиями, не узнает, что должна послать второй сигнал. + +Довод — принцип этой же функции, записанный над ней: «the intent is COMMITTED before systemd is touched, +so a platform that dies between the two still knows on its next sweep». Режим — часть намерения, значит +и он коммитится до systemd. + +⚠ Это схема ПЛЮС контракт: сегодня наружу едет булево `stop_requested` +(`docs/architecture/14-api-contract/openapi.yaml`, греп `stop_requested`). Форму предложи в отчёте — +ратифицирую минором; но САМ факт «режим переживает перезапуск платформы» не обсуждается. + ### 4.3 Путь второго сигнала — **делай РОВНО так** Жёсткая остановка = ВТОРОЙ сигнал движку. `systemctl stop` для этого не годится: он уже потрачен на @@ -114,7 +135,15 @@ committed_usd, which is what would let this side say "at most Y"». `estimated` — **2 хита, оба комментарии** (контроль: `committed_micro` — 1 хит; go-файлов вне тестов прибор прочёл **81**). -Движковая половина везёт их в кадр. **Твоя — прочитать и довести до пользователя.** Зачем это нужно +Движковая половина везёт их в ОБА кадра — и в кумулятивный `spend`, и в `Money` терминального +(`D39.235` п.1б). **Твоя — прочитать и довести до пользователя**, причём `spend` и есть тот канал, по +которому «не больше Y» отвечается ВО ВРЕМЯ прогона, а не после него. + +⛔ **И подними СВОЮ декларируемую версию декодера.** `platform/internal/ingest/events.go` держит +`StreamVersion = "1.1"`, и его собственный комментарий объясняет, что это УТВЕРЖДЕНИЕ о том, что билд +понимает, а не украшение («leaving it at 1.0 would have been a false one»). Читая поля минора `1.4`, ты +обязан сказать об этом константой — иначе повторишь ровно ту ошибку, от которой предостерегает твой же +комментарий. Зачем это нужно владельцу дословно: он разрешил списывать по ОЦЕНКЕ при УСЛОВИИ, что пометка стоит (`D39.230` п.1) — условие сегодня исполнено на одном канале из двух и потребителя не имеет. Пользователь должен видеть не только «списано X», но и «из них оценка Y». @@ -217,6 +246,8 @@ catches SIGTERM and exits 1». Движок на пойманном сигнал ## 11. Критерий завершённости 1. Грейс покрывает законное ожидание движка, число обосновано арифметикой и проверено исполнением. +1-бис. Режим остановки переживает перезапуск платформы (§4.2-бис), и это показано, а не заявлено. +1-трис. Оба кадра читаются в форме `D39.235` п.1, декларируемая версия декодера поднята. 2. У продукта есть обе остановки (`mode: soft|hard`, дефолт `soft`); путь второго сигнала работает и показан исполнением. 2-бис. `Stop` не блокируется на грейс — показано исполнением, своим замером, а не моим (§4.7). 3. Мягко остановленный прогон не читается реконсиляцией ни как падение, ни как дочитанная книга. diff --git a/docs/architecture/05-decisions-log.md b/docs/architecture/05-decisions-log.md index 08435d17..9b6ba882 100644 --- a/docs/architecture/05-decisions-log.md +++ b/docs/architecture/05-decisions-log.md @@ -5,6 +5,7 @@ > ⚠ **Эррата 15.08 (D39.132):** D39.131 п.2(а) перечисляет полосу отказов «10 конфиг · 11 источник · 12 лок · 19 безымянный» — читать через D39.132 п.2(г): полоса ДОПОЛНЕНА **exit 13 = `schema_mismatch`** (~~обе зоны сошлись на числе независимо~~; ратифицирован направлением, ФИНАЛИЗИРОВАН приёмкой D39.134). > ⚠ **Эррата 15.08-б (D39.134 п.2а, вписана по аудиту корпуса):** клейм «зоны сошлись на 13 НЕЗАВИСИМО» (строкой выше и в D39.132 п.2г) СНЯТ — платформа прочитала число из незакоммиченного дерева движка и сама это записала (`platform/internal/ingest/exit.go:63-64`); на силу ратификации не влияет. > ⚠ **Эррата 15.08-в (D39.136 п.5):** форма контракт-ревью «фазный воркфлоу оркестратора» ОТМЕНЕНА словом владельца тем же днём — исполняет ОТДЕЛЬНАЯ СЕССИЯ со своим онбордингом (норма D39.120 п.2; оркестратор — автор части ратификаций 0.2.3, author≠reviewer); запущенная воркфлоу-фаза 1 остановлена, её результаты выброшены; промт — `docs/CONTRACT_REVIEW_SESSION_PROMPT.md`. +> ⚠ **Эррата 10.09-е (`D39.235` п.1) — ДВА ПРАВИЛА ФОРМЫ КАДРА, КОТОРЫХ НОТА НЕ НАЗВАЛА, а без них две зоны решили бы по-разному.** **(а) `Money` на исходе `stopped` — «всегда» надо читать как «всегда, КОГДА СЧЁТЧИКИ ЕСТЬ».** `moneyLedger()` возвращает `nil` не только без потолка, но и когда волн ещё не было (`backend/internal/pipeline/events.go`, греп `moneyLedger`): остановка на ингесте или севе счётчиков не имеет, и это законная пустота, а не умолчание — иначе движок не смог бы исполнить ноту буквой. **(б) ПРАВИЛО ПРИСУТСТВИЯ `Stop` — «есть, если остановку ЗАПРОСИЛИ», а НЕ «если исход `stopped`».** Прецедент дословно у соседнего поля: `Money` present iff a ceiling was REACHED, not iff the outcome is ceiling. Важно ровно в одном случае, и он в паке назван: остановку запросили, а прогон уехал `failed` из-за упавшего соседа — поле обязано БЫТЬ. Оба правила внесены в оба промта до выдачи. ⭐ Нашёл не я: нота прошла ревью старшего коллеги уже после того, как я на неё сослался в паках. > ⚠ **Эррата 10.09-д (`D39.234` п.5) — «13 битых якорей, ВСЕ в чужих зонах» неверно: четыре были в МОЕЙ.** Прибор в тот момент показывал их в `docs/BACKLOG.md` (три указателя в контракт и один в отчёт `research/25`), а я, перечисляя корни целей, прочитал зону ЦЕЛИ вместо зоны ДОКА — тот же класс, что «знаменатель по одному глаголу» (`D39.232` п.5б). Все четыре пере-наведены 10.09. ⇒ норма: у битого якоря ДВЕ зоны, и чинит его владелец ДОКА, а не владелец цели. > ⚠⚠ **Эррата 10.09-г (`D39.215` п.3) — НОТА НЕСЁТ УСИЛЕНИЕ, КОТОРОЕ ПРОМТ ЭТОГО ЖЕ ПАКА ЗАПРЕЩАЕТ, И ПО ПРАВИЛУ «ЖУРНАЛ БЬЁТ ВСЁ» ПОБЕЖДАЕТ НОТА.** В теле п.3 стоит «прибор ШТРАФУЕТ ровно то поведение, которое сам же заказывает» и «СИСТЕМАТИЧЕСКИ поощряет якорение на черновике». Оба оборота — усиление. Что стоит: ПРОТИВОРЕЧИЕ КОНСТРУКЦИИ доказано. Чего НЕТ: замера ВЕЛИЧИНЫ — единственная улика (пометка «отход от черновика») встречается в сыром судейском массиве **ОДИН раз**. Это сказано в двух живых носителях: `docs/POLYGON_INSTRUMENT_REPAIR_SESSION_PROMPT.md` (греп `было преувеличением`) и строка бэклога **319** («испр. 06.09 автором строки»). ⇒ читать п.3 как «конструкция противоречива, величина не замерена»; живой носитель величины — строка **319**, и её даёт пак ремонта прибора. ⚠ Опасность была не теоретической: промт сам отсылает к ноте оборотом «ратифицировано так и не иначе», то есть сессия, пришедшая за номером, читала бы ровно ту формулу, которую промт двумя строками выше запретил. Тело ноты не переписывается (D23.3). > ⚠ **Эррата 10.09-в (`D39.231` п.4) — ОТКРЫТЫЙ ВОПРОС ТОЙ НОТЫ ЗАКРЫТ В ТОТ ЖЕ ДЕНЬ.** Пункт 4 оставлял «чинить ли 31 позицию наряда» ждать вердикта о мягком останове. Вердикт есть: `D39.234` п.1 — форма «повтор ужесточает», мягкая остановка не рвёт летящие вызовы. Наряд при этом отработан целиком (36 позиций: 34 «сделано», 2 «пинг»), так что пере-считывать нечего; работа по мягкой остановке живёт строкой **381** и ещё не заказана.