textmachine/docs/CONTRACT_MINOR_SESSION_PROMPT.md

279 lines
31 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.

# Промт: контрактная сессия, минор 0.4.0 → 0.5.0 — снос отменённой модели и дверь, выведенная из глагола
> **Выдан оркестратором №19, 27.08.2026.** Порядок работ по шву ратифицирован **D39.156**; этот пак —
> пункт (2б) очереди, и он идёт ПОСЛЕ движкового пака, который закончен и заленджен 27.08 (**D39.158**,
> коммит `d1eb8a9`). Это существенно: тело новой двери больше не сочиняется — оно ВЫВОДИТСЯ из словаря
> уже работающего глагола.
## §0. Какая проблема и что решит твой результат
Контракт `docs/architecture/14-api-contract/` — единственный ратифицированный договор между фронтом и
платформой. Сегодня он **лжёт в двух местах и молчит в одном**, и цена лжи не абстрактная.
**1. Канон описывает дверь, которой нет и не будет.** 16.08 владелец отменил пер-термную модель
подписи банка памяти: подписывается ВЕСЬ банк одним «ОК», а пер-термно существует не подпись, а
ПРАВКА термина. Зона платформы снесла свою половину 22.08 — маршрут, хендлер, wire-типы. **Канон свою
половину не снёс**: путь `POST /books/{bookId}/bank/decisions`, глагол `submitBankDecisions` и три
схемы стоят в 0.4.0 как действующие. Следствие сегодня: клиент, сгенерированный по канону, получит
404 на ручке, которую канон объявляет обязательной, а деплой обслуживает на одну ручку меньше, чем
декларирует. Носитель — `PD-370`, вес **major**, зонная половина закрыта, контрактная открыта.
**2. Канон не предупреждает о конверте вне `/v0`.** Он говорит только «Signing in is not part of this
surface» (`openapi.yaml:35-36`), а компаньон описывает поверхность `/auth` в §2.14. Отказ оттуда может
прийти БЕЗ обязательного поля `code`, и сгенерированный по канону клиент об это спотыкается. Носитель
— строка бэклога **203**, пункт **(а)** — ⚠ проверь букву своим грепом, в первой редакции этого промта стояла «(е)», и это была моя ошибка чтения. У строки открыт ещё пункт **(к)** (владение `project_db` в `book.yaml`) — он в этот пак НЕ входит.
**3. Двери для правок банка в контракте нет вовсе.** Движок её теперь имеет: `tmctl bank-apply`
$0-глагол со своим словарём, кодами отказа и отчётом. Платформе нечего объявлять фронту, потому что в
каноне такой поверхности не существует.
**Что решит твой результат.** После пака канон перестаёт обещать несуществующее, начинает
предупреждать о конверте и объявляет дверь правок банка в форме, выведенной из глагола движка, — так
что платформенный пак (пункт 2в очереди) реализует объявленное, а не изобретает своё.
## §1. Зона записи и git
**Твоя зона — РОВНО два файла:** `docs/architecture/14-api-contract/openapi.yaml` (канон) и
`docs/architecture/14-api-contract/README.md` (компаньон). Плюс отчёт — см. §9.
- **Ты НЕ коммитишь.** Дерево готовишь и сдаёшь; лендит оркестратор. Канон — `CLAUDE.md`.
- **`platform/` и `frontend/` не трогать ничем**, даже одной строкой. Почему это названо отдельно —
§4: там лежит ровно одна строка, которую хочется поправить, и правит её оркестратор при лендинге.
- **`frontend/src/api/` — зеркало контракта, оно отстаёт РАТИФИЦИРОВАННО** (зона заморожена D39.147,
сверка `cmp` при разморозке). Версию оно не хардкодит — проверено грепом; трогать нечего.
- В дереве живёт незакоммиченная работа зоны полигона (20 позиций, `eval/` + `START_PROMT.MD`) и
возможны другие параллельные сессии. **Чужих файлов не касаться, `git add -A` не существует.**
## §2. Карта чтения — ≤5 позиций, и она ЗАКОН
Дальше — только по ссылкам отсюда. README проекта, CURRENT-STATE и голову журнала решений читать НЕ
нужно: ратифицированное вложено в тело этого промта.
1. **`docs/architecture/14-api-contract/README.md`** — компаньон, 1414 строк. Он и есть карта канона:
читается целиком, объясняет, ЗАЧЕМ канон устроен так. Начни с него, не с YAML.
2. **`openapi.yaml`, только нужные места** — 2592 строки, целиком НЕ читать. Отправные точки в §3.
3. **Словарь глагола — в КОДЕ, и он первичен:** `backend/internal/membank/decisions.go`, комментарий
к типу `Decision` (строки 55120). Он написан развёрнуто НАРОЧНО и прямо предупреждает, что
короткий лозунг «входной словарь = выходной» про него НЕВЕРЕН. Это твой источник тела двери.
4. **`backend/internal/pipeline/bankdecisions.go`** — форма отчёта (`BankDecisionsReport`) и шесть
исходов; `backend/cmd/tmctl/main.go:69-75` — полоса отказов 1019.
5. **Ратифицированное, что бьёт всё остальное, вложено сюда** (§3.0) — грепать D-лог не требуется.
## §3. Состав пака
### §3.0. Ратифицированное, из чего исходишь (не пере-открывать, конфликт — пингом)
- **D39.144 (16.08, слово владельца):** подписывается ВЕСЬ банк ОДНИМ «ОК». «Пер-термная подпись —
сотни кликов — НЕ модель продукта». Пер-термно существует ПРАВКА термина.
- **D39.158 (27.08):** стоп банка — ФЛАЖОК, который движок чтит сам; «ОК» = возобновление. Дверь
правок — `tmctl bank-apply`: проекция `--dry-run`, всё-или-ничего, байтовый no-op идемпотентен.
- **D39.156 п.6:** платформа НЕ пере-реализует движковый закон у себя.
- **Полоса отказов [10,19]** — ратифицированная: 10 конфиг · 11 источник · 12 лок · 13 схема ·
**14 решения отклонены (законный отказ, пользователь ПЕРЕ-решает)** · **15 write_incomplete
(документ принят, запись не довершена — слать ТОТ ЖЕ документ)** · 19 без имени.
- **Версионирование канона:** semver; пока мажор `0`, различающийся МИНОР несёт ломающие изменения по
замыслу. Клиент обязан игнорировать неизвестные поля и терпеть неизвестные enum-значения.
### §3.1. Снести отменённую модель — делай РОВНО так
Носители пере-сняты мной грепом 27.08, числа в строке `PD-370` («~17 мест + ~8») — ОЦЕНКА, не замер;
доверяй своему грепу, не строке. **Канон, 9 позиций:** `469` (путь) · `474` (`operationId`) · `495`
(запрос) · `501` (ответ) · `1830` (проза, ссылающаяся на путь) · `1939` (`BankDecision`) · `1980`
(`BankDecisionsRequest`) · `1990` (ссылка на элемент) · `1992` (`BankDecisionsResult`).
**Компаньон, 5 позиций:** `124` · `674` · `685` · `724` · `1173`.
**`674` — уже зачёркнутая строка таблицы** («~~`POST /bank/decisions`~~»), а не действующее
описание: прочти её целиком прежде, чем править, — там может стоять запись, которую снос обязан
СОХРАНИТЬ как историю, а не стереть.
**Свой греп обязателен**: мой шаблон ловил четыре имени, и позиция, названная иначе, в него не попала
бы. Пере-проверь ещё и по `dst`, `UnknownTermError`, `BankPage`.
### §3.2. Объявить дверь правок банка — реши САМ, аргументируй
Тело выводится из словаря глагола (позиция 3 карты чтения). **Что словарь говорит буквально:**
- Два действия: `approve` и `decline`. Больше нет.
- Идентичность термина — ЛИБО стабильный `id` из читаемого банка, ЛИБО кортеж
`src`/`sense`/`since_chapter`/`until_chapter`, из которого id выводится. **Взаимоисключающе**, и
причина названа в коде: принять оба значит позволить назвать два разных термина одним решением.
-**Кортеж — это НЕ второй способ назвать существующее. Это форма, которой терм ДОБАВЛЯЕТСЯ**
(`decisions.go:94-96`: «the form in which a term the bank does not have yet is added»).
**Неизвестный кортеж — НЕ ошибка.** Канон 0.4.0 делал ровно наоборот — отвечал `unknown`
(`openapi.yaml:486`), — и дверь, спроектированная по той же логике, убьёт добавление терма, то есть
половину продуктовой правки. Это место, где ошибиться легче всего; ошибка стоит целой ветки.
- `dst` — ОБЯЗАТЕЛЕН при `approve` и ЗАПРЕЩЁН при `decline`.
- `kind` — отсутствие значит «не решено», а не «сбросить».
- `note` — принимается и **НЕ публикуется**; отсутствие значит «не решено». Опустошить его дверью
нельзя, только заменить другими словами — узость названа в коде, не прячь её.
- `aliases` — публикуются и **НЕ принимаются**, намеренно.
- `gender` / `speech` / `decl` — ни то, ни другое: сид-онли, производителя нет (строка **210**), и
дверь на них была бы дверью на неработающую ось.
-**Названная узость v1, и у неё ДВЕ половины — обе в канон:** ОКНО ГЛАВ термина одним решением не
правится. `approve` нового кортежа **добавляет вторую строку и оставляет старую стоять**, а
`decline` **снимает ВСЕ окна поверхности** (`decisions.go:85-87`). Расширение или сдвиг окна —
операция в два вызова. Это ратифицированное свойство, а не дефект: предупреждением, не умолчанием.
- **Конверт документа — тоже словарь, и его нельзя изобретать заново:** `decisions_version` со
значением `tm-bank-decisions-v1` (`decisions.go:47`) · `book_id` **обязателен и сверяется с книгой**
(`:142`), потому что решения — единственный вход, несущий слова пользователя в чужую книгу незаметно
для даунстрима · **неизвестное поле — громкий отказ** (`DisallowUnknownFields`, `:133`), зеркало
п.4 закона шва.
- **Два потолка, оба объявляются:** **1 МиБ** на чтение документа и **5000 решений на один акт**
(`bankdecisions.go:647`, `maxDecisions`; за ним — отказ КЛАССОМ 14 с текстом «split it», потому что
дальше вызов перестаёт влезать в таймаут вызывающего и умирает на каждом ретрае). Опубликованная
дверь без объявленных лимитов — это сгенерированный клиент, шлющий двадцать тысяч решений вслепую.
- Правка СИД-терма этой дверью ОТКЛОНЯЕТСЯ по имени (`decisions.go:359`): это перенос базового
снапшота и пере-оплата черновой волны — другая дверь, ещё не спроектированная (строка **192**).
⛔ **Если отчёт публикуется на проводе — поле `signature` едет ТОЛЬКО со своим контрактом, и контракт
этот запретительный.** `SignatureState` (`bankdecisions.go:88-118`) — **ИНФОРМАЦИОННОЕ**, ровно как его
понизил D39.144: «it counts, it does not gate». Код говорит прямо: оно **НЕ отвечает на вопрос
«погаснет ли стоп» ни в одну сторону**, `undecided: 0` **ничего не обещает** о поведении следующего
прогона, а «решать поверхности — ПРАВО владельца, а не требование стопа». Есть и четвёртое поле,
`unreadable`, которое означает «эти два числа для этого вызова не значат НИЧЕГО».
Почему это в промте отдельным знаком: опубликуй `undecided` без контракта — и экран подписи при
разморозке фронта выучит из канона, что его работа — довести число до нуля. Это и есть отменённая
пер-термная модель, вернувшаяся через чёрный ход тем же минором, который её сносит. **Публикуешь
поле — публикуешь и запрет; не готов опубликовать запрет — не публикуй поле.**
**Твоя свобода и твой аргумент:** форма HTTP-поверхности (один `POST` с документом целиком против
чего-либо иного), имена схем, как ложатся коды 1019 на статусы HTTP, как выражается `--dry-run`.
**Решай сам и обоснуй в отчёте.** Совет, опровергаемый аргументом: код **14** — это НЕ ошибка сервера,
а законный отказ, по которому ПОЛЬЗОВАТЕЛЬ пере-решает; код **15** несёт указание «слать тот же
документ», и оба заслуживают различимости на проводе, а не общего `400`.
**Дверь объявляется, но платформой ещё НЕ обслуживается** — её смонтирует пак (2в). Прецедент того,
как контракт честно объявляет непостроенное, в каноне уже есть: `Capabilities.ExportFormats`, пустой
список = «здесь не построено», и это названо честным ответом. **Машиночитаемый признак «здесь не построено» ОБЯЗАТЕЛЕН — свободна только его ФОРМА.** Без него
пак объявит голую необслуживаемую дверь и воспроизведёт `PD-370` тем же коммитом, которым его
закрывает: «канон объявляет — деплой не обслуживает» и есть содержание той строки. Разберись, годится
ли `Capabilities` для двери; если нет — назови почему и предложи замену, но не отсутствие.
### §3.2-бис. Счётчики упразднённой модели — реши САМ, но решить ОБЯЗАН
Канон объявляет `pending_decisions` и `complete` — «how much of the bank a person has touched»
(`openapi.yaml:442-447`). Это счётчики платформенной таблицы `bank_decisions`, у которой **write-путь
снесён 22.08**: колонки были write-only, кормившая их дверь мертва, а новая (§3.2) пишет ДВИЖКОВЫЕ
файлы, не эту таблицу. Значит поля навсегда нули.
⚠ Мой греп §3.1 их НЕ ловит — имена другие, — и твой по тем же четырём именам не поймает тоже.
**Греп-форма, которая их вытаскивает: `bank_decisions` (с подчёркиванием).** Ею же находится носитель
компаньона **:358** («решение живёт отдельной таблицей `bank_decisions`») и тег `openapi.yaml:127`
(«Memory bank and term signing»).
**Заказ: реши судьбу обоих полей — снести · пере-определить на новую дверь · пометить — и обоснуй.**
⚠ Проза рядом (`:442-447`) при этом УЖЕ несёт ратифицированную модель («Signing the bank is ONE act
over the whole of it»): она правильная, и сносить её вместе со счётчиками было бы потерей.
### §3.3. Предупредить о конверте вне `/v0` — делай РОВНО так
Строка **203**, пункт **(а)** (не (е) — см. §0). Канон (`openapi.yaml:35-36`) говорит только «Signing in is not part of this
surface». Допиши предупреждение: отказ от поверхности вне версионного префикса может прийти БЕЗ
обязательного `code`, и клиент, сгенерированный по канону, обязан это терпеть. Поверхность описана в
компаньоне §2.14 — сошлись, не пересказывай (один носитель на факт).
### §3.4. Чего в этом паке НЕТ
**Сквозная полоса прогресса (строка 200) в этот минор НЕ входит — решение оркестратора 27.08,
подлежит слову владельца.** Причина механическая, и её надо знать: см. §4. Если владелец решит иначе,
получишь аддендум; сам не бери.
## §4. Развилка версии — прочти ДО того, как поднимешь номер
Гейт платформы `TestTheAnnouncedContractVersionIsTheOneTheCanonRatified`
(`platform/internal/gates/contract_test.go:14`) читает ТВОЙ канон и требует, чтобы константа
`platform/internal/httpapi/capabilities.go:13` (`ContractVersion = "0.4.0"`) ему равнялась. Гейт
заведён не зря: однажды деплой обслуживал `0.3.0`, когда каноном был `0.4.0`.
**Следствия, каждое проверено мной командой:**
1. В ту секунду, когда ты напишешь `version: 0.5.0`, батарея зоны платформы КРАСНЕЕТ. Это ожидаемо и
это не твоя ошибка. **Константу правит оркестратор при лендинге, одной строкой, тем же коммитом.**
Ты в `platform/` не пишешь.
2. Поэтому канон и деплой обязаны совпадать В МОМЕНТ ЛЕНДИНГА. Снос двери этому не мешает: платформа
свою половину уже снесла 22.08, после твоего сноса они СОВПАДУТ точно. Объявление необслуживаемой
двери — тоже, если оно честное (§3.2).
3. **А смена семантики ПРОГРЕССА — мешает.** Канон сегодня говорит: «When a stop is cleared the
counter starts again from zero» (`openapi.yaml:1293`). Владелец 20.08 назвал это дефектом и
потребовал ОДНУ долю на весь прогон, считаемую СЕРВЕРОМ. Но сервер её не считает и не будет считать
до пака (2в). Написать новую семантику в канон и поднять константу значит объявить `0.5.0` у
деплоя, который обслуживает прогресс по-старому, — ровно та ложь, против которой гейт и стоит.
**Мой разбор, который владелец может отменить:** прежнее «одним куском» покупалось тем, что иначе
фронт перегенерируется дважды. **Фронт ЗАМОРОЖЕН**, и его зеркало отстаёт ратифицированно — при
разморозке он перегенерируется ОДИН раз независимо от того, сколько миноров прошло. Значит цена
разделения сегодня равна нулю, а цена объединения — деплой, врущий о своей версии. Поэтому прогресс
едет с паком (2в) отдельным минором.
**Если по ходу работы найдёшь, что этот разбор неверен — это пинг, а не молчаливое отступление.**
## §5. Самопроверка ИСПОЛНЕНИЕМ — обязательна, и «перечитал сам» её не удовлетворяет
Сессии регулярно ошибаются, и самоотчёт «проверено» без исполнения регулярно оказывается ложным.
**Названный механизм, три пункта, каждый даёт проверяемый артефакт:**
1. **Канон обязан парситься.** Прогони валидатор OpenAPI (любой доступный: `python3 -c "import yaml,
sys; yaml.safe_load(open('...'))"` — минимум; если в окружении есть настоящий линтер схемы —
лучше). **Артефакт: команда и её вывод в отчёте.** YAML, который не грузится, — это канон,
который не читает никто.
2. **Ни одной висячей ссылки после сноса.** Схема, на которую больше никто не ссылается, и ссылка на
снесённую схему — оба дефекта. Собери проверку сам (греп `$ref` против списка `components.schemas`
годится) и **приложи её вывод**, а не утверждение. ⚠ **Туда же — ДУБЛИ КЛЮЧЕЙ:** `yaml.safe_load`
схлопывает их молча по правилу last-wins (проверено: `a: 1\na: 2` даёт `{'a': 2}` без единого
слова), а это реальный класс дефекта канона после правок — пятистрочный loader-хук их ловит.
3. **Гейт платформы обязан покраснеть ПРЕДСКАЗУЕМО.** Прогони его на копии дерева и покажи, что он
краснеет ИМЕННО на несовпадении версий и ИМЕННО с ожидаемым текстом. ⚠ **Копию снимай ВМЕСТЕ с
каноном:** `cp -a --parents platform docs/architecture/14-api-contract <куда>/`. Голая `cp -a
platform` даёт постоянный красный того же теста по другой причине, и вердикт будет ложным — норма
зоны платформы, `ENGINEERING_STANDARDS` §3 п.3.
**Субагенты РАЗРЕШЕНЫ явно** (харнесс по умолчанию их не берёт, и без этого разрешения запрет тихо
побеждает): бери отдельного агента-опровергателя на §3.2 — он получает твоё предложение двери и
словарь из кода, мандат «найди, где предложенное тело расходится со словарём глагола». Его находки —
в отчёт, включая те, что ты отверг, и с причиной.
⚠ **Соразмеряй веер заранее:** 24 агента, не больше. Панель на полтора десятка агентов на этой
машине убивает сама себя, и это уже стоило проекту находок.
## §6. Оси ревью — 13, сессия вправе заменить с аргументом
1. **Ось «клиент, сгенерированный по канону».** Пройди канон глазами генератора: обязательные поля,
которых сервер не шлёт; enum, который вырастет; ссылки в никуда. Это ось, на которой канон уже
ловили дважды (`0.3.0`/`0.4.0` и конверт `/auth`).
2. **Ось «словарь двери против словаря глагола».** Каждое поле твоей схемы — с ответом, откуда оно в
`decisions.go` и почему принимается/публикуется именно так. Поле без ответа — либо изобретение,
либо находка.
3. **Ось «что читатель канона узнает о том, чего нет».** Необслуживаемая дверь, узость окна глав,
отклонение сид-терма — все три обязаны быть В КАНОНЕ явными, а не выводимыми.
## §7. Записка-план и комплектность
**До правок** выложи записку-план: что меняешь, где, чем проверишь. **В конце** — механическая сверка
комплектности против §3 таблицей: пункт заказа → что сделано → каким исполнением подтверждено. Пункт,
по которому в колонке «исполнение» стоит слово, а не команда, считается НЕ сделанным.
## §8. «Заявление = команда»
Любое число и любая категорика в отчёте — с командой, которой они получены. «Снёс девять мест» без
`grep -c` — не факт. Приёмка пере-считает выборочно, и расхождение будет стоить дороже, чем честное
«не мерил».
## §9. Эхо-протокол старта
ДО работы — ≤10 строк: **скоуп** (что делаешь) · **инварианты** (что не сдвинется) · **не-делать**
(что явно вне пака). Если эхо расходится с этим промтом — расхождение и есть первый вопрос.
## §10. Obstacle — обязательная секция отчёта
**«Что НЕ удалось и что НЕ проверено»** — отдельной секцией, не россыпью. Названный пробел стоит
дёшево; необъявленная ошибка автора — это находка, которой нет, и она всплывёт у приёмки дороже.
## §11. Канал вопросов
Конфликт промта с кодом или доками — **пинг оркестратору через владельца, НЕ интерпретация в свою
пользу и НЕ обход**. Отчёт и вопросы пиши в `docs/PROGRESS.md`? — **нет**: у контрактной сессии зонного
журнала нет, поэтому отчёт кладёшь **отдельным файлом `docs/CONTRACT_MINOR_REPORT.md`**, а оркестратор
переносит существенное в журнал и в ноту при лендинге.
**Тесты и гейты не подгонять под зелень.** Гейт платформы обязан покраснеть — это ожидаемое
поведение, описанное в §4, а не повод его править. Несогласие с гейтом — вопрос, не правка.