Compare commits

..

6 commits

4 changed files with 693 additions and 124 deletions

View file

@ -1,91 +1,326 @@
# Промт сессии: БЭКЕНД — «число согласия на пути второй покупки» # Промт сессии: БЭКЕНД — «число согласия на пути второй покупки»
> **Роль:** бэкенд. **Зона записи:** `backend/`. **Не коммитишь** — дерево готовишь и передаёшь на > **Роль:** бэкенд. **Зона записи:** `backend/`. Чужие зоны (`platform/`, `frontend/`, `eval/`)
> лендинг оркестратору. Чужие зоны читаешь, не правишь. Норматив стиля — `docs/architecture/12-go-style-notes.md`. > читаешь, не правишь; в `docs/` пишешь ТОЛЬКО в секцию «Бэкенд» журнала `docs/PROGRESS.md` — это
> единственное разрешённое каноном исключение. **Не коммитишь** и не трогаешь индекс и историю —
> полный список запретов и причина: `CLAUDE.md`, «Git-координация мультисессий».
> Норматив стиля — `docs/architecture/12-go-style-notes.md`.
## Какая проблема и что изменит твой результат ## Какая проблема и что изменит твой результат
Продукт продаёт главы **частями**. Покупатель берёт кусок, потом ещё кусок — и на этом пути число, Продукт продаёт главы **частями**. Покупатель берёт кусок, потом ещё кусок — и на этом пути число,
которым движок спрашивает согласия на оплату, **врёт в трёх местах**, а один юнит может быть оплачен которым движок спрашивает согласия на оплату, **врёт** (занижает при смене модели стадии и не
дважды. После пака покупатель, берущий книгу частями, видит число, которое не занижает при смене раскрывает, какая его часть — старые деньги), а один юнит может быть оплачен дважды. После пака
модели стадии, признаётся, какая его часть — старые деньги, и не платит второй раз за юнит, который покупатель, берущий книгу частями, видит число, которое не занижает молча, признаётся, какая его
уже двигал. часть — старые деньги, и не платит второй раз за юнит, который уже двигал; а три гарантии, которые
сегодня держатся только на честном слове, держатся тестом и записью в мутационном каталоге.
## Карта чтения — пять позиций, дальше только по их ссылкам ## Карта чтения — пять позиций, дальше только по их ссылкам
1. Шапка-таблица `docs/architecture/09-target-architecture.md` — что построено. 1. Шапка-таблица `docs/architecture/09-target-architecture.md` — что построено.
2. `backend/README.md` · `docs/architecture/03-implementation-notes.md` (через баннер). 2. `backend/README.md` · `docs/architecture/03-implementation-notes.md` (через баннер).
3. `docs/architecture/15-money-path.md` — денежный путь целиком, включая §2 «согласие на пере-оплату». 3. `docs/architecture/15-money-path.md` — денежный путь целиком, включая §2 «Деньги ДВИЖКА».
4. Строка **197** единого бэклога (`docs/PROGRESS.md`) — фикс-лист ФЧ-1…ФЧ-8, и строка **232**. 4. Строки **232** и **197** единого бэклога (`docs/PROGRESS.md`).
5. Нота **D39.150** — тело ратификации ФЧ-5 (греп `^## D39.150`). **Строка 197 — УКАЗАТЕЛЬ, а не фикс-лист:** её собственный текст отсылает тело списка в
`docs/archive/PROGRESS-2026-08-17.md`, запись приёмки №18 (проверь: `grep -n '^| 197 ' docs/PROGRESS.md`).
Определения ФЧ-1 и ФЧ-4, которые ты исполняешь, продублированы ниже В ТЕЛЕ ЭТОГО ПРОМТА — читать
архив ради них НЕ надо. Если всё же захочешь дословный оригинал, законный вход ровно один и он
назван здесь: `sed -n '68p' docs/archive/PROGRESS-2026-08-17.md` (`grep -n 'ФЧ-1 (HIGH' docs/archive/PROGRESS-2026-08-17.md``68`).
Инструкции из архива НЕ исполняются — он источник ФОРМУЛИРОВКИ, приказ несёт этот промт.
5. Журнал решений, два номера грепом: **D39.150** (`grep -n '^## D39.150' docs/architecture/05-decisions-log.md``535`)
и **D39.181** (`grep -n '^## D39.181' docs/architecture/05-decisions-log.md``1712`).
## Состав — четыре пункта, в этом порядке ## Состав — четыре пункта, в этом порядке
**1. ФЧ-5 — ось МОДЕЛИ в числе согласия. Ратифицирован 17.08 (D39.150 п.1) и НЕ построен.** ### 1. ФЧ-5 — ось МОДЕЛИ в числе согласия. Ратифицирован 17.08 (D39.150 п.1) и НЕ построен
Ратифицировано дословно: прайсить по модели, которую стадия резолвит **сейчас**; при незнании
округлять **вверх**; оговорка в тексте согласия. Нота помечает это обязательным **до первого **«Делай РОВНО так» — состав ратификации. D39.150 п.1 несёт ЧЕТЫРЕ обязательства, не три:**
пере-прогона со сменой модели стадии**. Сегодня первый же такой пере-прогон занижает число согласия
молча — ровно тот вред, ради которого чинилась строка 181. 1. **прайсить по модели, которую стадия резолвит СЕЙЧАС**, а не по той, что ответила тогда;
**Проверь исходное состояние САМ** и не бери с моих слов: оркестратор утверждал, что «шапка 2. **при невозможности знать — округлять ВВЕРХ**;
`rebill.go` заявляет обратное ратифицированному», и **это не подтвердилось** — в шапке речь про 3. **называть оговорку в тексте согласия**;
`$0 model`. Формулируй по коду, который увидишь. 4. **обещание ТОЧНОСТИ снимается с провода.** Дословно из ноты: «Следствие: обещание точности
снимается с провода — комментарий „`status` не может назвать сумму, отличную от `translate`“ …
при исполнении ФЧ-5 **обязан быть заменён** честной формулой „оценка, ошибается вверх, вот чем“».
Контракт механизма после пака — **не точность, а НАПРАВЛЕНИЕ ошибки: «никогда не занизить молча»**;
завышение безопасно (оператор согласился на большее, чем заплатит), занижение — тот вред, ради
которого чинилась строка 181.
⚠ **Пункт 4 — это правка ТЕКСТА в двух живых местах, и оба надо снять, иначе ФЧ-5 придётся
переоткрывать** (класс «текст обещает больше числа» — ровно тот, ради которого заведён фикс-лист):
- `backend/internal/pipeline/status.go:217`=`can never quote a different number` — «…so status can never quote a different number than the
one translate enforces» (`grep -n 'can never quote a different number' backend/internal/pipeline/status.go``217`);
- `backend/internal/pipeline/reprice.go:24`=`NOT projected (like the content axis` — шапка файла: «NOT projected (like the content axis in
rebill.go): **ROUTING**. A row is priced by the model that ANSWERED, not by the one its stage resolves
to now» (`grep -n 'NOT projected (like the content axis' backend/internal/pipeline/reprice.go``24`).
Это заявление становится ЛОЖНЫМ ровно в тот момент, когда ты исполнишь пункт 1.
Нота помечает ФЧ-5 обязательным **до первого пере-прогона со сменой модели стадии** (D39.150 п.2).
Замер вреда из приёмки: **`$0.003640` против честных `$0.036400`** при `HistoricalRows=0`
(`grep -on '\$0.003640 против честных \$0.036400' docs/archive/PROGRESS-2026-08-17.md``68:...`).
**Проверь исходное состояние САМ и не бери с моих слов.** Противоречащая ратификации шапка — в
`reprice.go` (см. выше); шапка `rebill.go` про это НЕ говорит, там речь про `$0`-строку. Формулируй
по коду, который увидишь.
**«Решаешь сам и аргументируешь»:** форма хранения и проводки «модели, которую стадия резолвит сейчас»
в числе согласия; конкретная формула округления вверх при незнании; точные слова оговорки и обеих
заменяемых формул.
### 2. Строка 232, ВТОРАЯ половина — слот объёмного гранта
Первая половина (читающий метод `store.AnnouncedOnceKeys()`) исполнена 31.08. Живой остаток по строке:
**юнит, прерванный МЕЖДУ волнами (signature stop, денежный потолок, Ctrl-C), тратит слот объёмного
гранта повторно** — покупатель платит за N глав и получает меньше. Замер строки — **4 купленных юнита
→ 2 главы** (`grep -on '4 купленных юнита → 2 главы' docs/PROGRESS.md``175:...`). Разбор —
`backend/docs/MONEY_HONESTY_REPORT.md` §4.5 (`grep -n '^### 4.5' backend/docs/MONEY_HONESTY_REPORT.md``206`).
**Остатков в коде названо ДВА, а не один — и это НЕ одно и то же.** Докстринг, приземлённый тем же
коммитом, перечисляет их под заголовком «⚠ TWO RESIDUALS, named rather than implied»
(`grep -n 'TWO RESIDUALS' backend/internal/pipeline/volume.go``523`):
- **(1) конвейер, получающий ПЕРВУЮ редакторскую стадию**, двигает отгружающую волну с draft на edit,
и уже прочитанные юниты репортятся свежими: «closing it needs a record of what the shipping wave WAS,
which nothing stores today». **ВНЕ СКОУПА этого пака** — закрытие требует носителя того, чем была
отгружающая волна, а пак схему хранилища не трогает (см. ПОРЯДОК ДЕПЛОЯ). ⚠ Строка 232 считает свою
первую половину ИСПОЛНЕННОЙ, а §4.5 отчёта называет только слот — то есть остаток (1) живёт СЕГОДНЯ
только в этом докстринге. **Назови его в отчёте, чтобы он не умер молча вместе с паком.**
- **(2) слот гранта** — В СКОУПЕ, это и есть твой пункт 2.
**Закрытие 232 объявляется ТОЛЬКО по остатку (2).** Написать «строка 232 закрыта» без этой оговорки —
ошибка отчёта.
**«Делай РОВНО так» — ратифицированный ответ на развилку, которую строка 232 требует назвать в
промте.** Ключ анонса несёт ВОЛНУ (`grep -n 'unitOnceKey is the identity' backend/internal/pipeline/events.go`
`396`), значит «юнит отгружен» — факт per-wave, и надо решить, ПО КАКОЙ волне судить. Ответ есть и
он не новый механизм: **отгрузкой считается волна, владеющая ОТГРУЖАЮЩЕЙ (последней) стадией —
`finalStageWave`** (`grep -n 'finalStageWave is the wave that owns the SHIPPING' backend/internal/pipeline/snapshot.go`
`243`; сама функция — `247`). На редакторском конвейере это `edit`, на черновом-только — `draft`.
**Не draft-волна и не «любая»:** иначе черновой-только конвейер получит ось, которая НИКОГДА ничего не
считает отгруженным. Отступление от этого — пинг, не решение.
**«Решаешь сам и аргументируешь»:** где именно вычитается уже потраченный слот, как это переживает
рестарт, и что происходит с частично двинутым юнитом.
**2. Строка 232, ВТОРАЯ половина.** Первая (читающий метод `store.AnnouncedOnceKeys()`) исполнена
31.08. Живой остаток: **юнит, прерванный МЕЖДУ волнами, тратит слот объёмного гранта повторно**
покупатель платит за N глав и получает меньше. Разбор — `backend/docs/MONEY_HONESTY_REPORT.md` §4.5.
**Оговорка веса, проверь её:** платформа сегодня продаёт денежным потолком (`--ceiling-usd`), а **Оговорка веса, проверь её:** платформа сегодня продаёт денежным потолком (`--ceiling-usd`), а
`--max-units` не передаёт вовсе — то есть вред пока не доезжает до пользователя через платформу. `--max-units` не передаёт вовсе — то есть вред пока не доезжает до пользователя ЧЕРЕЗ платформу. Это
Это меняет приоритет, но не отменяет дефект. меняет приоритет, но не отменяет дефект. Проводка `--max-units` в платформу — отдельный ГЕЙЧЕНЫЙ пункт
очереди, не твой (`grep -n 'Проводка .--max-units. в платформу остаётся ГЕЙЧЕНОЙ' docs/PROGRESS.md``19`).
**3. ФЧ-4 — раскрытие «часть суммы — старые деньги» не доезжает до `status --json`:** ### 3. ФЧ-4 — раскрытие «часть суммы — старые деньги»
`HistoricalRows` теряется на границе. Покупатель не может отличить новую трату от уже оплаченной.
**4. ФЧ-1 — посадки на гарантии, чьи мутации ПЕРЕЖИВАЮТ зелёную батарею.** **У ФЧ-4 ДВЕ половины. Закрыт = ОБЕ.** Дословно из фикс-листа: «раскрытие „часть суммы — старые
**Ограничь тремя**, поимённо: D39.149 п.5 называет три HIGH. Не превращай в бесконечный список — деньги“ не доезжает до `status`/`--json` (`HistoricalRows` теряется на границе `status.go`), **а также**
это последний пункт, и он берётся, если осталось время. второй операторский текст отказа (`--accept-rebill`) цитирует пере-прайсенное число без basis».
- **(а)** `HistoricalRows` теряется на границе отчёта: `rebillOutcome` возвращает
`(basis, units, usd, outputUnits)` и роняет его (`grep -n 'rebillOutcome' backend/internal/pipeline/status.go`),
в `StatusReport` соответствующего json-тега нет. Покупатель не может отличить новую трату от уже
оплаченной.
- **(б)** текст отказа по названному потолку `--accept-rebill=<usd>` печатает `proj.USD` **без**
`projectionBasis` и **без** `HistoricalRows`, тогда как оба других текста согласия их несут
(`grep -n 'caps consent at' backend/internal/pipeline/rebill.go``425`, ветка
`r.AcceptRebill.Capped`; для сравнения `grep -n 'func projectionBasis' backend/internal/pipeline/rebill.go``305`).
**Инвариант, которому подчиняется этот пункт — закон раскрытия D39.181 п.1** (ратифицирован словом
владельца 31.08, самая свежая норма зоны): четыре обязанности — **не молчать · не называть причину,
которую не сравнивал · ФИГУРА ЕДЕТ С БАЗИСОМ · КАНАЛ НАЗНАЧАЕТ ПОТРЕБИТЕЛЬ**. Ты добавляешь денежную
фигуру на опубликованную поверхность — обе выделенные обязанности про твой случай прямо.
**И не рассчитывай, что сборка тебя поправит:** спроектированный гейт Г1 (реестр раскрытия +
рефлективный конформанс-тест), который поймал бы новое поле без записи, **НЕ построен**
`grep -rn 'Consumers\b' backend/internal --include=*.go | wc -l``0`. Компилятор молчит.
**«Делай РОВНО так»:** обе половины, и фигура едет с базисом. **«Решаешь сам и аргументируешь»:** форма
раскрытия исторических строк на проводе и в операторском тексте (имя поля, форма basis, что печатать
при `HistoricalRows=0`).
### 4. ФЧ-1 — посадки на гарантии, чьи мутации ПЕРЕЖИВАЮТ зелёную батарею
**«Делай РОВНО так» — ограничь ТРЕМЯ, и вот они поимённо.** D39.149 п.5
(`grep -n '^## D39.149' docs/architecture/05-decisions-log.md``523`; сам пункт — `533`) говорит «Три HIGH фикс-листа —
не дефекты поведения, а НЕПРИБИТЫЕ гарантии: мутации переживали зелёную батарею», но поимённо называет
ровно ОДНУ; остальные две живут только в теле фикс-листа. Поэтому все три вписаны сюда, и брать надо
ЭТИ, а не изобретать свои:
1. **Снятие фильтра по книге в `CheckpointUsageForBook`**`WHERE j.book_id = ?` не валит ни store-,
ни денежные pipeline-тесты (`grep -n 'func (s \*Store) CheckpointUsageForBook' backend/internal/store/ledger.go`
`367`, сам фильтр — `371`). Единственная защита от того, чтобы деньги ЧУЖОЙ книги вошли в число
согласия. **Воспроизведено оркестратором ЛИЧНО.** Существующий тест
(`internal/store/ledger_test.go:10`=`func TestCheckpointUsageForBookReturnsInsertionOrder`) пинит только
порядок вставки.
2. **Ключ ячейки можно лишить `chunk_idx`** — обход начнёт съедать деньги соседнего чанка (во всех
фикстурах глава = один чанк, поэтому никто не краснеет).
3. **Ветка пометки «строка не пере-оценена» не ловится на продовом пути**`fromHistory` из
`(rp *repricer) usd` (`grep -n 'func (rp \*repricer) usd' backend/internal/pipeline/reprice.go``113`).
**Честность про источник:** гарантия (1) воспроизведена приёмкой лично, а **(2) и (3) приняты СО СЛОВ
мутационной линзы** — приёмка №18 записала это в свою секцию «что НЕ проверено». Значит первый твой шаг
по каждой из них — **воспроизвести мутацию и увидеть ЗЕЛЁНУЮ батарею**; если мутация на самом деле
краснеет, гарантия уже прибита, и это **находка**, а не повод молча заменить её своей.
⚠ **Механизм посадок в зоне УЖЕ ПОСТРОЕН — не пиши четвёртый одноразовый харнесс.**
`backend/cmd/tmmutate` + каталог `backend/cmd/tmmutate/mutations.json` (**73** записи:
`python3 -c "import json;print(len(json.load(open('backend/cmd/tmmutate/mutations.json'))))"``73`).
Его собственный докстринг объясняет, зачем он в репозитории: «Two packs and one acceptance in a row have
each written this harness from scratch in a scratch directory, and it died with the session every time».
Денежного пути в каталоге сегодня НЕТ вовсе — `grep -c 'reprice.go\|rebill.go\|volume.go\|ledger.go' backend/cmd/tmmutate/mutations.json`
`0`, то есть твои три гарантии туда и просятся.
⚠⚠ **ЛОВУШКА РЕПОЗИТОРИЯ, читай прежде чем запускать:** `-root` **без дефолта**, и копия дерева — **не
предложение**. Работать ТОЛЬКО на копии:
```
cp -a backend /tmp/mut/backend && cd /tmp/mut/backend
go run ./cmd/tmmutate -root . # весь каталог
go run ./cmd/tmmutate -root . -id A-lock-arbiter # выборочно
go run ./cmd/tmmutate -root . -logs /tmp/mutlogs # сохранить полный вывод тестов
```
Причина в докстринге прямая: однажды посадка просидела в ОБЩЕМ дереве сорок минут после таймаута, и
независимый ревьюер намерил по нему несуществующий дефект. Здесь параллельные сессии — это цена чужой
работы.
**«Делай РОВНО так»:** ровно три гарантии выше · каждая заводится **ЗАПИСЬЮ в `mutations.json`**, а не
только тестом · `tmmutate` гоняется на копии. **«Решаешь сам и аргументируешь»:** форма самих посадок
(какую строку и как ломать), уровень теста (store / pipeline / оба), формулировка `why` в каталоге.
⚠ Это ПОСЛЕДНИЙ пункт: он берётся, если после первых трёх осталось время. Не превращай в бесконечный
список — если времени хватило только на одну гарантию, это законный исход, назови его в отчёте.
## Границы — что НАМЕРЕННО не берём ## Границы — что НАМЕРЕННО не берём
- **Строка 160 «структура глав, Этап 0»** — хотя слово владельца 15.08 ставит её ближайшим паком и её - **Строка 160 «структура глав, Этап 0»** — хотя слово владельца 15.08 ставит её ближайшим паком и её
блокер закрыт 30.08. Причина техническая: строка требует бампа **формы манифеста**, а параллельно блокер снят 30.08 — строки 231 и 236 в таблице БОЛЬШЕ НЕТ
идёт платформенный пак, и незнакомая форма манифеста отправляет КАЖДУЮ новую книгу в `rejected`. (`grep -n '236 сужена D39.175' docs/PROGRESS.md``256`). Причина техническая и она в ПОРЯДКЕ ДЕПЛОЯ ниже: строка требует бампа
Берётся отдельно, со стоп-миром. **формы манифеста**, а параллельно идёт платформенный пак. Берётся отдельно, со стоп-миром.
- **Читающая сторона банка** (строки 221 · 224 · 226 · 228 · 230) — крупнейшая продуктовая дыра зоны, - **Читающая сторона банка — гейты РАЗНЫЕ, и их надо различать** (проверено по ячейкам:
но её гейт не техника, а **слово владельца**. Вопрос вынесен ему отдельно. `for n in 221 224 226 228 230; do grep "^| $n " docs/PROGRESS.md | awk -F'|' '{print $4" | "$5}'; done`):
- **221** — гейт «разморозка фронта», то есть слово владельца. Вопрос вынесен ему отдельно
⚠ (следа этого вопроса в репозитории нет — если тебе это важно для планирования, спроси, а не считай фактом).
- **224** — «держит экран подписи вместе с 221», зона «бэкенд + платформа». **Не берём: ресурс пака.**
- **226** — «диагностики нет вовсе». **Не берём: ресурс пака.**
- **228** — гейт «ЗАКАЗ по читающей стороне банка», то есть отдельный заказ оркестратора, а не слово
владельца. **Не берём: гейт — отдельный заказ.**
- **230** — зона «бэкенд + контракт». **Не берём: пак версию контракта не двигает** (см. ПОРЯДОК ДЕПЛОЯ).
- **Остальная свободная работа зоны в этот пак НЕ входит:** строки **246** (СРОЧНО — две копии полосы
отказов обещают разное), **247**, **233**, **229**, **141**-остаток, **131**. Из свободной очереди пак
берёт ровно **232** и **197** (`grep -n 'Свободная работа зоны' docs/PROGRESS.md``254`).
- **Следующий платный замер** — не в этом паке: у его оси «эффорт off против low» не разрешено - **Следующий платный замер** — не в этом паке: у его оси «эффорт off против low» не разрешено
предусловие (см. ниже), и до решения владельца он не заказывается. предусловие, и носитель предусловия назван — **строка 237(б)**, вендор-сверка маппинга эффорта
`deepseek-v4-pro` (`grep -n '^| 237 ' docs/PROGRESS.md``178`; реестр
`docs/architecture/13-tech-debt-anchors.md` §Б-108). По гардрейлу владельца закрывается ТОЛЬКО
вендор-докой. До решения владельца замер не заказывается.
## ⚠ ПОРЯДОК ДЕПЛОЯ — почему пак безопасен параллельно платформе ## ⚠ ПОРЯДОК ДЕПЛОЯ — почему пак безопасен параллельно платформе
Зоны независимы по коду, но **не по деплою**: форма манифеста требует «платформа первой», схема **Пак НЕ меняет форму манифеста, схему хранилища и версию контракта.** Понадобилось — **стоп и пинг**,
хранилища v16 — «движок первым». **Пак НЕ меняет форму манифеста, схему хранилища и версию не «сделаю аккуратно». Почему это жёстко:
контракта.** Понадобилось — **стоп и пинг**, не «сделаю аккуратно».
## Свобода и её разметка - **Форма манифеста БЕЗОПАСНОГО ПОРЯДКА НЕ ИМЕЕТ.** Гейт платформы — строгое равенство ОДНОЙ константе:
`if m.Version != KnownManifestVersion` (`grep -n 'if m.Version != KnownManifestVersion' platform/internal/ingest/manifest.go`
`112`; сама константа — `grep -n 'KnownManifestVersion = ' platform/internal/ingest/manifest.go`
`88`, `"tm-manifest-v2"`). Окна из двух версий НЕТ. Поэтому «платформа первой» так же смертельно, как
«движок первым»: выкатив любую сторону в одиночку, ты отправляешь КАЖДУЮ новую книгу в отказ
(`ReasonParserUnavailable`, который жжёт бюджет попыток). Бамп формы = **стоп-мир** либо специально
построенное временное окно двух версий. ⛔ **Рантбук зоны платформы советует обратное:**
`platform/deploy/README.md:213`=`сначала платформа, потом движок`. По коду этот совет опасен —
дефект чужой зоны, он передан платформенному паку; тебе он важен тем, что «платформа первой» НЕ
является безопасным обходом, если ты вдруг упрёшься в форму манифеста.
- **Схема хранилища — «движок первым»**, и это документировано: движок первым → `tmctl migrate` по
каждой книге до прогонов → и только потом платформа (D39.158 п.6,
`grep -n 'движок первым' docs/architecture/05-decisions-log.md``715`).
- **Версия контракта** — пак её не двигает. Твоё новое поле в `status --json` аддитивно и безопасно:
платформа читает отчёт аллоулистом (`ingest.StatusReport`, `runner/engine.go` «decodes the allowlisted
subset»), неизвестные поля игнорирует. Это ПРОВЕРЕНО, но проверь и ты — клейм «зоны независимы по
коду» держится именно на этом.
«Делай РОВНО так» — порядок пунктов · границы выше · запрет на три вещи из порядка деплоя · ## Свобода и её разметка — сводка
ограничение ФЧ-1 тремя посадками. **Решаешь сам и аргументируешь**: форма хранения и проводки
модели в числе согласия, форма раскрытия исторических строк, состав посадок. Каждый пункт состава размечен внутри себя; здесь только свод, тело — выше.
| Пункт | «Делай РОВНО так» (отступление = пинг) | «Решаешь сам и аргументируешь» |
|---|---|---|
| 1 ФЧ-5 | четыре обязательства D39.150 п.1, включая снятие обещания точности в ОБОИХ живых местах | форма проводки текущей модели, формула округления вверх, слова оговорки |
| 2 строка 232 | ось = `finalStageWave`; закрытие объявляется только по остатку «слот»; остаток «первая редакторская стадия» — вне скоупа и НАЗЫВАЕТСЯ в отчёте | где вычитается потраченный слот, как переживает рестарт |
| 3 ФЧ-4 | обе половины; фигура едет с базисом (D39.181 п.1) | форма раскрытия на проводе и в тексте отказа |
| 4 ФЧ-1 | ровно три названные гарантии; запись в `mutations.json`; `tmmutate` только на копии | форма посадок, уровень тестов, текст `why` |
Плюс общее «ровно так»: **порядок пунктов** · **границы выше** · **три запрета из ПОРЯДКА ДЕПЛОЯ**.
Всё, что здесь не названо, — твоё решение с аргументом в отчёте. Мои приоры **опровергаются аргументом
или замером**; неопровергнутое исполняется как написано.
## Мандат самопроверки — исполнением ## Мандат самопроверки — исполнением
`make battery` — не отчёт, а предусловие. Обязательны: **дифф `^func Test` исполнением, не памятью** · `make battery`**не отчёт, а предусловие** (проверено на этой машине: `cd backend && make battery`
**собственные адверсариальные посадки ВНЕ списка выше** · **отдельный проход по СВОЕЙ ГОТОВОЙ работе** EXIT=0, `golangci-lint` `0 issues`). Зелёная батарея НИЧЕГО не доказывает про твою работу — весь смысл
свежим контекстом. Субагенты разрешены явно; модель задавай ИМЕНЕМ. пункта 4 в том, что мутации её переживают.
**Править или удалять тест ради зелени — недопустимо.** Правка, вызванная сменой поведения, которая
заказана этим промтом, — обслуживание, и она **объявляется в отчёте**: что изменилось в поведении, **Обязательны, и каждое — с ПРЕДЪЯВЛЯЕМЫМ АРТЕФАКТОМ в отчёте:**
какой тест это описывал, куда уехала гарантия.
⚠ Перед отчётом **сверь каждый клейм с результатом инструмента ЭТОЙ сессии**. Последний абзац — 1. **Дифф `^func Test` исполнением, а не памятью.** Команда:
план или обещание? Сделай сейчас. `grep -rn '^func Test' backend --include=*_test.go | wc -l` (на HEAD этой смены → `1083`) — и
пофамильный дифф имён до/после. **Удалённых имён должно быть 0**; если не 0 — назвать каждое и
объяснить.
2. **Собственные адверсариальные посадки ВНЕ трёх гарантий пункта 4** — механизм тот же `tmmutate` на
копии дерева. Артефакт: **красный вывод на каждую посадку**, и отдельно — список посадок, которые
батарея ПЕРЕЖИЛА (это и есть находки).
3. **Отдельный проход по СВОЕЙ ГОТОВОЙ работе свежим контекстом.** **Субагенты разрешены явно; модель
задавай ИМЕНЕМ и знай, сколько их у тебя работает** (Fable 5 — рекомендация одного-двух, не потолок;
веер под предмет выбираешь ты).
**Артефакт прохода — СПИСОК НАХОДОК С ДИСПОЗИЦИЯМИ в отчёте.** «Прошёл, находок нет» отчётом НЕ
является: ноль находок обосновывается тем, ЧТО ИСКАЛОСЬ и какими командами.
**Оси ревью, которые я называю уязвимыми ИМЕННО В ЭТОМ ПАКЕ** (13 хватит; **вправе заменить с
аргументом**, если найдёшь уязвимее):
1. **Направление ошибки.** Округление ВВЕРХ ложится на путь, где старое поведение занижало. Классика
этого места — **двойной счёт** (округлили и там, и там) и **перелёт порога согласия** (число выросло
настолько, что гейт спрашивает там, где раньше не спрашивал, или наоборот).
2. **`HistoricalRows` на новой границе** — не теряется и не удваивается; текст отказа и `--json` говорят
ОДНО И ТО ЖЕ число с одним и тем же базисом.
3. **Слот гранта на черновом-только конвейере**`finalStageWave` на нём равен `draft`; убедись
исполнением, что ось не выродилась в «никогда ничего не отгружено».
**Особо недоверчиво к `usd()`:** его собственный докстринг называет вывод принадлежности к поколению
«an inference, not an identity — **the one place in here worth distrusting**» и перечисляет ДВЕ
предпосылки, каждая из которых ломается (`sed -n '86,112p' backend/internal/pipeline/reprice.go`). Твоя
правка ложится прямо на него.
⚠ Запрет подгонки тестов — `CLAUDE.md` (D39.183). Край, который тебе понадобится: **этот промт
заказывает смену поведения в пунктах 1 и 3** (обещание точности снимается, текст отказа меняется), и
протухший под неё тест правится как обслуживание — но правка ОБЪЯВЛЯЕТСЯ в отчёте: что изменилось,
какой тест это описывал, куда уехала гарантия. Нет в промте и нет ратификации — **пинг**.
⚠ Перед отчётом **сверь каждый клейм с результатом инструмента ЭТОЙ сессии**. Последний абзац — план
или обещание? Сделай сейчас.
## Отчёт ## Отчёт
Записка-план ДО работы (эхо: скоуп · инварианты · не-делать) в секцию «Бэкенд» `docs/PROGRESS.md`. **Записка-план ДО работы** — эхо-протокол старта, ≤10 строк «скоуп · инварианты · не-делать», в секцию
Итог — туда же, каждое число **с командой получения**. Обязательная секция **«Что НЕ удалось и что «Бэкенд» `docs/PROGRESS.md`. Первым действием, до кода.
НЕ проверено»**. Конфликт промта с кодом — **пинг через владельца, не интерпретация**.
**Комплектность против заказа — механически:** пройди по четырём пунктам состава и по таблице разметки и
отчитайся ПО КАЖДОМУ. ⚠ Эта тема трижды проваливалась на том, что исполнялся первый пункт, а нумерованный
остаток бросался.
**Итог — туда же, и «заявление = команда»:** каждое число и каждая категорика — **с командой получения**,
которая это число реально печатает. Приёмка пере-ранит твои команды; команда, печатающая «ok» вместо
числа, считается отсутствующей.
**Обязательная секция «Что НЕ удалось и что НЕ проверено».** ⚠ «Не проверено» ≠ «опровергнуто» — это
РАЗНЫЕ исходы, и **третий законен**: «взялся, не смог, вот где встал» — полноценный результат смены, а не
провал. Сюда же обязательно попадает остаток «первая редакторская стадия» (пункт 2) и любая гарантия
ФЧ-1, до которой не дошли руки.
**Конфликт промта с кодом или доками — ПИНГ через владельца, не интерпретация.** И у тебя есть **право
сказать «этого делать не надо»** с аргументом: сессия, приносящая развилку вместо тихой девиации, делает
правильно. Этот промт писан по коду на HEAD своей смены; код первичен, и если ты видишь другое — это
находка, а не твоя ошибка.
## Прямой канал ## Прямой канал
Механизм — `CLAUDE.md`, секция «Связь между сессиями». Свой блок в `/tmp/textmachine-channel` Механизм целиком — `CLAUDE.md`, «Связь между сессиями»: свой блок в `/tmp/textmachine-channel`, сверка
первым делом. ⚠ Адрес из файла **не доказательство, что сессия жива**: сверяйся с `ListAgents`. с `ListAgents` перед отправкой, канала нет — нормальный случай, сессии подряд не опрашивать. Что
Нужной роли нет ⇒ **канала нет, это НОРМАЛЬНЫЙ случай**, вопрос секцией в отчёт. добавляет этот пак: **эхо-записку отправь ПЕРВЫМ действием по адресу оркестратора** — это и есть
проверка канала.

View file

@ -1,8 +1,9 @@
# Промт сессии: ПЛАТФОРМА, пак P13 — «шов возврата оплаченного прогона» # Промт сессии: ПЛАТФОРМА, пак P13 — «шов возврата оплаченного прогона»
> **Роль:** платформа. **Зона записи:** `platform/`. **Не коммитишь** — дерево готовишь и передаёшь > **Роль:** платформа. **Зона записи:** `platform/`. Чужие зоны читаешь, не правишь. **Не коммитишь**
> на лендинг оркестратору (канон — `CLAUDE.md`, таблица ролей). Чужие зоны читаешь, не правишь. > и вообще не трогаешь индекс и историю — почему именно, и полный список запретов: `CLAUDE.md`,
> **Обязателен к ссылке:** `platform/docs/ENGINEERING_STANDARDS.md`; отступление от него — пинг, не > «Git-координация мультисессий».
> **Обязателен к ссылке:** `platform/docs/ENGINEERING_STANDARDS.md`; отступление — пинг, не
> самовольство. Направление зоны — `platform/docs/PLATFORM_DIRECTION.md`. > самовольство. Направление зоны — `platform/docs/PLATFORM_DIRECTION.md`.
## Какая проблема и что изменит твой результат ## Какая проблема и что изменит твой результат
@ -11,99 +12,430 @@
пользователя и без единого сигнала оператору**. Четыре независимых пути ведут к одному и тому же: пользователя и без единого сигнала оператору**. Четыре независимых пути ведут к одному и тому же:
человек заплатил, а получил либо не то, либо ничего, и вывести его из этого состояния нечем, кроме человек заплатил, а получил либо не то, либо ничего, и вывести его из этого состояния нечем, кроме
psql. После пака оплаченный прогон возвращается на **те деньги, за которые был продан**, оператор 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` до первой команды.** Замер 02.09 на этом хосте: **Условий хоста у батареи ЧЕТЫРЕ, и все четыре на этом хосте выполнимы. Выставь их до первой
`go test ./... -count=1` в `platform/` без DSN даёт **661 тест: PASS 342, SKIP 319, FAIL 0, exit 0**, команды** — иначе половина зоны молча не гоняется:
все 18 пакетов «ok». В `pgstore` тёмно 167 из 177, в `runs` — 110 из 135, и среди пропущенных —
**105 тестов денежного пути реконсилятора**.
`make check` про это ЧЕСТЕН — печатает «did NOT run (set TM_PLATFORM_TEST_DSN…)» и имена скипов. | условие | как выставить | что откроет |
|---|---|---|
| `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 ./...`. Молчит именно голый `go test ./...`.
**Цена, которую ты обязан не повторить:** в регистре **восемь рядов** заведены с формулировкой **Цена, которую ты обязан не повторить:** в регистре **21 ряд** заведён с формулировкой «мутация
«мутация пережила ПОЛНУЮ батарею», **четыре из них открыты** (`PD-433`, `PD-386`, `PD-389`, `PD-388`), пережила ПОЛНУЮ батарею», и **восемь из них открыты**`PD-433` · `PD-375` · `PD-382` · `PD-383` ·
и ни один не называет DSN. На хосте без базы такой вердикт не значит ничего. `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 = да».** Иначе пак **Каждая посадка мутации в твоём отчёте обязана нести строку «гонялось при DSN = да».** Иначе пак
проверит себя ровно тем способом, каким заводились ряды, которые он чинит. проверит себя ровно тем способом, каким заводились ряды, которые он чинит.
## Состав пака — четыре правки и один гейт ## Состав пака — четыре правки, гейт и одна строка рантбука
**1. ГЛАВНОЕ — денежный бюджет прогона ЧИТАТЬ, а не пере-выводить из ставки.** ### 1. ГЛАВНОЕ — денежный бюджет прогона ЧИТАТЬ, а не пере-выводить из ставки
`internal/runs/reconcile.go`, греп `s.Pricing.Ceiling(l.CeilingChapters)` в `reopen`: бюджет берётся
из ставки СЕГОДНЯШНЕГО деплоя. Вызывающих двое — свиповый `restart` и **контрактный `Resume`**, то
есть путь дёргает пользователь. Смена `TM_PLATFORM_USD_PER_CHAPTER` между покупкой и продолжением
молча пере-оценивает уже оплаченный прогон в обе стороны; при удвоении ставки на тонком счёте он
становится невозобновляемым. Носитель — `PD-168` (стоит `minor`, вес занижен).
**Задача:** сумма, на которую пользователь согласился, хранится при покупке и читается при
перезапуске. Форму решаешь сам и аргументируешь.
**2. `PD-89` — печатать ключ идемпотентности НА ПУТИ ОШИБКИ.** `write()` чеканит `id = newKey()`, `internal/runs/reconcile.go`, функция `reopen` (`:1350`). **Ставка читается там ДВАЖДЫ, и оба места
зовёт денежную операцию и на ошибке возвращает `err`, **не напечатав ключ**. Неоднозначный обрыв на денежные:**
коммите → оператор повторяет без `--key`**второе начисление**. Комментарий тремя строками ниже
описывает ровно эту ловушку, но только для пути чтения баланса. Лечение — одна строка; посадка
обязательна.
**3. `PD-426` + `PD-214` — решать «ЧЕЙ handshake» ПРЕЖДЕ, чем его валидировать.** ```
`internal/ingest/tail.go`, функция `apply`: четыре отказа (декод, `checkVersion`, пустой cd platform && grep -n 'Pricing' internal/runs/reconcile.go
`EngineRunID`, `Seq != 1`) стоят ВЫШЕ ветки «another attempt's stream begins here». Чужой ЗАКОННЫЙ # 1363: budget := s.Pricing.Ceiling(l.CeilingChapters)
hello карантинит проекцию **оплаченного живого прогона**, снятия карантина в дереве нет, операторской # 1397: consent = s.Pricing.Ceiling(l.CeilingChapters)
ручки нет. Гейдж `tm_platform_quarantined_attempts` это видит и молчит о том, что делать. ```
**Задача:** перестановка решения о принадлежности выше валидации + ручка снятия карантина.
**4. МЕЛКОЕ, закрывающее урок пака: гейт батареи печатает ВСЕ условия хоста, а не первое.** - `:1363`**бюджет** перезапуска: `remaining = budget - spent`, отсюда `Ceiling` в `RestartInput`.
Сегодня `platform/Makefile` подсказывает роль первого недостающего условия и молчит про остальные. - `:1397`**funded consent на пере-оплату** при `book.BankMoved`, уезжает в `RestartInput.AcceptRebill`.
Стоит в ветке `if from == fromAFinishedRun`, то есть **только на пользовательском `Resume`** — ровно
на том пути, который и опасен. Комментарий `:1384-1386` подтверждает денежный смысл: «funded
consent, the same figure Start grants».
**5. ГЕЙТ ПРОТИВ КЛАССА, а не против ряда** (заказ оркестратора №22, замер 02.09): колонка Вызывающих у `reopen` двое: свиповый `restart` (`:1224`, вызов `:1244`, `fromALiveRun`) и контрактный
серьёзности регистра разъехалась с сутью — **21 открытый ряд** несёт два и более слова тревоги `Resume` (`:1481`, вызов `:1548`, `fromAFinishedRun`). Смена `TM_PLATFORM_USD_PER_CHAPTER`
(деньги · холд · «молча» · блокирует · невидим · 500 · паника) при `minor`/`info`. Добавь в гейт (`internal/config/config.go:485`=`TM_PLATFORM_USD_PER_CHAPTER`) между покупкой и продолжением молча пере-оценивает уже оплаченный
зоны печать таких рядов. Без него следующая разведка снова доложит «major всего два». прогон в обе стороны; при удвоении ставки на тонком счёте он становится невозобновляемым. Носитель —
`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 мс - **`PD-412`/`PD-413` (LATERAL в полосном агрегате).** Находки настоящие и замеренные (1.359 мс →
на книге 2283 глав), но это переписывание запроса в горячем read-модельном пути, а арифметика полосы 0.307 мс на книге 2283 глав — числа принадлежат `PD-412`), но это переписывание запроса в горячем
в этом паке не трогается. Контенция, а не корректность денег. read-модельном пути, а арифметика полосы в этом паке не трогается. Контенция, а не корректность денег.
- **`PD-162` (клин на удалённом каталоге).** Настоящий и денежный, но его терминальность — это эскроу - **`PD-162` (клин на удалённом каталоге).** Настоящий и денежный, но его АВТОМАТИЧЕСКАЯ терминальность
(П-18), а половину эскроу рядом с проектируемым целым зона уже отказывалась строить (диспозиция P7). — это эскроу (`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` в корне отсутствует), но **НЕ по Зоны независимы по коду (разные модули, перекрёстных импортов нет в обе стороны, `go.work` в корне
деплою**, и сцеплений ДВА с противоположным порядком: форма манифеста ⇒ «сначала платформа, потом отсутствует), но **НЕ по деплою**, и сцеплений ДВА.
движок» (незнакомая форма → `parser_unavailable`, интейк массово отклоняет); схема хранилища v16 и
обход ⇒ «движок первым → `tmctl migrate` → потом платформа» (D39.158 п.6). - **Форма манифеста БЕЗОПАСНОГО ПОРЯДКА НЕ ИМЕЕТ.** Гейт интейка — строгое равенство ОДНОЙ константе
**Поэтому:** пак НЕ меняет форму манифеста, схему хранилища и версию контракта. Понадобилось — (`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 · строка «DSN = да» у каждой посадки · границы выше · запрет на **«Делай РОВНО так» (отступление = пинг):**
три вещи из порядка деплоя. Всё остальное — **решаешь сам и аргументируешь**: форма хранения бюджета, - предусловие хоста (четыре условия) · строка «DSN = да» у каждой посадки;
форма ручки снятия карантина, состав посадок, порядок работ внутри пака. - **ФАКТ каждой из шести правок** — что именно закрывается: (1) `reopen` читает хранимую сумму в ОБОИХ
местах, `:1363` и `:1397`, схему платформы не трогая; (2) ключ печатается на пути ошибки;
(3) свойство «чужая строка не карантинит нашу проекцию» с тремя оговорками пункта 3 (декод остаётся
выше · понижение только при известном `want` · при `want == ""` валидация полная); (4) цель `check`
печатает ВСЕ условия; (5) гейт против класса живёт в зоне платформы; (6) ряд регистра и правка
строки рантбука;
- **обязательные посадки у пунктов 2 и 3**;
- границы выше · три запрета из порядка деплоя · запрет трогать `docs/scripts/counts.py`.
**«Решаешь сам и аргументируешь» — ФОРМА и только она:** какую колонку холда читать и как быть без
холда · форма печати ключа · форма ручки снятия карантина · форма печати условий в `Makefile` ·
место, предикат и порог гейта пункта 5 · формулировка правки рантбука в пункте 6 · остальной состав
посадок · порядок работ внутри пака.
⚠ Катч-олла «всё остальное — сам» больше нет: если чего-то нет ни в одном списке — это пинг, а не
твоё усмотрение.
## Мандат самопроверки — исполнением, не перечитыванием ## Мандат самопроверки — исполнением, не перечитыванием
Батарея зелёная — это НЕ отчёт. Обязательны: **живая проба каждого из четырёх путей** под DSN · Батарея зелёная — это НЕ отчёт. Обязательны все три:
**собственные адверсариальные посадки ВНЕ списка выше** · и **отдельный проход по СВОЕЙ ГОТОВОЙ
работе** свежим контекстом. Субагенты разрешены явно; модель задавай ИМЕНЕМ и знай, сколько их 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). Здесь важен один его край: заказанность
решает ЗАКАЗ, а не ты, и **этот промт заказывает смену поведения в пунктах 14** — протухший под неё
тест правится как обслуживание, но правка ОБЪЯВЛЯЕТСЯ в отчёте (что изменилось, какой тест это
описывал, куда уехала гарантия).
## Отчёт ## Отчёт
Записка-план ДО работы (эхо-протокол: скоуп · инварианты · не-делать) в `platform/docs/platform-PROGRESS.md`. **Записка-план ДО работы** (эхо-протокол: скоуп · инварианты · что НЕ делаю, ≤10 строк) — в
Итог — туда же. Каждое число и каждая категорика — **с командой получения**: приёмка пере-ранит. `platform/docs/platform-PROGRESS.md`, и тем же текстом первым действием по каналу (см. ниже).
Обязательная секция **«Что НЕ удалось и что НЕ проверено»** — «не проверено» ≠ «опровергнуто». Итог — туда же. ⛔ В `docs/PROGRESS.md` платформа НЕ пишет (канон, таблица ролей).
Конфликт промта с кодом — **пинг через владельца, не интерпретация**; право сказать «этого делать не
надо» с аргументом у тебя есть. **Каждое число и каждая категорика — с КОМАНДОЙ ПОЛУЧЕНИЯ, которая это число реально печатает.**
Приёмка пере-ранит. ⚠ Команда, печатающая «ok» вместо числа, командой
получения не является.
Обязательная секция **«Что НЕ удалось и что НЕ проверено»**: исходов ТРИ, третий законен, и молчание
об упёршемся пункте дороже самого упора.
## Канал вопросов и право отказаться
**Конфликт промта с кодом — пинг через владельца, не интерпретация.** Если число выше не сошлось,
если факт о коде не воспроизводится, если два места этого промта говорят разное — это находка, неси
её, а не подгоняй работу под текст.
**Право сказать «этого делать не надо» с аргументом у тебя есть** — включая пункты состава и границы
выше. Сессия, приносящая развилку вместо тихой девиации, делает правильно.
## Прямой канал ## Прямой канал
Механизм — `CLAUDE.md`, секция «Связь между сессиями». Впиши свой блок в `/tmp/textmachine-channel` Механизм целиком — `CLAUDE.md`, «Связь между сессиями»: впиши свой блок в `/tmp/textmachine-channel`,
первым делом; адрес роли бери оттуда, но **сверяйся с `ListAgents`: адрес из файла не доказательство, сверься с `ListAgents` перед отправкой, канала нет — это нормально и опрашивать сессии подряд не надо.
что сессия жива**. Нужной роли нет ⇒ **канала нет, и это НОРМАЛЬНЫЙ случай — не опрашивай сессии Что добавляет этот пак: **эхо-записку отправь ПЕРВЫМ действием по адресу оркестратора — это и есть
подряд**, вопрос секцией в отчёт. проверка канала**; и знай, что в файле прямо сейчас лежат блоки закрытых смен, один с пометкой
«⚠ СМЕНА СДАНА 31.08 … по адресу не писать».

View file

@ -16,10 +16,12 @@
**Залендены и ратифицированы, приёмочные дампы — телами нот:** платформенный **P11** — D39.169 · движковый пак **«деньги»** — D39.170 · платформенный **P12** — D39.180 (вместе с контрактным минором **0.9.0**; `PD-425` закрыт им же) · бэкенд-пак **«деньги и честность выдачи»** — D39.182 (тестов 1045→1083, удалённых ноль) · **закон раскрытия** движка — D39.181, ратифицирован владельцем и применён к одиннадцати экземплярам. **Залендены и ратифицированы, приёмочные дампы — телами нот:** платформенный **P11** — D39.169 · движковый пак **«деньги»** — D39.170 · платформенный **P12** — D39.180 (вместе с контрактным минором **0.9.0**; `PD-425` закрыт им же) · бэкенд-пак **«деньги и честность выдачи»** — D39.182 (тестов 1045→1083, удалённых ноль) · **закон раскрытия** движка — D39.181, ратифицирован владельцем и применён к одиннадцати экземплярам.
**ВЫДАНЫ 02.09, сессий ещё не было — работают ПАРАЛЛЕЛЬНО, зоны независимы по коду:** платформенный **P13** «шов возврата оплаченного прогона» (`docs/PLATFORM_P13_SESSION_PROMPT.md`: `PD-168` бюджет из холда · `PD-89` ключ на пути ошибки · `PD-426`/`PD-214` чужая строка не карантинит проекцию · `PD-374` гейт батареи · гейт против класса серьёзности · строка рантбука деплоя) и бэкендовый **«число согласия на пути второй покупки»** (`docs/BACKEND_CONSENT_SESSION_PROMPT.md`: ФЧ-5 ось модели · строка 232 остаток слота · ФЧ-4 обе половины · ФЧ-1 три гарантии). ⚠ **Сцепление по деплою названо в обоих: у формы манифеста БЕЗОПАСНОГО ПОРЯДКА НЕТ** — гейт интейка `platform/internal/ingest/manifest.go:112`=`if m.Version != KnownManifestVersion` сравнивает со строгим равенством одной константе, окна двух версий нет; рантбук `platform/deploy/README.md:213`=`сначала платформа, потом движок` советует обратное и отдан на починку паку P13 пунктом 6.
**Проводка `--max-units` в платформу остаётся ГЕЙЧЕНОЙ. Живое основание одно и оно механическое:** единственный писатель `bank_moved_at` — дверь правок банка (`platform/internal/runs/bank.go:182`), а рост АВТО-банка от майнинга этого флага не ставит, поэтому майнящая книга на второй покупке идёт без `--resnapshot` и умирает на снапшот-гарде движка — холд взят, попытка сожжена (`PD-422`). Это чинится проводкой признака движения банка от майнинга, а не решением владельца. **Проводка `--max-units` в платформу остаётся ГЕЙЧЕНОЙ. Живое основание одно и оно механическое:** единственный писатель `bank_moved_at` — дверь правок банка (`platform/internal/runs/bank.go:182`), а рост АВТО-банка от майнинга этого флага не ставит, поэтому майнящая книга на второй покупке идёт без `--resnapshot` и умирает на снапшот-гарде движка — холд взят, попытка сожжена (`PD-422`). Это чинится проводкой признака движения банка от майнинга, а не решением владельца.
**ОТКРЫТО НА ОРКЕСТРАТОРЕ №22:** **ОТКРЫТО НА ОРКЕСТРАТОРЕ №22:**
**Ресёрч-сессия `textmachine-36`** написала `docs/research/29-harness-topology-survey.md` (топологии харнесса), НЕ коммитит — ⚠ **лендинг на №22, обязательство ПЕРЕАДРЕСОВАНО 01.09** (№21 закрыт, не успел). Состояние на 01.09: файл лежит в СОСЕДНЕМ worktree `/home/ubuntu/projects/textmachine/docs/research/` (незакоммиченное между worktree НЕ ВИДНО — `git status` в main-дереве пуст по построению), вырос до ~302 КБ, сессия idle и «готово» не прислала. ⚠ Условия лендинга, согласованные с ней: ратифицировать НЕЧЕГО (ресёрч-фактура, не решение) · нужна ревью-шапка (что доказано / что СНЯТО / что не проверено) · её §4 и §5 остаются obstacle-секциями · её собственный адверсариальный проход дал 86 находок и 20 записей класса блокер, из которых 15 РАЗЛИЧНЫХ и все исправлены (5 были дублями разных рецензентов) · незакрытый долг назван ею самой: 49 находок класса ВАЖНОЕ и 17 мелочей. ⚠ Формулировка для шапки, точнее оркестраторской: судья как СТАДИЯ ДВИЖКА не построен и не вызывался (0 вызовов), но судьи в ПОЛИГОНЕ были и есть — без этого различения следующая сессия выбросит замеры топологии. **Ресёрч-сессия `textmachine-36`** написала `docs/research/29-harness-topology-survey.md` (топологии харнесса), НЕ коммитит — ⚠ **лендинг на №22, обязательство ПЕРЕАДРЕСОВАНО 01.09** (№21 закрыт, не успел).**МАРШРУТ ЗАКРЫТ СЛОВОМ ВЛАДЕЛЬЦА 02.09: «не трогай соседнюю git tree ветку вообще».** №22 внёс этот файл в `main` копированием из соседнего worktree и был поправлен дважды: файл принадлежит ветке `polygon`, в `main` ему делать нечего. Лендинг ОТКАЧЕН (`git revert`, файл в чужом дереве восстановлен байт-в-байт). ⚠ **Следующей смене: не повторять — ни `cp`, ни чтением, ни `git -C` по соседнему дереву.** Открытый вопрос владельцу: файл лежит на ветке зоны, значит и коммитит его та зона своей рукой, а в `main` он приезжает мержем ветки; подтверждения этого маршрута нет, поэтому обязательство висит. Состояние на 01.09: файл лежит в СОСЕДНЕМ worktree `/home/ubuntu/projects/textmachine/docs/research/` (незакоммиченное между worktree НЕ ВИДНО — `git status` в main-дереве пуст по построению), вырос до ~302 КБ, сессия idle и «готово» не прислала. ⚠ Условия лендинга, согласованные с ней: ратифицировать НЕЧЕГО (ресёрч-фактура, не решение) · нужна ревью-шапка (что доказано / что СНЯТО / что не проверено) · её §4 и §5 остаются obstacle-секциями · её собственный адверсариальный проход дал 86 находок и 20 записей класса блокер, из которых 15 РАЗЛИЧНЫХ и все исправлены (5 были дублями разных рецензентов) · незакрытый долг назван ею самой: 49 находок класса ВАЖНОЕ и 17 мелочей. ⚠ Формулировка для шапки, точнее оркестраторской: судья как СТАДИЯ ДВИЖКА не построен и не вызывался (0 вызовов), но судьи в ПОЛИГОНЕ были и есть — без этого различения следующая сессия выбросит замеры топологии.
**НОРМЫ, ЗАРАБОТАННЫЕ ЭТИМИ ПРИЁМКАМИ:** **НОРМЫ, ЗАРАБОТАННЫЕ ЭТИМИ ПРИЁМКАМИ:**
- **Зелёная батарея — это ПОЛНЫЙ список пакетов ПЛЮС отсутствие FAIL**, а не отсутствие FAIL: обёртка - **Зелёная батарея — это ПОЛНЫЙ список пакетов ПЛЮС отсутствие FAIL**, а не отсутствие FAIL: обёртка

View file

@ -22,8 +22,8 @@
| Роль | Активный промт | Статус | | Роль | Активный промт | Статус |
|---|---|---| |---|---|---|
| Оркестратор | [ORCHESTRATOR_SESSION_PROMPT.md](ORCHESTRATOR_SESSION_PROMPT.md) | роль и нормы; счётчик роли — CURRENT-STATE | | Оркестратор | [ORCHESTRATOR_SESSION_PROMPT.md](ORCHESTRATOR_SESSION_PROMPT.md) | роль и нормы; счётчик роли — CURRENT-STATE |
| Бэкенд | [BACKEND_CONSENT_SESSION_PROMPT.md](BACKEND_CONSENT_SESSION_PROMPT.md) | последний пак — «деньги и честность выдачи», принят и заленджен 31.08 (**D39.182**; закон раскрытия — **D39.181**). Открыто: различение уровней теста (вопрос владельцу) · §2.2 против `snapshotdiff` · строка **246** (чинит платформа) | | Бэкенд | [BACKEND_CONSENT_SESSION_PROMPT.md](BACKEND_CONSENT_SESSION_PROMPT.md) | **ВЫДАН 02.09, сессия не отработала** — пак «число согласия на пути второй покупки» (ФЧ-5 · строка 232 · ФЧ-4 · ФЧ-1). Предыдущий пак — «деньги и честность выдачи», заленджен 31.08 (**D39.182**; закон раскрытия — **D39.181**) |
| Платформа | [PLATFORM_P13_SESSION_PROMPT.md](PLATFORM_P13_SESSION_PROMPT.md) | последний пак — P12 «долги под ногами», принят и заленджен 31.08 (**D39.180**) вместе с контрактным минором. Открытое зоны — её регистр дефектов (`platform/docs/DEFECT_REGISTER.md`, счёт — `counts.py --check`) | | Платформа | [PLATFORM_P13_SESSION_PROMPT.md](PLATFORM_P13_SESSION_PROMPT.md) | **ВЫДАН 02.09, сессия не отработала** — пак P13 «шов возврата оплаченного прогона» (`PD-168` · `PD-89` · `PD-426`/`PD-214` · `PD-374` · гейт против класса · строка рантбука). Предыдущий — P12, заленджен 31.08 (**D39.180**) вместе с контрактным минором |
| Полигон | [POLYGON_EXP2223_REDO_SESSION_PROMPT.md](POLYGON_EXP2223_REDO_SESSION_PROMPT.md) (отложенный — [POLYGON_PACKAGE4_SESSION_PROMPT.md](POLYGON_PACKAGE4_SESSION_PROMPT.md), строка 85) | фаза Д ИДЁТ; ⚠ живой носитель курса — в `eval/dovodka/`, какой именно называет зона (⚠ [POLYGON_PHASE_D_HANDOFF.md](POLYGON_PHASE_D_HANDOFF.md) — перекрытый снимок, читать не как курс) | | Полигон | [POLYGON_EXP2223_REDO_SESSION_PROMPT.md](POLYGON_EXP2223_REDO_SESSION_PROMPT.md) (отложенный — [POLYGON_PACKAGE4_SESSION_PROMPT.md](POLYGON_PACKAGE4_SESSION_PROMPT.md), строка 85) | фаза Д ИДЁТ; ⚠ живой носитель курса — в `eval/dovodka/`, какой именно называет зона (⚠ [POLYGON_PHASE_D_HANDOFF.md](POLYGON_PHASE_D_HANDOFF.md) — перекрытый снимок, читать не как курс) |
| Фронт | активного НЕТ | **ЗОНА ЗАМОРОЖЕНА** (D39.136 п.2 + D39.147: разморозка отдельным словом владельца, не привязана к P7); перечень первого касания — в зонном журнале | | Фронт | активного НЕТ | **ЗОНА ЗАМОРОЖЕНА** (D39.136 п.2 + D39.147: разморозка отдельным словом владельца, не привязана к P7); перечень первого касания — в зонном журнале |
| Контракт | активного НЕТ | канон — `architecture/14-api-contract/`; ⚠ версию брать ФАЙЛОМ (`grep '^ version:' openapi.yaml`), не отсюда: миноры ратифицируются D-нотами и их содержание грепается по реестру нот. | | Контракт | активного НЕТ | канон — `architecture/14-api-contract/`; ⚠ версию брать ФАЙЛОМ (`grep '^ version:' openapi.yaml`), не отсюда: миноры ратифицируются D-нотами и их содержание грепается по реестру нот. |