31 KiB
Платформенный пак: ПРОГОН НЕ ВРЁТ О СЕБЕ — ни оператору, ни покупателю, ни счёту
Выдача №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. Карта чтения — ЗАКОН, пять позиций
CLAUDE.mdв корне — канон проекта целиком.platform/internal/runs/reconcile.go— свип, исходы попытки, перезапуск, парковка. 1932 строки, читать целиком: весь пак живёт здесь и в соседях по пакету.platform/internal/jobs/jobs.go— очередь и её предел попыток. Короткий файл; докблоки несущие.platform/docs/STACK_DECISIONS.md— ловушки стенда и ЕДИНСТВЕННЫЙ носитель числа гейтов батареи (787 строк). ⛔ Читать ДО первой команды: половина ловушек ниже взята оттуда, и там их больше.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 пакетов.
Самопроверкой он не является. Сверх него — адверсариальный проход по своей готовой работе; субагентов поднимать
разрешаю явно. Направление даю я — четыре места, где этот пак мягок:
- Кап попыток (§4.1) — посадка, снимающая предел, обязана краснеть ИМЕННО пином предела, а не соседним тестом.
- Пометка нашего грейса (§4.2) — проверь, что она доезжает до человека, а не только до базы: «поле заполнено» и «оператор это видит» — разные утверждения.
- Слово покупателю (§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нужной версии установлен, но НЕ наPATH—export 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 (пять рядов реестра про холды — один предмет) в пак НЕ входит, но соседствует: если твоя правка его задевает, назови это, а не чини молча.