441 lines
42 KiB
Markdown
441 lines
42 KiB
Markdown
# Промт сессии: ПЛАТФОРМА, пак P13 — «шов возврата оплаченного прогона»
|
||
|
||
> **Роль:** платформа. **Зона записи:** `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-374` —
|
||
`platform/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-check` → `Error 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`.
|
||
- `:1397` — **funded 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`) возвращает ошибку, **не напечатав ключ**.
|
||
Неоднозначный обрыв на коммите → оператор повторяет без `--key` → `newKey()` чеканит новый →
|
||
**второе начисление**. Комментарий `: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**, из них `major` — **2**,
|
||
и следующая разведка снова доложит «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 `reopen` — **DSN нужен** (денежный путь `internal/runs`, 110 тестов тёмных без него);
|
||
- п.2 `write()` — юнит-посадки идут **без DSN**, но сквозной `TestCommandsAgainstALiveDatabase`
|
||
в `cmd/tmplatformctl/main_test.go` **DSN-гейчен** (без DSN в `./cmd/...` скипается 6 тестов);
|
||
- п.3 `ingest/tail.go` — **DSN не нужен**, пакет гоняется целиком (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). Здесь важен один его край: заказанность
|
||
решает ЗАКАЗ, а не ты, и **этот промт заказывает смену поведения в пунктах 1–4** — протухший под неё
|
||
тест правится как обслуживание, но правка ОБЪЯВЛЯЕТСЯ в отчёте (что изменилось, какой тест это
|
||
описывал, куда уехала гарантия).
|
||
|
||
## Отчёт
|
||
|
||
**Записка-план ДО работы** (эхо-протокол: скоуп · инварианты · что НЕ делаю, ≤10 строк) — в
|
||
`platform/docs/platform-PROGRESS.md`, и тем же текстом первым действием по каналу (см. ниже).
|
||
Итог — туда же. ⛔ В `docs/PROGRESS.md` платформа НЕ пишет (канон, таблица ролей).
|
||
|
||
**Каждое число и каждая категорика — с КОМАНДОЙ ПОЛУЧЕНИЯ, которая это число реально печатает.**
|
||
Приёмка пере-ранит. ⚠ Команда, печатающая «ok» вместо числа, командой
|
||
получения не является.
|
||
|
||
Обязательная секция **«Что НЕ удалось и что НЕ проверено»**: исходов ТРИ, третий законен, и молчание
|
||
об упёршемся пункте дороже самого упора.
|
||
|
||
## Канал вопросов и право отказаться
|
||
|
||
**Конфликт промта с кодом — пинг через владельца, не интерпретация.** Если число выше не сошлось,
|
||
если факт о коде не воспроизводится, если два места этого промта говорят разное — это находка, неси
|
||
её, а не подгоняй работу под текст.
|
||
|
||
**Право сказать «этого делать не надо» с аргументом у тебя есть** — включая пункты состава и границы
|
||
выше. Сессия, приносящая развилку вместо тихой девиации, делает правильно.
|
||
|
||
## Прямой канал
|
||
|
||
Механизм целиком — `CLAUDE.md`, «Связь между сессиями»: впиши свой блок в `/tmp/textmachine-channel`,
|
||
сверься с `ListAgents` перед отправкой, канала нет — это нормально и опрашивать сессии подряд не надо.
|
||
Что добавляет этот пак: **эхо-записку отправь ПЕРВЫМ действием по адресу оркестратора — это и есть
|
||
проверка канала**; и знай, что в файле прямо сейчас лежат блоки закрытых смен, один с пометкой
|
||
«⚠ СМЕНА СДАНА 31.08 … по адресу не писать».
|