textmachine/docs/PLATFORM_RUN_TELLS_THE_TRUTH_SESSION_PROMPT.md

31 KiB
Raw Blame History

Платформенный пак: ПРОГОН НЕ ВРЁТ О СЕБЕ — ни оператору, ни покупателю, ни счёту

Выдача №15 очереди №23. Зона — platform/. Платных вызовов моделей пак НЕ делает: $0. ⚠ Числа и адреса этого промта сняты мной по дереву 17.09. Те, на которых ты строишь решение, пере-снимай сама — заимствованное число улики не заменяет. За сутки в моих же источниках адреса уезжали семь раз, а два вели в файлы, которых нет.

1. Какая проблема и что решит твой результат

Платформа сегодня рассказывает о прогоне три разные неправды, и каждая стоит денег или доверия.

Оператору: прогон, который мы сами убили по своему же грейсу, не имеет терминального кадра — он падает в причину отказа и читается как service_error. Человек видит поломку там, где сработала наша политика.

Покупателю: когда проекция паркуется, оператор узнаёт об этом ЧЕТЫРЬМЯ способами — столбцом в базе, писателем, выпиской и гейджем, — а платящий видит замерший экран без единого слова. Живая половина этого дефекта именно пользовательская.

Счёту: прогон, вышедший без маркера, перезапускается БЕЗ ВСЯКОГО ограничения — капа попыток у платформы нет. На дочитанной книге движок выходит сразу и снова без маркера, и цикл повторяется каждым проходом свипа. Естественного ограничителя в деньгах нет: перезапуск сперва рассчитывает старую попытку, потом берёт новый холд, а попытка ничего не потратила ⇒ холд возвращается целиком и берётся заново. Тревога при этом молчит, потому что счётчик отсрочек очищается «уликой» каждого рестарта и порог зависания недостижим.

Твой результат: о прогоне нельзя узнать неправду — ни из экрана, ни из кадра, ни из суммы согласия.

2. Зона записи и git

Пишешь platform/ и свой зонный журнал platform/docs/platform-PROGRESS.md (⚠ в docs/PROGRESS.md платформа НЕ пишет — решение владельца). Трогаешь platform/BACKLOG.md и platform/docs/DEFECT_REGISTER.md как свои. Не коммитишь — дерево передаёшь мне, лендинг мой. Движок (backend/) не правишь ни строкой. .env не читаешь.

КАНОН КОНТРАКТА — ОТДЕЛЬНАЯ ПРОЦЕДУРА, и без неё три пункта заказа повисают между зонами. Пометка §4.2, слово покупателю §4.4 и, возможно, видимость предела §4.1 приземляются в docs/architecture/14-api-contract/ — каталог вне твоей зоны. Порядок такой: форма (что за поле, какие значения, кто читает) → пинг мне → ратификация → стройка; прецедент зоны — форма ратифицируется ДО кода. Пока ратификации нет, контракт-видимая половина останавливается на предложении, и это законный исход пункта, а не недоделка. ⚠ git stash не используй: стек общий с другими рабочими копиями. Нужно отложить — временный коммит.

3. Карта чтения — ЗАКОН, пять позиций

  1. CLAUDE.md в корне — канон проекта целиком.
  2. platform/internal/runs/reconcile.goсвип, исходы попытки, перезапуск, парковка. 1932 строки, читать целиком: весь пак живёт здесь и в соседях по пакету.
  3. platform/internal/jobs/jobs.goочередь и её предел попыток. Короткий файл; докблоки несущие.
  4. platform/docs/STACK_DECISIONS.md — ловушки стенда и ЕДИНСТВЕННЫЙ носитель числа гейтов батареи (787 строк). Читать ДО первой команды: половина ловушек ниже взята оттуда, и там их больше.
  5. platform/docs/DEFECT_REGISTER.md — твой реестр: форма строки, статусы, вес. Ряды пака в нём уже есть.

⚠ Ряды единого трекера (docs/BACKLOG.md) — 394 · 396 · 398 · 399 · 400 · 416 · 486. Их тексты несут замеры и разбор; читай по номеру, а не целиком.

4. Что построить

4.1. Кап попыток прогона — ДЕЛАЙ; предмет уже стоит в твоём регистре, и пак его ПЕРЕ-ВЗВЕШИВАЕТ (ряд 398)

Сегодня предела попыток У ПРОГОНА нет. Замер, который ряд предъявляет и который я пере-снял: MaxAttempts в не-тестовом коде платформы — 7 хитов, все в очереди (platform/internal/jobs/jobs.go:57=MaxAttempts is 1 on purpose, значение на :63), и ни одного про попытки прогона.

⇒ Построй предел, после которого платформа перестаёт перезапускать и говорит об этом. Форма твоя, но два условия жёсткие: предел обязан быть виден оператору ДО того, как упрётся, и исчерпание не должно выглядеть как авария — это наша политика, а не поломка (см. §4.2).

Предмет уже заведён у тебя: PD-215, вес info, с прописанным лечением («счётчиком перезапусков на прогон или бэк-оффом по времени последней попытки») и с оценкой «денег не теряется». ⇒ пак его ПЕРЕ-ВЗВЕШИВАЕТ, а не заводит дубль: цена не в потраченном, а в том, что цикл бесконечен и тревога о нём молчит. Закрой или пере-взвесь ИМЕННО PD-215, назвав, почему вес меняется.

И механизм я в первой редакции назвал неверно — поймал опровергатель. Счётчик отсрочек не «очищается уликой»: он живёт на ПОПЫТКЕ (platform/internal/pgstore/runs.go:404=set reconcile_failures = reconcile_failures + 1, очистка :443), а рестарт открывает НОВУЮ строку попытки (platform/internal/pgstore/runs.go:1568=next := attemptNo + 1) — счётчик стартует с нуля ПО ПОСТРОЕНИЮ, и гейдж зависания читает то же поле. ⇒ кап, положенный на попытку, повторит дефект. Выражай его через номер попытки прогона, а не через счётчик внутри попытки.

4.2. Убитый нашим грейсом — что от ряда 394 ОСТАЛОСЬ ( половина уже вылечена)

ИСПР. перед выдачей: первая редакция этого блока заказывала ПОСТРОЕННОЕ, и поймал опровергатель, не я. Я писал, что наш грейс читается как service_error. Неверно с 11.09: в аварийную причину уходят РОВНО шесть значений (platform/internal/runs/reconcile.go:1180="oom-kill", "core-dump", "watchdog"), нашего таймаута среди них нет, и всё прочее возвращается прерыванием (platform/internal/runs/reconcile.go:1183=return "interrupted"). Лечение стерегут два пина — platform/internal/runs/gracekill_test.go:21=func TestAKillByOurOwnGraceIsNotReportedAsABrokenDeployment и :62, — залендено коммитом 00d590e и перечислено принятым в D39.242. Ряд 394 протух: он от 10.09, лечение от 11.09.

Что осталось: у класса нет СВОЕЙ пометки — он отвечает interrupted вместе с «кончилось, и никто не описал чем». Вреда «поломка вместо политики» больше нет, но и различить политику от чужого обрыва нельзя.

И цена этой пометки — не правка Go, а ратификация. RunFailureReason — ЗАКРЫТЫЙ словарь в ТРЁХ носителях: канон docs/architecture/14-api-contract/openapi.yaml:1708=RunFailureReason:, валидатор platform/internal/pgstore/sink.go:607=validFailureReason is the contract's RunFailureReason и констрейнт базы platform/internal/pgstore/migrations/00016_read_surface.sql:58=check (failure_reason is null or failure_reason in с тремя значениями. ⇒ четвёртое значение требует бампа канона И миграции. ⚠ Ловушка, которой не было в первой редакции: этот словарь не стережёт НИ ОДИН гейт — хитов в platform/internal/gates/ ноль при контроле 12 у соседнего словаря. Значит расхождение с каноном пройдёт make check ЗЕЛЁНЫМ (на этом хосте тесты с базой скрыты) и упадёт в проде на констрейнте. ⇒ порядок обязателен: форма → пинг мне → ратификация → стройка.

⚠ И вторая неточность прежней редакции: «при записанном намерении отдаёт stopped» верно только когда намерение СТАРШЕ маркера; обратный случай запинен тем же файлом.

4.3. Перезапуск без намерения — ПЕРЕ-РЕШИ ЯВНО, не наследуй (ряд 396)

Платформа перезапускает прогон, вышедший с кодом остановки БЕЗ записанного намерения (platform/internal/runs/reconcile.go, греп interruptedBySomeoneElse, 5 вхождений). Решение осознанное, с записанным доводом — перезагрузка хоста против ручного systemctl stop, асимметрия цен, — поэтому строка не о дефекте.

Но инвариант D39.240 обязан эту развилку пересмотреть ЯВНО: сегодня она означает, что прогон, остановленный мимо продуктовой двери, будет поднят заново и продолжит тратить. ⇒ назови выбор своими словами, с ценой каждой стороны, и запини выбранное. Отказ пере-решать — законный исход, если довод сильнее; молчаливое наследование — нет.

4.4. Покупателю сказать, что происходит (ряд 399)

Половину этого ряда НЕ трогай — она построена, и первая редакция строки этого не знала. Оператор видит парковку ЧЕТЫРЬМЯ способами, и список берётся у ряда 399, а не у меня: столбец миграции 00034_spawn_proof_and_park.sql · писатель platform/internal/pgstore/runs.go:1888=MarkParked records that this attempt · чтение на выписке · гейдж platform/internal/metrics/metrics.go:109=parked_attempts. Плюс операторский листинг и строка лога с ограничителем частоты platform/internal/runs/reconcile.go:944=func (s *Service) sayParked. ⚠ Сайт вызова platform/internal/runs/reconcile.go:532=s.Store.MarkParked(ctx, l.AttemptID в этот счёт НЕ входит: оператор его не видит, это адрес для чтения кода.

Живая половина — ПОЛЬЗОВАТЕЛЬСКАЯ, и только она: платящий видит замерший экран без объяснения. Механизм — курсор встречает чужой хендшейк и проекция останавливается до конца попытки (platform/internal/ingest/tail.go:36=another stream begins here, обработка platform/internal/runs/reconcile.go:780=errors.Is(err, ingest.ErrForeignStreamAhead)).

⚠ И возьми у кода его собственную оговорку, она несущая: platform/internal/runs/reconcile.go:791=may be this very run, alive and writing — «чужак» может оказаться этим же прогоном. ⇒ формулировка для покупателя не должна утверждать чужое вмешательство.

4.5. Смерть между правкой банка и записью о ней (ряд 400)

platform/internal/runs/bank.go, греп RecordBankMove: ветка ошибки стора обработана — клиент пере-шлёт, всё сходится, — а смерть ПРОЦЕССА между применением правки и записью факта канала не имеет. Цену называет собственный комментарий кода: отметка о движении банка остаётся пустой ⇒ следующий прогон допускается без пере-снапшота ⇒ умирает на снапшот-гарде уже ПОСЛЕ взятого холда.

Это тоже D39.240: «не оставить мусор» и «правильно возобновить» — ровно оно.

4.6. Согласие соразмерно пере-покупаемому (ряд 416)

Замер: на резюме после банк-стопа согласие выставляется на весь бюджет прогона, тогда как фактически пере-куплено на 78× меньше. От пере-покупки спасли чекпойнты, а не потолок согласия: промахнись хеши промптов — и черновая волна была бы куплена заново под уже подписанным согласием.

Но заказ СУЖЕН, и вот почему — находка опровергателя, проверенная мной. Величины, которой можно ограничить согласие, у платформы в точке решения НЕТ по построению, и код это уже записал: platform/internal/runs/reconcile.go:1600=a projection to cap against does not exist here со ссылкой на эрратy 28.08-к. Посчитать проекцию умеет только движок, а его ты не правишь. Вдобавок стор расширяет согласие ТОЛЬКО вверх (platform/internal/pgstore/runs.go:1605=accept_rebill_micro = greatest(r.accept_rebill_micro, довод на :1483=Widening only) ⇒ меньшее число на втором резюме молча не применится. И ряд 416 размечен платформа+бэкенд.

Твоя половина — ПРОВЕНАНС и видимость, а не величина: человек должен видеть, ЧТО именно он подписывает и откуда взялась сумма, а молчаливое расширение не должно съедать пере-решение. Движковую половину (проекция пере-покупки) назови отдельной строкой в отчёте как заказ следующему паку — строить её здесь нельзя. ⚠ И знай контекст, иначе решишь не тот вопрос: у движка УЖЕ есть ратифицированный порог согласия; вопрос не «есть ли потолок», а почему бланкетное согласие платформы его отменяет.

4.7. Довесок, который чинит доверие ко всем твоим числам (ряд 486)

Мутационный харнесс зоны засчитывает поимкой ЛЮБОЕ красное и не требует зелёного базового прогона пакета до первой посадки. ⇒ пакет, красный по своей причине, отчитается каждой посадкой как о поимке, и «посадок N, поймано M» становится утверждением о другом предмете.

Два пункта: зелёный базовый прогон как условие, и падение ИМЕННО названного пина как условие засчёта. Десяток строк, но без них числа твоей же приёмки недоказательны.

4.8. Стандарт работы — слово владельца, ратифицировано D39.258 п.3

Чистый код, рефакторинг там, где он нужен, никаких заплаток, техдолга не копить. Мерило проекта — качество художественного перевода длинной книги; механизм строится там, где несёт качество или деньги.

5. Самопроверка ИСПОЛНЕНИЕМ

Гейт зоны — make check из platform/ (build vet fmt lint sqlc-check + go test ./... -race -count=1), 20 пакетов. Самопроверкой он не является. Сверх него — адверсариальный проход по своей готовой работе; субагентов поднимать разрешаю явно. Направление даю я — четыре места, где этот пак мягок:

  1. Кап попыток (§4.1) — посадка, снимающая предел, обязана краснеть ИМЕННО пином предела, а не соседним тестом.
  2. Пометка нашего грейса (§4.2) — проверь, что она доезжает до человека, а не только до базы: «поле заполнено» и «оператор это видит» — разные утверждения.
  3. Слово покупателю (§4.4) — фикстура, где «чужак» есть ЭТОТ ЖЕ прогон. Если формулировка врёт на ней, она врёт.
  4. Согласие (§4.6) — пин на величину, а не на факт наличия согласия.

ЧЕТЫРЕ ЛОВУШКИ ГЕЙТА, каждая даёт ложную зелень; все из носителя зоны, и там их больше:

  • Вердикт читается строкой MAKE_EXIT из лога, а не уведомлением харнесса. Замер зоны 17.09: первый прогон дал MAKE_EXIT=2, потому что sqlc лежал вне PATH, — а уведомление сказало «exit code 0».
  • Пины инструментов: golangci-lint ровно 2.12.2, sqlc ровно v1.31.1, Go ≥1.26.6. tools-check стоит первым и убивает прогон до единого теста. ⚠ Лечение, а не только ловушка: на этом хосте линтер и Go пину удовлетворяют, а sqlc нужной версии установлен, но НЕ на PATHexport PATH=$PATH:$(go env GOPATH)/bin перед make check, иначе получишь тот самый MAKE_EXIT=2 при «успешном» уведомлении.
  • «Ноль FAIL» из прежних отчётов СНЯТО самим носителем зоны: с поднятыми гейтами замер 11.09 его опроверг, и STACK_DECISIONS.md предупреждает — следующая сессия, прочитав «0 FAIL», получит красное и решит, что сломала сама. Красное на входе не обязательно твоё: сперва развернись на чистом HEAD и сравни. ⚠ И условий, дающих скипы, ШЕСТЬ, а не четыре: сверх четырёх гейтов — отсутствующий артефакт контраста движка и занятый локальный порт, причём один тест умеет скипнуться двумя условиями сразу, поэтому считать их по одному прогону нельзя.
  • Без строки подключения к базе батарея МОЛЧА прячет около трёхсот тестов и остаётся зелёной. Гейтов батареи четыре, и единственный их носитель — platform/docs/STACK_DECISIONS.md, не этот промт. make conditions печатает условия хоста; на этой машине все шесть переменных сейчас не заданы.
  • Движковый бинарь второго гейта пересобирается из текущего backend/, не берётся со стенда: стендовый tmctl давал три красных теста платформы с текстом про движковый реестр моделей — падала платформа, лгал бинарь движка.

Полнота сверяется СПИСКОМ: comm -23 списка go list ./... против вердиктов лога обязан быть пуст, и контроль перевёрнутого сравнения тоже печатается. «Нет слова FAIL» полнотой не является.

И про стенд, чтобы ты не потеряла час: отчёт прошлой сессии зоны обещает копии tm-mut-94-1709 и tm-head-94-1709 «оставленными для приёмки» — этих каталогов на машине НЕТ (проверено). Уцелел только Postgres стенда: сокет на месте, порт 55433, поднят с пустым listen_addresses. ⚠ TCP-проба даёт Connection refused на ЖИВОМ сервере — живость спрашивай тем же DSN, которым читают тесты. ⚠ Демон гаси только по PID: шаблон по пути совпадает с собственной командной строкой оболочки и убивает вызвавшего.

6. Оси ревью

Две-три, можешь заменить с аргументом: денежная (ни один путь не берёт холд, который некому вернуть) · правда о состоянии (то, что видит человек, совпадает с тем, что случилось) · возобновление без мусора (D39.240: остановка отличима от аварии, возобновление корректно).

7. Записка-план

До первой правки кода — короткая записка в свой зонный журнал: какие из семи предметов берёшь, от каких отказываешься и почему, где проводишь границу класса «наша политика» против «авария», и что решила по развилке §4.3. ⚠ Сбрасываемыми без вреда для пака я считаю два: 416 (сужен до провенанса) и 486 (довесок-инструмент); остальные пять несут предмет. Решишь иначе — назови довод.

8. Заявление = команда

Любое число и любая категорика отчёта идут с командой, которой они получены. «Батарея зелёная», «мутации пойманы», «скипов столько-то» без команды — утверждение О проверке, а не проверка. ⚠ Рядом с любым НУЛЁМ печатай контрольную величину: «ноль строк» и «нет такой таблицы» в выводе неразличимы.

9. Эхо-протокол старта

Первым действием — десять строк своими словами: скоуп, инварианты, чего не делаешь. Дословный пересказ промта подтверждает канал, но не понимание.

10. Что не удалось — и где прибор слеп

Секция «не удалось / не проверено» обязательна. ⚠ И вторая половина: «где мой прибор слеп и я это знаю» — что невидимо, почему не чинила, чем закрывается. Норма зоны: в коде названо, в отчёте нет — значит для следующей смены не названо.

11. Канал вопросов, право отказа, советчик

Конфликт промта с кодом — пинг, не интерпретация: промт мог устареть между написанием и чтением. Право сказать «этого делать не надо» с аргументом у тебя есть, и §4.3 я жду скорее пере-решённым с доводом, чем построенным.

Тебе разрешён один старший советчик (model: "fable" явно) на развилку, которую не закрывает своё суждение; досылай вопросы ему, а не поднимай новых. Он советчик, не источник истины: его посылки проверяются деревом — мой собственный советчик по соседнему паку дал девять замечаний, и часть не подтвердилась.

12. Прямой канал

Механизм — CLAUDE.md §«Связь между сессиями»; здесь не повторяется, но исполняется первым делом: впиши свой блок в /tmp/textmachine-channel (роль · имя из ListAgents · ссылка на ЭТОТ файл · дата · что делаешь и чего не трогаешь), дописывая в конец и не трогая чужие блоки. Адрес оркестратора бери оттуда же, туда же шли эхо из блока 9.

Адрес из файла — НЕ доказательство, что сессия жива: файл переживает смерть сессии, а ListAgents — нет.Нужной роли в файле нет, файла нет или имя не отвечает ⇒ КАНАЛА НЕТ, и это НОРМАЛЬНЫЙ случай: НЕ опрашивай сессии подряд. Вопрос — секцией в свой отчёт, работа продолжается.

13. Критерий завершённости — проверяемый

  • у каждого пункта заказа исход: сделано · не делаю с доводом · пинг;
  • круги сошлись: последний не дал новых находок, прежние закрыты таблицей «находка → что сделано → чем предъявлено»;
  • таблица мутаций полная, выжившие названы поимённо, и харнесс §4.7 уже чинит её смысл;
  • числа сняты после последней правки, у каждого команда повторения; всё живое — в дереве;
  • гейт зоны зелёный целиком (сверка списком целей, а не отсутствием слова FAIL), MAKE_EXIT предъявлен строкой;
  • сказано явно: «работа завершена, править не планирую».

Приложение. Ряды и контракт

Строит: 398 · 394 · 396 · 399 · 400 · 416 · 486. Условия работы: контракт зоны сейчас 0.16.0, и его стережёт независимый гейт, читающий САМ канон, а не копию числа — если твоя работа контракта не касается, версию не трогай вовсе. ⚠ Ряд 397 (пять рядов реестра про холды — один предмет) в пак НЕ входит, но соседствует: если твоя правка его задевает, назови это, а не чини молча.