# Промт: контрактная сессия, минор 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` (строки 55–120). Он написан развёрнуто НАРОЧНО и прямо предупреждает, что короткий лозунг «входной словарь = выходной» про него НЕВЕРЕН. Это твой источник тела двери. 4. **`backend/internal/pipeline/bankdecisions.go`** — форма отчёта (`BankDecisionsReport`) и шесть исходов; `backend/cmd/tmctl/main.go:69-75` — полоса отказов 10–19. 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` с документом целиком против чего-либо иного), имена схем, как ложатся коды 10–19 на статусы 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 — он получает твоё предложение двери и словарь из кода, мандат «найди, где предложенное тело расходится со словарём глагола». Его находки — в отчёт, включая те, что ты отверг, и с причиной. ⚠ **Соразмеряй веер заранее:** 2–4 агента, не больше. Панель на полтора десятка агентов на этой машине убивает сама себя, и это уже стоило проекту находок. ## §6. Оси ревью — 1–3, сессия вправе заменить с аргументом 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, а не повод его править. Несогласие с гейтом — вопрос, не правка.