textmachine/docs/PLATFORM_P9_SESSION_PROMPT.md

251 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Промт: платформа, пак P9 — смонтировать дверь правок банка, довести прогресс и расшить живой прогон
> **Выдан оркестратором №19, 27.08.2026.** Это пункт **(2в)** очереди, ратифицированной **D39.156**, и
> последний в связке шва. Два предыдущих отработаны: движковый пак — **D39.158** (`d1eb8a9`),
> контрактный минор 0.5.0 — **D39.161**. Твой пак закрывает связку и **впервые делает возможным живой
> прогон книги насквозь** (строка **202**, не гонялся НИ РАЗУ).
## §0. Какая проблема и что решит твой результат
Сегодня цепь «пользователь поправил термин → перевод пере-собрался с этой правкой» **разорвана на
твоей стороне**. Движок принимать правки умеет с 27.08: есть $0-глагол `tmctl bank-apply`. Канон
объявил дверь `POST /books/{bookId}/bank/corrections` СЕГОДНЯ же, минором 0.5.0 (D39.161). **Платформа не делает ни того, ни
другого:** двери нет, глагол не вызывается, ключи движку не передаются.
Следствия, каждое с носителем:
- **Правку положить некуда** — строка **199**, движковая половина построена, платформенный конец открыт.
- **Живой прогон невозможен** — строка **211**: движок на SaaS не получает провайдерских ключей.
⚠ Движковая половина этого блокера ПОСТРОЕНА (`--keys-file`), платформа его не передаёт: греп по
зоне даёт НОЛЬ. То есть блокер снят наполовину и вторая половина — твоя.
- **Полоса прогресса обнуляется на снятии стопа** — требование владельца от 20.08, строка **200**.
- **Канон объявляет два поля, которых деплой больше не должен слать** — `PD-399`.
- **Деплой знает чужую конвенцию пути** — строка **213**.
**Что решит результат.** После пака: пользователь правит термин через API, правка доезжает до движка,
прогон возобновляется и пере-собирает банк с ней; полоса не врёт; и книгу наконец можно прогнать
насквозь по-настоящему.
## §1. Зона записи и git
**Твоя зона — `platform/`.** Отчёт и находки — зонный журнал `platform/docs/platform-PROGRESS.md`,
новые дефекты — строками в `platform/docs/DEFECT_REGISTER.md` тем же деревом.
- **Ты НЕ коммитишь.** Лендит оркестратор после адверсариальной приёмки. Канон — `CLAUDE.md`.
- `docs/` (корень), `backend/`, `frontend/`, `eval/`**не трогать ничем**. Если увидишь, что правка
нужна там, — это пинг, а не правка.
- В дереве живёт незакоммиченная работа зоны полигона (20 позиций). **Чужого не касаться.**
- **Мутации — только в КОПИИ дерева, и копия несёт КАНОН:**
`cp -a --parents platform docs/architecture/14-api-contract <куда>/`. Голая `cp -a platform` даёт
постоянный красный `gates.TestTheAnnouncedContractVersionIsTheOneTheCanonRatified` и вердикты лгут —
норма зоны `ENGINEERING_STANDARDS` §3 п.3.
## §2. Карта чтения — ≤5 позиций, ЗАКОН
1. **`docs/architecture/14-api-contract/openapi.yaml`, блок `/books/{bookId}/bank/corrections`**
(`:478`) и схемы `BankCorrection*` — это ТВОЙ заказ дословно: описание двери там уже написано, тебе
его исполнять, а не сочинять. Плюс `Capabilities` (`:1297` — флаг `bank_corrections_enabled`).
2. **`docs/architecture/14-api-contract/README.md` §2.19** — вывод двери из словаря глагола с
провенансом `file:line` и **тремя названными обязательствами монтажа** (они адресованы тебе).
3. **Словарь и коды глагола — в КОДЕ движка, он первичен:**
`backend/internal/membank/decisions.go` (тип `Decision`, строки 51117) ·
`backend/internal/pipeline/bankdecisions.go` (отчёт, `SignatureState` `:88-118`, кап `:647`) ·
`backend/cmd/tmctl/main.go:69-75` (полоса отказов 1019) и `:95` (код 3 — стоп).
4. **`platform/docs/platform-PROGRESS.md`** — свой журнал: последняя запись приёмки P8-REVIEW и
**ПИНГ №21** (он про твою дверь).
5. **`docs/architecture/18-bank-ontology.md`** (69 строк, ратифицирована D39.158) — роли носителей и
дисциплина проекции. Короткая и объясняет, ПОЧЕМУ дверь устроена так.
⚠ Ратифицированное, что бьёт всё, вложено в §3.0 — грепать журнал решений не требуется.
## §3. Состав пака
### §3.0. Ратифицированное, из чего исходишь
- **D39.144:** подписывается ВЕСЬ банк ОДНИМ «ОК». Пер-термно существует не подпись, а ПРАВКА.
- **D39.158:** стоп банка — ФЛАЖОК, движок чтит его сам; «ОК» = возобновление.
- **D39.156 п.6:** платформа НЕ пере-реализует движковый закон у себя. Считает движок — ты отдаёшь.
- **D39.161:** канон 0.5.0; дверь объявлена, признак «не построено» машиночитаем.
- **D39.160:** сквозная полоса прогресса едет ЗДЕСЬ, а не в контрактном миноре.
### §3.1. Смонтировать дверь — контракт делай РОВНО так, свободна только внутренняя реализация
Маршрут, схемы, коды и тексты **уже написаны в каноне** — исполняй их, не переизобретай. Раскладка
отказов оттуда: **409 `bank_corrections_refused`** (всё-или-ничего; отказанные — в `refusals[]`
конверта `Problem`) · **409 `run_in_flight`** · **503 `bank_corrections_incomplete`** («слать ТОТ ЖЕ
документ», ретрай сходится) · **413** (1 МиБ) · **404**, когда дверь не смонтирована.
**Отображение кодов движка на HTTP — РАЗМЕЧЕНО ПОПОЛАМ, не путай половины.**
**Заказано каноном, делай ровно так** (не «твой аргумент» — это уже решено и опубликовано):
**14 → 409 `bank_corrections_refused`** · **15 → 503 `bank_corrections_incomplete`** ·
**12 → 409 `run_in_flight`** · **тело > 1 МиБ → 413**.
**Свободен и обязан обосновать:** чем едут **13** (схема проекта не та — операторская ошибка деплоя,
не пользовательская) и **10 / 11 / 19**. Приор, опровергаемый аргументом: ни один из них не должен
становиться неразличимым 500 — пользователь обязан отличать «пере-реши» от «повтори то же» от
«позови оператора».
**`Capabilities.bank_corrections_enabled`** обязателен: `false` ⇒ дверь не смонтирована ⇒ `404`.
Условное монтирование в зоне уже есть — `platform/internal/httpapi/v0.go:75+`, поле `mounts:`.
### §3.2. Форма вызова: СИНХРОННО, и вот почему — но проверь на себе
Ратифицированный состав (2в) говорит «воркер решений → глагол перед возобновлением». Он писался,
когда двери в контракте НЕ БЫЛО. **Проверено мной 27.08 исполнением:**
- движок на банковом стопе **ВЫХОДИТ** (`cmd/tmctl/main.go:95`, код **3**), процесса не остаётся;
- флок проекта — **не-блокирующий эксклюзивный на процесс** (`backend/internal/store/store.go:187`),
ядро отпускает его на выходе процесса;
- значит **на стопе флок свободен**, и синхронный вызов `bank-apply` из обработчика законен;
- кап в 5000 решений поставлен движком ровно из соображения «вызов должен влезать в таймаут
вызывающего» (`bankdecisions.go:647`) — то есть синхронность в нём уже учтена.
**Заказ: строй синхронно** — принял документ, вызвал глагол, вернул его отчёт как квитанцию.
**Один остаток, и это НЕ воркер, а одна строчка сериализации.** Гонка: `resume` спавнит прогон,
ПОКА дверь обслуживает вызов по той же книге. Она доброкачественная по построению — `translate`
упрётся в занятый флок и откажет классом 12, — но холостая попытка стоит денег и шума, поэтому дешевле
её не плодить: **пер-книжная сериализация «не спавнить resume, пока по этой книге живёт вызов
corrections»** — мьютекс в обработчике, не очередь и не воркер. Зеркальная половина уже дана самой
дверью: corrections пришёл при живом прогоне → мгновенный класс 12 → твой `409 run_in_flight`.
**Взаимоисключение живого прогона и решения — не наша выдумка, а п.2 закона входной двери шва**, и
оно запинено цепным тестом движка (`backend/internal/pipeline/bankchain_test.go:62-64`): «a live run
and a decision are mutually exclusive by design… the operator decides between runs». Твой обработчик
обязан жить в том же ~60-секундном классе бюджета, который зона даёт вызовам движка.
**Если найдёшь, что синхронность не держится** (свой замер, не рассуждение) — это ПИНГ, и я снимаю
пункт «воркер» эрратой к D39.156. Молча воркер не строить и молча не выкидывать.
### §3.3. Передать движку ключи — делай РОВНО так
Строка **211**, вторая половина. Движок принимает `--keys-file <абсолютный путь>`
(`backend/cmd/tmctl/dotenv.go:46,63`, пин `cmd/tmctl/keysfile_test.go`). Раннер строит argv в одном
месте — `platform/internal/runner/runner.go:146` (`startArgv`, «built in one place so a test can read
it»). **Путь берётся из конфигурации деплоя, в окружение юнита ключи не кладутся.**
⚠ Это половина БЛОКЕРА живого прогона: без неё строка 202 не двигается.
### §3.4. Полоса прогресса — реши САМ, но причину знай
Строка **200**, требование владельца: **ОДНА доля на всю работу прогона**, считает СЕРВЕР (иначе
клиент снова начнёт знать про фазы), плюс подпись «что делается сейчас».
**Причина обнуления найдена мной и она конкретна:** `platform/internal/pgstore/readmodel.go`
`segmentUnits` (`:400`) переключается с `units_draft_done` на завершённые юниты в момент, когда банк
released, а `runProgress` (`:446`) меряет от базовой линии прогона и каппится его покупкой. То есть
полоса меряет СНАЧАЛА черновую волну, ПОТОМ редакторскую — отсюда «100%, затем ноль».
**Не сноси то, что стоит по делу.** Комментарий `:437-445` объясняет, почему бар прогона
базируется и каппится: решения переживают прогоны, возобновлённый прогон пере-ходит готовые главы за
$0 и не должен их двигать. Рядом уже есть `chaptersDone` (`:403`) — КНИЖНАЯ доля, другой вопрос.
**Твоя задача — монотонная доля на всю работу прогона через обе волны; форма твоя, обоснуй.**
### §3.5. Снять два поля с провода — делай РОВНО так
`PD-399`. Канон 0.5.0 больше НЕ объявляет `pending_decisions` и `complete`, а
`pgstore/readmodel.go:330` их кладёт. ⚠ **Их присутствие ЗАПИНЕНО**
`internal/httpapi/reading_test.go:113` — значит это правка с пином, а не вычёркивание.
### §3.6. Снять дубль конвенции пути — делай РОВНО так
Строка **213**: `platform/internal/runner/artifacts.go:65+` (`projectDB`) парсит `book.yaml`
нестрогим декодером ради двух ключей и при пустом `project_db` САМА вычисляет путь по движковой
конвенции. Платформа не должна знать конвенцию чужой зоны.
### §3.7. Чего в паке НЕТ — и пропуски ПОДПИСАНЫ
Читающая сторона банка (решённость · история · улика предложения — строки **221**, **224**, **226**)
**отдельный ДВИЖКОВЫЙ пак**, разобран консилиумом 27.08 и заказа пока не имеет. Не бери, даже если
покажется, что экрану это нужно: писатель там движок, не ты.
**Пропуски, которые я подписываю сознательно** (норма требует сверять состав против всех листов
владельца и подписывать пропуск, а не умалчивать):
- **`sqlc`НЕ в этом паке.** Слово владельца 20.08 «я вообще за» стоит в твоём журнале, и решение
22.08 — **отдельной сессией**: работа механическая (41 запрос в пяти файлах), и мешать её с
содержательной запрещено прямо. Не бери попутно.
- **Строка 198** (апгрейд движка безвозвратно стирает замечания и счётчики книги) — **НЕ в этом
паке**, хотя она твоей зоны и помечена «до первого реального пользователя». Причина: она о
композиции двух половин и требует движковой стороны, а этот пак обязан остаться про шов и живой
прогон. Она пойдёт следующим заказом — не считай пропуск забывчивостью.
- **Открытые строки регистра `PD-375``PD-398`** (пак P8-REVIEW) — **НЕ в этом паке** целиком; из них
сюда взята одна `PD-399`, потому что её породил вчерашний минор. Остальные — отдельный кодовый пак.
## §4. Самопроверка ИСПОЛНЕНИЕМ — «перечитал сам» её не удовлетворяет
Обязательная норма зоны: **каждый деливерабл проверен исполнением, не чтением.** Названный механизм:
1. **Батарея с ТРЕМЯ гейтами** (`TM_PLATFORM_TEST_DSN` · пара `_ENGINE_BIN` + `_BOOK_TEMPLATE` ·
достижимый пользовательский менеджер systemd). Ожидание — EXIT=0, линтер 0 issues, **скипов 0**.
⚠ Скипы логом не доказываются: секция «did NOT run» печатается только при найденном `--- SKIP`;
счёт снимай отдельно `go test ./... -count=1 -v` с грепом.
2. **Дверь — ЖИВОЙ пробой против НАСТОЯЩЕГО движка**, а не мока: подними стенд (рецепт —
`STACK_DECISIONS.md` §«Стенд разработчика»), доведи книгу до банкового стопа, пошли в дверь
`preview`, потом настоящую правку, потом `resume` — и покажи, что следующий прогон её ВИДИТ.
Это и есть доказательство, что цепь срослась. **Артефакт: лог пробы в отчёте.**
3. **Свои посадки мутаций** по норме §3 п.3: минимум по одной на дверь, на ключи и на прогресс;
вердикт — по ДЕЛЬТЕ против чистой базовой линии ТОЙ ЖЕ копии и по ТОПИЧНОСТИ упавшего теста, не по
цвету батареи.
**Субагенты РАЗРЕШЕНЫ явно** (харнесс по умолчанию их не берёт): опровергатель на раскладку кодов
§3.1 и на форму прогресса §3.4 — ЗАКАЗ, не право; его находки в отчёт, включая отвергнутые с причиной.
**Веер соразмеряй заранее: 24 агента.** Панель на полтора десятка на этой машине убивает сама себя.
## §5. Оси ревью — 13, вправе заменить с аргументом
1. **Ось «деньги»** — обязательная норма зоны: сверить леджер ДВУМЯ путями на нетривиальном
состоянии. Твой пак трогает возобновление, а это денежный путь.
2. **Ось «дверь против словаря глагола»** — каждое поле и каждый код с ответом, откуда он в
движковом коде. Поле без ответа — либо изобретение, либо находка.
3. **Ось «что увидит пользователь, когда сломается»** — 409 против 503 против 500: различает ли
клиент «пере-реши» от «пошли то же самое ещё раз».
## §6. Записка-план, комплектность, «заявление = команда»
**До правок** заведи запись в зонном журнале с планом. **В конце** — таблица комплектности против §3:
пункт → что сделано → каким ИСПОЛНЕНИЕМ подтверждено. Пункт со словом вместо команды в третьей
колонке считается НЕ сделанным. Любое число отчёта — с командой, которой оно получено.
## §7. Эхо-протокол старта — и он же проверка канала
ДО работы — ≤10 строк: **скоуп · инварианты · не-делать**. Расхождение эха с промтом — первый вопрос.
**Эхо ПОШЛИ МНЕ первым действием**, а не пиши в пустоту: `SendMessage` на имя из §10 (в конце промта). Это двойная
проверка — я вижу твоё понимание скоупа ДО того, как ты начал, а ты убеждаешься, что канал живой,
пока чинить его дёшево.
## §8. Obstacle — обязательная секция
**«Что НЕ удалось и что НЕ проверено»** отдельной секцией. Названный пробел дёшев; необъявленная
ошибка автора — находка, которой нет.
## §9. Канал вопросов и твоё право отказаться
Конфликт промта с кодом или доками — **пинг оркестратору через владельца**, не интерпретация в свою
пользу и не обход.
**У тебя есть право сказать «этого делать не надо» — с аргументом.** Промт писал не тот, кто живёт
в этом коде. Если пункт заказа окажется неверным, вредным или уже исполненным, **скажи это и не
делай** — назвав, чем именно он неверен. Отказ с разбором стоит дороже послушного исполнения плохого
пункта, и сегодня это уже случилось дважды: пункт «воркер решений» я снял эрратой, потому что
исполнитель показал изменившуюся посылку; и мой промт контрактного минора содержал две неверных
посылки, которые нашла его сессия и принесла пингом.
**Аддендумы по ходу доезжают ТОЛЬКО релеем через владельца.** Что не пришло релеем — не заказ.
**Тесты и гейты не подгонять под зелень**: несогласие с гейтом — вопрос, не правка.
## §10. Как со мной связаться — прямой канал
**Впиши себя в `/tmp/textmachine-channel` первым делом** — дописать свой блок в конец, чужие не трогать; ключи и правила в шапке самого файла. Тогда тебя найдут, а не будут искать.
**Мой адрес — там же, не здесь.** Прочитай его — там имя моей сессии и
чем писать (`SendMessage`). Имя в промте я НЕ пишу нарочно: оно протухает, а протухший адрес в
документе выглядит живым и стоит сессии дня.
**Пиши, не копи.** Поводы, по которым молчать хуже: нашёл глупость или ошибку в этом промте (сегодня
их находили в КАЖДОМ моём промте — в одном девять, две из них убили бы работу) · пункт заказа
оказался уже исполненным или вредным · упёрся в развилку, которую я не назвал · нужен факт из чужой
зоны, которую тебе не читать.
**КАНАЛА НЕТНЕ ИСКАТЬ.** Файла нет или имя не отвечает — значит оркестратора сейчас нет, и это
НОРМАЛЬНЫЙ случай, а не авария (файл лежит в `/tmp` именно затем, чтобы умирать вместе с адресом).
Тогда:
1. **НЕ опрашивай сессии подряд** в поисках меня — это шум, который ничего не находит;
2. положи вопрос секцией **«вопросы оркестратору»** в свой отчёт и **продолжай работу**;
3. владелец прочитает отчёт и пере-передаст — он единственный вечный канал.