textmachine/docs/archive/prompts/PLATFORM_P13_SESSION_PROMPT_2026-09-02.md

42 KiB
Raw Blame History

Промт сессии: ПЛАТФОРМА, пак P13 — «шов возврата оплаченного прогона»

⚠⚠ АРХИВ. ПРОМТ ОТРАБОТАН — ИНСТРУКЦИИ ОТСЮДА НЕ ИСПОЛНЯЮТСЯ. Выдан 02.09, сдан 03.09, принят с дофиксом (D39.188). Исход: шесть пунктов состава исполнены, заказ пункта 3 оказался НЕДОСТАТОЧЕН и зона это доказала (PD-438, major), дофикс по шести пунктам отработан, приёмка второго круга оставила одну правку записки о хосте. ⚠ Часть якорей этого файла МЕРТВА по построению: пак переписал цели (строка рантбука, Quarantine). Живые носители: D39.188 · docs/PLATFORM_P13_DOFIX_2026-09-03.md · platform/docs/platform-PROGRESS.md.

Роль: платформа. Зона записи: platform/. Чужие зоны читаешь, не правишь. Не коммитишь и вообще не трогаешь индекс и историю — почему именно, и полный список запретов: CLAUDE.md, «Git-координация мультисессий». Обязателен к ссылке: platform/docs/ENGINEERING_STANDARDS.md; отступление — пинг, не самовольство. Направление зоны — platform/docs/PLATFORM_DIRECTION.md.

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

Оплаченный прогон сегодня может подорожать, зависнуть или замереть на экране — без вины пользователя и без единого сигнала оператору. Четыре независимых пути ведут к одному и тому же: человек заплатил, а получил либо не то, либо ничего, и вывести его из этого состояния нечем, кроме psql. После пака оплаченный прогон возвращается на те деньги, за которые был продан, оператор перестаёт начислять кредиты дважды, а чужая строка в общем журнале книги перестаёт замораживать проекцию чужого прогона.

Карта чтения — пять позиций, дальше только по их ссылкам

  1. platform/README.md — устройство зоны · platform/docs/ENGINEERING_STANDARDS.md (обязателен).
  2. platform/docs/platform-PROGRESS.md — зонный журнал: текущее состояние и пинги оркестратора (там же ПИНГ №21 про гейт батареи и ПИНГ №22 про заниженную ставку и снятый сайдкар).
  3. Ряды регистра, названные ниже поимённо: PD-168 · PD-89 · PD-426 · PD-214 · PD-374platform/docs/DEFECT_REGISTER.md, греп по номеру.
  4. docs/architecture/15-money-path.md §3 — деньги платформы по шагам (холд, расплата, run abandon).
  5. docs/architecture/14-api-contract/ — канон контракта; зеркало фронта НЕ норматив.

⚠ Ратифицированное вложено в тело этого промта; код-факты добываешь САМ — указатели ниже отправные точки, код первичен. Числа ниже даны С КОМАНДОЙ ПОЛУЧЕНИЯ: пере-ранишь — сойдётся; не сошлось — это находка, неси её пингом, а не подгоняй под текст.

⚠ ПРЕДУСЛОВИЕ, НЕ ПУНКТ РАБОТЫ — прочти прежде всего

Условий хоста у батареи ЧЕТЫРЕ, и все четыре на этом хосте выполнимы. Выставь их до первой команды — иначе половина зоны молча не гоняется:

условие как выставить что откроет
TM_PLATFORM_TEST_DSN стенд уже поднят (/tmp/.s.PGSQL.55433); DSN — platform/docs/STACK_DECISIONS.md, раздел стенда разработчика, греп port=55433 денежный путь runs, pgstore, books, cmd/tmplatformctl
TM_PLATFORM_TEST_ENGINE_BIN + TM_PLATFORM_TEST_BOOK_TEMPLATE движковый бинарь и шаблон деплоя 3 теста internal/runner + internal/books
достижимый пользовательский systemd на этом хосте /run/user/1000 жив, ставить нечего systemdOrSkip, internal/runner/systemd_test.go
export PATH=$HOME/go/bin:$PATH ⚠ без него make check НЕ ЗАПУСКАЕТСЯ ВОВСЕ: tools-check падает на sqlc («got: none»), хотя нужный v1.31.1 лежит в ~/go/bin весь make check

Проверка последнего одной командой: cd platform && make tools-checkError 1 на sqlc; cd platform && PATH=$HOME/go/bin:$PATH make tools-check → выход 0. Сети нет — ничего не ставь, бинарь уже на диске, вопрос только в PATH.

Замер 02.09 на этом хосте, без DSN (числа — с командами; голый go test ./... печатает ровно 18 строк «ok» и НИ ОДНОГО из этих чисел, поэтому команда счёта — verbose):

cd platform && go test ./... -count=1 -v > /tmp/battery_v.txt 2>&1; echo "exit=$?"
grep -cE '^=== RUN   [^/]+$' /tmp/battery_v.txt   # 661  верхнеуровневых тестов
grep -c '^--- PASS'  /tmp/battery_v.txt           # 342
grep -c '^--- SKIP'  /tmp/battery_v.txt           # 319  верхнеуровневых скипов
grep -c '^--- FAIL'  /tmp/battery_v.txt           # 0
grep -c '^ok '       /tmp/battery_v.txt           # 18   пакетов
grep -c -- '--- SKIP' /tmp/battery_v.txt          # 330  — ТО ЖЕ САМОЕ вместе с подтестами

Снимаю кажущееся противоречие заранее: ПИНГ №21 в зонном журнале пишет про пустое окружение 330 скипов, здесь стоит 319. Это ОДИН И ТОТ ЖЕ прогон: make check грепает -- '--- SKIP', то есть считает подтесты вместе с верхнеуровневыми. Оба числа верны, расходиться тут не о чем.

Среди пропущенных — 110 тестов денежного пути реконсилятора (верхнеуровневых; вхождений сообщения вместе с подтестами — 112):

cd platform
go test ./internal/runs/ -count=1 -v 2>&1 \
  | awk '/reconciler.s money path/{p=1;next} p&&/^--- SKIP/{n++} {p=0} END{print n+0}'   # 110
go test ./internal/runs/ -count=1 -v 2>&1 | grep -c "reconciler's money path"            # 112

make check про скипы ЧЕСТЕН — печатает строку «did NOT run …» и имена скипнутых тестов. Молчит именно голый go test ./....

Цена, которую ты обязан не повторить: в регистре 21 ряд заведён с формулировкой «мутация пережила ПОЛНУЮ батарею», и восемь из них открытыPD-433 · PD-375 · PD-382 · PD-383 · PD-386 · PD-388 · PD-389, плюс PD-423 (там фраза в смысле «пин ЗЕЛЁН в полной батарее со скипами 0»). Ни один не называет DSN, то есть «полная батарея» этих вердиктов могла быть той самой, где тёмно 319 тестов.

cd platform && awk -F'|' '/^\| PD-/{l=tolower($0);
  if(l~/полную батарею|полной батарее|всю батарею/){st=$(NF-2);gsub(/^ +| +$/,"",st);
  if(st=="open")o++;else f++}}END{print "OPEN="o,"FIXED="f,"TOTAL="o+f}' docs/DEFECT_REGISTER.md
# OPEN=8 FIXED=13 TOTAL=21

Каждая посадка мутации в твоём отчёте обязана нести строку «гонялось при DSN = да». Иначе пак проверит себя ровно тем способом, каким заводились ряды, которые он чинит.

Состав пака — четыре правки, гейт и одна строка рантбука

1. ГЛАВНОЕ — денежный бюджет прогона ЧИТАТЬ, а не пере-выводить из ставки

internal/runs/reconcile.go, функция reopen (:1350). Ставка читается там ДВАЖДЫ, и оба места денежные:

cd platform && grep -n 'Pricing' internal/runs/reconcile.go
# 1363:  budget := s.Pricing.Ceiling(l.CeilingChapters)
# 1397:  consent = s.Pricing.Ceiling(l.CeilingChapters)
  • :1363бюджет перезапуска: remaining = budget - spent, отсюда Ceiling в RestartInput.
  • :1397funded consent на пере-оплату при book.BankMoved, уезжает в RestartInput.AcceptRebill. Стоит в ветке if from == fromAFinishedRun, то есть только на пользовательском Resume — ровно на том пути, который и опасен. Комментарий :1384-1386 подтверждает денежный смысл: «funded consent, the same figure Start grants».

Вызывающих у reopen двое: свиповый restart (:1224, вызов :1244, fromALiveRun) и контрактный Resume (:1481, вызов :1548, fromAFinishedRun). Смена TM_PLATFORM_USD_PER_CHAPTER (internal/config/config.go:485=TM_PLATFORM_USD_PER_CHAPTER) между покупкой и продолжением молча пере-оценивает уже оплаченный прогон в обе стороны; при удвоении ставки на тонком счёте он становится невозобновляемым. Носитель — PD-168 (стоит minor, вес занижен; замер ряда: холд 5.500000 вместо 2.500000).

Задача (делай РОВНО так, отступление = пинг):Первая половина УЖЕ построена, строить нечего. PD-168 дословно: «Исходная сумма восстановима без пересчёта — она лежит в холде первой попытки (reservations.ceiling_micro_usd / run_attempts.ceiling_micro_usd)». Обе колонки в схеме есть:

cd platform && grep -n 'ceiling_micro_usd' internal/pgstore/migrations/*.sql
# ЧЕТЫРЕ хита, из них колонки — две:
#   00007_credits.sql:72 — reservations.ceiling_micro_usd
#   00009_runner.sql:36  — run_attempts.ceiling_micro_usd (:70 — её же `drop` в Down-миграции)
#   00011_ceiling_argument.sql:5 — КОММЕНТАРИЙ-предупреждение, не колонка

Значит задача не «завести хранение», а «заставить reopen ЧИТАТЬ уже хранимое вместо s.Pricing.Ceiling(...)». Схему хранилища платформы при этом не трогаем — миграция не нужна и не заказана. Понадобилась вопреки этому — стоп и пинг, а не «сделаю аккуратно».

Лечение обязано закрыть ОБА места, :1363 и :1397. Закрыт один — это половина, и в отчёте так и объявляется: путь остаётся под «согласием, которого пользователь не давал». ⚠ Не спутай с третьей колонкой: run_attempts.ceiling_arg_micro_usd (00011_ceiling_argument.sql) — это КНИЖНЫЙ аргумент --ceiling-usd для движка, а не бюджет попытки; миграция прямо предупреждает «It is NOT ceiling_micro_usd».

Решаешь сам и аргументируешь: какую именно из двух колонок читать, как быть, если холда первой попытки нет (старые прогоны), и нужен ли отдельный аккуратный фолбэк.

2. PD-89 — печатать ключ идемпотентности НА ПУТИ ОШИБКИ

cmd/tmplatformctl/main.go, функция write() (:184; ⚠ якорь ряда :143-150 дрейфанул и указывает теперь на вызывающего grant). id = newKey() чеканится на :189, applied, err := op(id, now) — на :191, и if err != nil { return err } (:192-194) возвращает ошибку, не напечатав ключ. Неоднозначный обрыв на коммите → оператор повторяет без --keynewKey() чеканит новый → второе начисление. Комментарий :195-198 описывает ровно эту ловушку, но только для пути чтения баланса («a failed BALANCE READ would have bought a double credit»).

Задача (делай РОВНО так): на пути ошибки ключ обязан быть виден оператору. Посадка мутации обязательна. Форма печати (куда, каким текстом, out или stderr) — решаешь сам и аргументируешь.

3. PD-426 + PD-214 — чужая строка не должна карантинить НАШУ проекцию

internal/ingest/tail.go, функция apply (:145). Четыре отказа стоят выше ветки, решающей принадлежность: :150 декод payload · :153 checkVersion · :156 пустой EngineRunID · :162 Seq != 1. Ошибка любого из них терминальна для проекции: quarantines() (internal/runs/reconcile.go:770-781=func quarantines(err error) bool) пропускает только context.Canceled/DeadlineExceeded и pgstore.IsTransient, всё прочее едет в Store.Quarantine. Снятия карантина в дереве НЕТ (grep -rniE 'unquarantine|clearquarantine|quarantine_reason *= *null|dequarantine' по .go/.sql — пусто при живом положительном контроле internal/pgstore/runs.go:1470=func (s *Store) Quarantine), операторской ручки нет, а гейдж tm_platform_quarantined_attempts (internal/metrics/metrics.go:80=Name: "quarantined_attempts") это видит и молчит о том, что делать.

ПОСЫЛКА, КОТОРУЮ НАДО ЗНАТЬ ТОЧНО — иначе живая проба будет искать несуществующее. Полностью ЗАКОННЫЙ чужой hello сегодняшней сборки (мажор потока тот же, engine_run_id непустой, seq == 1) НЕ карантинит — он уходит в default и молча пропускается, и это защищено ЗЕЛЁНЫМ пином:

cd platform && go test ./internal/ingest/ -count=1 -run TestAnotherAttemptsStreamInTheSameJournalIsSkipped -v
# --- PASS  (тело пина — tail_test.go:185-187: hello(1,"eng-1") … hello(1,"eng-2"), ошибки нет)

Карантинит чужая ИЛИ БИТАЯ hello-строка, и условий ровно четыре — они же перечислены в PD-214 («ручной tmctl оператора в каталоге книги, старый мажор, баг чужой сборки»): другой МАЖОР потока (checkVersion сравнивает ТОЛЬКО мажор, internal/ingest/events.go:196-208=func checkVersion(got string) error; у платформы StreamVersion = "1.1", у движка "1.2"обе стороны на мажоре 1, поэтому сегодня проходит) · пустой engine_run_id · seq != 1 · нераспарсиваемый payload. ⚠ Формулировка PD-426 («попасть можно по чужому ЗАКОННОМУ handshake'у») — переупрощение, ряд описывает класс шире, чем воспроизводится; читай PD-214, он точнее.

Задача — СВОЙСТВО, а не перестановка (делай РОВНО так, отступление = пинг):

строка, которая не наша, не должна карантинить нашу проекцию.

Что это значит по букве, чтобы ты не внёс дефект буквальным исполнением:

  • Отказ по ДЕКОДУ (:150) остаётся ВЫШЕ и не двигается. EngineRunID живёт ВНУТРИ payload Hello (internal/ingest/events.go:82-92=EngineRunID string), в Envelope (:71-79) его нет — значит без успешного декода принадлежность просто НЕИЗВЕСТНА. Переставляемы только три отказа, не четыре.
  • Понижаются только checkVersion, пустой EngineRunID и Seq != 1и ТОЛЬКО в ветке, где want уже известен (want != ""): там принадлежность решается сравнением h.EngineRunID == want, и чужая строка обязана уходить в «не наше, пропустить» ДО валидации.
  • При want == "" валидация обязана остаться полной. Ветка case want == "": (:166) УСЫНОВЛЯЕТ поток — зовёт sink.Begin и ставит want, mine = h.EngineRunID, true. Снять валидацию там значит принять поток ЧУЖОГО МАЖОРА как свой. Это ровно тот дефект, который пак чинит, только с другой стороны.
  • Зелёный пин TestAnotherAttemptsStreamInTheSameJournalIsSkipped обязан остаться зелёным: он описывает уже правильную половину поведения.
  • Курсорная семантика (held против pos, движение курсора на hello) — не предмет правки; тронул — объясни зачем.

Вторая половина ряда — ручка снятия карантина. Что она есть — «делай РОВНО так»; форма (подкоманда tmplatformctl, аргументы, что именно сбрасывается, кто имеет право) — решаешь сам и аргументируешь.

Посадка мутации обязательна на обе половины.

4. МЕЛКОЕ, закрывающее урок пака: гейт батареи печатает ВСЕ условия хоста, а не одно

Дефектов тут ДВА, и они в РАЗНЫХ целях. Заказывается (а), не (б).

(а) — заказ. Цель check печатает над списком скипов жёстко зашитый литерал:

cd platform && sed -n '91,100p' Makefile
# if grep -q -- '--- SKIP' .check.log; then \
#   echo "--- did NOT run (set TM_PLATFORM_TEST_DSN for the schema tests) ---"; \

Строка ни от чего не зависит: она печатается и когда DSN ВЫСТАВЛЕН, а скипы дал совсем другое условие, — то есть отправляет читателя к ручке, которая уже включена. Демонстрация: с выставленным DSN go test ./internal/books/ ./internal/runner/ -count=1 -v даёт 3 скипа, причина у них TM_PLATFORM_TEST_ENGINE_BIN, а рецепт всё равно советует DSN. Это PD-374 («отправляя читателя к ручке, которая тут ни при чём») и ПИНГ оркестратора №21 в зонном журнале с таблицей пусто 330 / DSN 3 / +ENGINE_BIN 0.

(б) — НЕ заказ, не путай. Поведение «останавливается на первом недостающем» принадлежит цели tools-check (Makefile:60-66): там каждая проверка делает exit 1, поэтому при живом golangci-lint и отсутствующем sqlc печатается только про sqlc. Это отдельная история; тронешь — объяви отдельно.

Задача (делай РОВНО так): цель check печатает ВСЕ условия хоста и что каждое включает. Решаешь сам и аргументируешь: форма. Приор ПИНГа №21 — выводить перечень из тех же переменных, которые читают хелперы тестов, чтобы список не разъехался с реальностью третий раз; довод против «просто дописать вторую строку» — он чинит сегодняшний счёт, а не механизм. Условий сегодня четыре (таблица в предусловии), и systemdOrSkip с PATH-до-sqlc в подсказке сегодня не названы вовсе.

5. ГЕЙТ ПРОТИВ КЛАССА, а не против ряда

Колонка серьёзности регистра разъехалась с сутью: открытых рядов 94, из них major2, и следующая разведка снова доложит «major всего два», хотя тревожных рядов на порядок больше.

cd platform && awk -F'|' '/^\| PD-/{st=$(NF-2);gsub(/^ +| +$/,"",st);w=$4;gsub(/^ +| +$/,"",w);
  if(st=="open"){t++; if(w~/major/)m++}}END{print "open="t,"major="m}' docs/DEFECT_REGISTER.md
# open=94 major=2

Предикат «слова тревоги» задаю дословно, потому что от его прочтения число прыгает вдвое. Список маркеров (стеммы): деньг · холд · молча · блокир · невидим · 500 · паник.

cd platform && for M in any distinct; do printf "%s: " "$M"; awk -F'|' -v mode="$M" '
/^\| PD-/{st=$(NF-2);gsub(/^ +| +$/,"",st);w=$4;gsub(/^ +| +$/,"",w);
 if(st=="open" && w !~ /major/){l=tolower($0); n=split("деньг холд молча блокир невидим 500 паник",m," "); c=0;
  for(i=1;i<=n;i++){ if(mode=="distinct"){ if(l~m[i]) c++ } else { t2=l; c+=gsub(m[i],"",t2) } }
  if(c>=2) k++}}END{print k+0}' docs/DEFECT_REGISTER.md; done
# any:      21   (≥2 ВХОЖДЕНИЯ любого маркера)
# distinct: 13   (≥2 РАЗНЫХ маркера)

То есть 21 — это счёт по «≥2 вхождения», а не по «два разных слова»; по «двум разным» их 13 (а по буквальным словоформам — 11 и 18 соответственно). Обе величины настоящие, но гейт обязан знать, какую он считает.

Задача (делай РОВНО так): гейт зоны печатает открытые не-major ряды, несущие маркеры тревоги. Решаешь сам и аргументируешь: место, предикат и порог — и объявляешь выбранный предикат в отчёте командой, дающей его число. Приор по месту: platform/internal/gates/ — там уже есть прецедент чтения канона выше модуля (contract_test.go, константа zoneRoot + "/../docs/...").

docs/scripts/counts.py НЕ ТРОГАТЬ — это единственное, что сегодня читает platform/docs/DEFECT_REGISTER.md (grep -rn 'DEFECT_REGISTER' --include=*.go --include=Makefile --include=*.py --include=*.sh platform/ docs/scripts/ → один хит, counts.py:51), и его шапка прямо запрещает правку чужим сессиям: «скрипт — зона ОРКЕСТРАТОРА». Красный --check/--lint не обходить и не «чинить» подгонкой — это тот же запрет, что подгонка тестов; непонятно или кажется неправым → пинг.

6. МЕЛКОЕ, найденное при сборке промта: рантбук ТВОЕЙ зоны советует деструктивный порядок деплоя

platform/deploy/README.md:213=сначала платформа, потом движок — «Правило то же, что у схемы хранилища: сначала платформа, потом движок». По коду безопасного порядка у формы манифеста НЕТ: гейт интейка — строгое равенство ОДНОЙ константе, окна двух версий нет (обе ссылки — в разделе ПОРЯДОК ДЕПЛОЯ ниже). Платформа, выкаченная первой со знанием новой формы, отправит в parser_unavailable КАЖДУЮ книгу ещё не обновлённого движка — ровно тот симптом, который абзац рантбука строкой выше описывает как беду ОБРАТНОГО порядка.

Делай РОВНО так: ряд в docs/DEFECT_REGISTER.md (класс doc) + правка строки рантбука. Решаешь сам и аргументируешь: формулировка — назвать бамп формы стоп-миром или описать временное окно двух версий как условие, при котором безопасный порядок вообще появляется. ⚠ Увидишь в коде окно версий, которого я не нашёл, — это находка, а не твоя ошибка: скажи и не чини.

Границы — что НАМЕРЕННО не берём

  • PD-412/PD-413 (LATERAL в полосном агрегате). Находки настоящие и замеренные (1.359 мс → 0.307 мс на книге 2283 глав — числа принадлежат PD-412), но это переписывание запроса в горячем read-модельном пути, а арифметика полосы в этом паке не трогается. Контенция, а не корректность денег.
  • PD-162 (клин на удалённом каталоге). Настоящий и денежный, но его АВТОМАТИЧЕСКАЯ терминальность — это эскроу (platform/BACKLOG.md П-18/П-20), а строить половину эскроу рядом с проектируемым целым зона уже отказывалась: пере-диспозиция P6 по PD-154 (греп половину эскроу в platform/docs/ — единственный хит, DEFECT_REGISTER.md:101). ⚠ Ряд называет и ВТОРОЕ, дешёвое лечение — «либо проверкой каталога при допуске». Этим паком оно тоже НЕ берётся: пак закрывает денежный шов возврата, а валидация каталога на допуске — это правка входной двери Start, отдельный предмет с собственной ценой на ложных отказах. Операторская ручка на этот класс уже построена (run abandon [--release-hold], P8-FIX; строка П-20 фиксирует, что «пока эскроу нет, ручка остаётся ручной»). ⚠ Считаешь границу неверной — скажи аргументом, право такое у тебя есть (см. «Канал вопросов»).
  • Дверь выдачи — отдельный пак: createExport/getExport в каноне 0.9.0 есть, платформой не смонтированы (grep -rn 'createExport\|getExport' platform/internal/ --include=*.go — пусто), но дверь требует минора канона и сводит на себя все точки столкновения.

⚠ ПОРЯДОК ДЕПЛОЯ — почему пак безопасен параллельно бэкенду

Зоны независимы по коду (разные модули, перекрёстных импортов нет в обе стороны, go.work в корне отсутствует), но НЕ по деплою, и сцеплений ДВА.

  • Форма манифеста БЕЗОПАСНОГО ПОРЯДКА НЕ ИМЕЕТ. Гейт интейка — строгое равенство ОДНОЙ константе (internal/ingest/manifest.go:112=if m.Version != KnownManifestVersion; сама константа — :88=KnownManifestVersion = "tm-manifest-v2"). Окна из двух версий нет, поэтому «платформа первой» ровно так же смертельно, как «движок первым»: односторонняя выкатка отправляет КАЖДУЮ книгу в parser_unavailable, который жжёт бюджет попыток. Бамп формы = стоп-мир либо специально построенное временное окно двух версий.
  • Схема хранилища ДВИЖКА ⇒ «движок первым → tmctl migrate по каждой книге → и только потом платформа снимает свой обход» (D39.158 п.6). Голова схемы сегодня 16 (backend/internal/store/migrate.go:485=func SchemaHead() int, len(migrations)) — это персистентная память ДВИЖКА, а не Postgres платформы.

Поэтому пак НЕ меняет: форму манифеста · схему хранилища ДВИЖКА · версию контракта. Понадобилось — стоп и пинг, не «сделаю аккуратно». Платформенная миграция этим абзацем НЕ запрещена: по пункту 1 она не нужна (сумма уже персистится) и потому не заказана; понадобилась — тоже пинг, но по другому основанию.

Свобода и её разметка

«Делай РОВНО так» (отступление = пинг):

  • предусловие хоста (четыре условия) · строка «DSN = да» у каждой посадки;
  • ФАКТ каждой из шести правок — что именно закрывается: (1) reopen читает хранимую сумму в ОБОИХ местах, :1363 и :1397, схему платформы не трогая; (2) ключ печатается на пути ошибки; (3) свойство «чужая строка не карантинит нашу проекцию» с тремя оговорками пункта 3 (декод остаётся выше · понижение только при известном want · при want == "" валидация полная); (4) цель check печатает ВСЕ условия; (5) гейт против класса живёт в зоне платформы; (6) ряд регистра и правка строки рантбука;
  • обязательные посадки у пунктов 2 и 3;
  • границы выше · три запрета из порядка деплоя · запрет трогать docs/scripts/counts.py.

«Решаешь сам и аргументируешь» — ФОРМА и только она: какую колонку холда читать и как быть без холда · форма печати ключа · форма ручки снятия карантина · форма печати условий в Makefile · место, предикат и порог гейта пункта 5 · формулировка правки рантбука в пункте 6 · остальной состав посадок · порядок работ внутри пака.

⚠ Катч-олла «всё остальное — сам» больше нет: если чего-то нет ни в одном списке — это пинг, а не твоё усмотрение.

Мандат самопроверки — исполнением, не перечитыванием

Батарея зелёная — это НЕ отчёт. Обязательны все три:

  1. Живая проба каждого пункта состава. Поимённо, с пометкой про DSN:
    • п.1 reopenDSN нужен (денежный путь internal/runs, 110 тестов тёмных без него);
    • п.2 write() — юнит-посадки идут без DSN, но сквозной TestCommandsAgainstALiveDatabase в cmd/tmplatformctl/main_test.go DSN-гейчен (без DSN в ./cmd/... скипается 6 тестов);
    • п.3 ingest/tail.goDSN не нужен, пакет гоняется целиком (0 скипов);
    • п.4 Makefile — DSN не нужен, но проба обязана гоняться при РАЗНЫХ окружениях, иначе она не видит того самого дефекта;
    • п.5 гейт — DSN не нужен;
    • п.6 рантбук — кода не касается, «проба» это ряд регистра и counts.py --check зелёным.
  2. Собственные адверсариальные посадки ВНЕ списка выше.
  3. Отдельный проход по СВОЕЙ ГОТОВОЙ работе свежим контекстом. ⚠ Класс, купленный прошлой сменой: веер по исходным уликам находит втрое меньше, чем веер, читающий готовый результат.

Субагенты разрешены явно. Модель задавай ИМЕНЕМ и знай, сколько их у тебя работает; обычно хватает одного-двух, но веер под предмет выбираешь ты.

Предметные оси ревью — под характер ЭТОЙ работы

(1) ШОВ — инвентарь каналов другой стороны чтением ЧУЖОГО кода, не по памяти (D39.132). Пункт 3 живёт целиком на шве с движком, и спроектировать порядок проверок по своей половине шва нельзя. Открой и прочитай: backend/internal/runevents/ (что движок реально пишет в общий events.jsonl, словарь типов, StreamVersion, комментарий Envelope — «a resumed run is a NEW process that appends a second hello to the same file and numbers from 1 again») и backend/internal/pipeline/events.go (греп hello at seq 16 — отказ Seq != 1 наблюдался НА РЕАЛЬНОМ БИНАРЕ, 33 строки, 15 байт-в-байт дублей). Вопросы, на которые обязан быть ответ ЧТЕНИЕМ: с какого seq нумерует второй процесс · какие мажоры потока сегодня возможны в одном журнале · что происходит при дозаписи. Читаешь — не правишь: backend/ чужая зона.

(2) ДЕНЬГИ — пере-вывод сумм из холда первой попытки. Не «код компилируется», а «на живой БД при изменённой TM_PLATFORM_USD_PER_CHAPTER резервируется та сумма, на которую человек согласился» — и по пути Resume, и по пути свипа, и в AcceptRebill.

⚠ ПРОВЕРЯЕМЫЙ АРТЕФАКТ находок — обязателен

Тест на вырожденность, применяй к каждой строке своего отчёта: если её букву удовлетворяет НИЧТО — строка дефектна. «Батарея зелёная», «перечитал сам», «субагент ответил: находок нет» — это не отчёт, это его отсутствие. В журнал идут ТРИ таблицы:

  • Посадки: строка на посадку — что мутировано (file:line + сама мутация) · какой тест покраснел (ИМЕНЕМ) · DSN = да/нет. ⚠ Если по посадке НЕ покраснел ни один тест — так и пиши: это находка (дыра в покрытии), а не провал отчёта, и она сама становится строкой регистра.
  • Живые пробы: путь · команда · наблюдённый выход цитатой, не пересказом · вердикт.
  • Веер: на каждого агента — модель ИМЕНЕМ · что он читал · находки с file:line. «Находок нет» принимается ТОЛЬКО со списком того, что проверялось; голое «находок нет» означает, что веер не отработал — задание переформулируй и гоняй заново.

⚠ Запрет подгонки тестов и гейтов — CLAUDE.md (D39.183). Здесь важен один его край: заказанность решает ЗАКАЗ, а не ты, и этот промт заказывает смену поведения в пунктах 14 — протухший под неё тест правится как обслуживание, но правка ОБЪЯВЛЯЕТСЯ в отчёте (что изменилось, какой тест это описывал, куда уехала гарантия).

Отчёт

Записка-план ДО работы (эхо-протокол: скоуп · инварианты · что НЕ делаю, ≤10 строк) — в platform/docs/platform-PROGRESS.md, и тем же текстом первым действием по каналу (см. ниже). Итог — туда же. В docs/PROGRESS.md платформа НЕ пишет (канон, таблица ролей).

Каждое число и каждая категорика — с КОМАНДОЙ ПОЛУЧЕНИЯ, которая это число реально печатает. Приёмка пере-ранит. ⚠ Команда, печатающая «ok» вместо числа, командой получения не является.

Обязательная секция «Что НЕ удалось и что НЕ проверено»: исходов ТРИ, третий законен, и молчание об упёршемся пункте дороже самого упора.

Канал вопросов и право отказаться

Конфликт промта с кодом — пинг через владельца, не интерпретация. Если число выше не сошлось, если факт о коде не воспроизводится, если два места этого промта говорят разное — это находка, неси её, а не подгоняй работу под текст.

Право сказать «этого делать не надо» с аргументом у тебя есть — включая пункты состава и границы выше. Сессия, приносящая развилку вместо тихой девиации, делает правильно.

Прямой канал

Механизм целиком — CLAUDE.md, «Связь между сессиями»: впиши свой блок в /tmp/textmachine-channel, сверься с ListAgents перед отправкой, канала нет — это нормально и опрашивать сессии подряд не надо. Что добавляет этот пак: эхо-записку отправь ПЕРВЫМ действием по адресу оркестратора — это и есть проверка канала; и знай, что в файле прямо сейчас лежат блоки закрытых смен, один с пометкой «⚠ СМЕНА СДАНА 31.08 … по адресу не писать».