31 KiB
Промт: контрактная сессия, минор 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 и голову журнала решений читать НЕ нужно: ратифицированное вложено в тело этого промта.
docs/architecture/14-api-contract/README.md— компаньон, 1414 строк. Он и есть карта канона: читается целиком, объясняет, ЗАЧЕМ канон устроен так. Начни с него, не с YAML.openapi.yaml, только нужные места — 2592 строки, целиком НЕ читать. Отправные точки в §3.- Словарь глагола — в КОДЕ, и он первичен:
backend/internal/membank/decisions.go, комментарий к типуDecision(строки 55–120). Он написан развёрнуто НАРОЧНО и прямо предупреждает, что короткий лозунг «входной словарь = выходной» про него НЕВЕРЕН. Это твой источник тела двери. backend/internal/pipeline/bankdecisions.go— форма отчёта (BankDecisionsReport) и шесть исходов;backend/cmd/tmctl/main.go:69-75— полоса отказов 10–19.- Ратифицированное, что бьёт всё остальное, вложено сюда (§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.
Следствия, каждое проверено мной командой:
- В ту секунду, когда ты напишешь
version: 0.5.0, батарея зоны платформы КРАСНЕЕТ. Это ожидаемо и это не твоя ошибка. Константу правит оркестратор при лендинге, одной строкой, тем же коммитом. Ты вplatform/не пишешь. - Поэтому канон и деплой обязаны совпадать В МОМЕНТ ЛЕНДИНГА. Снос двери этому не мешает: платформа свою половину уже снесла 22.08, после твоего сноса они СОВПАДУТ точно. Объявление необслуживаемой двери — тоже, если оно честное (§3.2).
- А смена семантики ПРОГРЕССА — мешает. Канон сегодня говорит: «When a stop is cleared the
counter starts again from zero» (
openapi.yaml:1293). Владелец 20.08 назвал это дефектом и потребовал ОДНУ долю на весь прогон, считаемую СЕРВЕРОМ. Но сервер её не считает и не будет считать до пака (2в). Написать новую семантику в канон и поднять константу значит объявить0.5.0у деплоя, который обслуживает прогресс по-старому, — ровно та ложь, против которой гейт и стоит.
Мой разбор, который владелец может отменить: прежнее «одним куском» покупалось тем, что иначе фронт перегенерируется дважды. Фронт ЗАМОРОЖЕН, и его зеркало отстаёт ратифицированно — при разморозке он перегенерируется ОДИН раз независимо от того, сколько миноров прошло. Значит цена разделения сегодня равна нулю, а цена объединения — деплой, врущий о своей версии. Поэтому прогресс едет с паком (2в) отдельным минором.
Если по ходу работы найдёшь, что этот разбор неверен — это пинг, а не молчаливое отступление.
§5. Самопроверка ИСПОЛНЕНИЕМ — обязательна, и «перечитал сам» её не удовлетворяет
Сессии регулярно ошибаются, и самоотчёт «проверено» без исполнения регулярно оказывается ложным. Названный механизм, три пункта, каждый даёт проверяемый артефакт:
- Канон обязан парситься. Прогони валидатор OpenAPI (любой доступный:
python3 -c "import yaml, sys; yaml.safe_load(open('...'))"— минимум; если в окружении есть настоящий линтер схемы — лучше). Артефакт: команда и её вывод в отчёте. YAML, который не грузится, — это канон, который не читает никто. - Ни одной висячей ссылки после сноса. Схема, на которую больше никто не ссылается, и ссылка на
снесённую схему — оба дефекта. Собери проверку сам (греп
$refпротив спискаcomponents.schemasгодится) и приложи её вывод, а не утверждение. ⚠ Туда же — ДУБЛИ КЛЮЧЕЙ:yaml.safe_loadсхлопывает их молча по правилу last-wins (проверено:a: 1\na: 2даёт{'a': 2}без единого слова), а это реальный класс дефекта канона после правок — пятистрочный loader-хук их ловит. - Гейт платформы обязан покраснеть ПРЕДСКАЗУЕМО. Прогони его на копии дерева и покажи, что он
краснеет ИМЕННО на несовпадении версий и ИМЕННО с ожидаемым текстом. ⚠ Копию снимай ВМЕСТЕ с
каноном:
cp -a --parents platform docs/architecture/14-api-contract <куда>/. Голаяcp -a platformдаёт постоянный красный того же теста по другой причине, и вердикт будет ложным — норма зоны платформы,ENGINEERING_STANDARDS§3 п.3.
Субагенты РАЗРЕШЕНЫ явно (харнесс по умолчанию их не берёт, и без этого разрешения запрет тихо побеждает): бери отдельного агента-опровергателя на §3.2 — он получает твоё предложение двери и словарь из кода, мандат «найди, где предложенное тело расходится со словарём глагола». Его находки — в отчёт, включая те, что ты отверг, и с причиной.
⚠ Соразмеряй веер заранее: 2–4 агента, не больше. Панель на полтора десятка агентов на этой машине убивает сама себя, и это уже стоило проекту находок.
§6. Оси ревью — 1–3, сессия вправе заменить с аргументом
- Ось «клиент, сгенерированный по канону». Пройди канон глазами генератора: обязательные поля,
которых сервер не шлёт; enum, который вырастет; ссылки в никуда. Это ось, на которой канон уже
ловили дважды (
0.3.0/0.4.0и конверт/auth). - Ось «словарь двери против словаря глагола». Каждое поле твоей схемы — с ответом, откуда оно в
decisions.goи почему принимается/публикуется именно так. Поле без ответа — либо изобретение, либо находка. - Ось «что читатель канона узнает о том, чего нет». Необслуживаемая дверь, узость окна глав, отклонение сид-терма — все три обязаны быть В КАНОНЕ явными, а не выводимыми.
§7. Записка-план и комплектность
До правок выложи записку-план: что меняешь, где, чем проверишь. В конце — механическая сверка комплектности против §3 таблицей: пункт заказа → что сделано → каким исполнением подтверждено. Пункт, по которому в колонке «исполнение» стоит слово, а не команда, считается НЕ сделанным.
§8. «Заявление = команда»
Любое число и любая категорика в отчёте — с командой, которой они получены. «Снёс девять мест» без
grep -c — не факт. Приёмка пере-считает выборочно, и расхождение будет стоить дороже, чем честное
«не мерил».
§9. Эхо-протокол старта
ДО работы — ≤10 строк: скоуп (что делаешь) · инварианты (что не сдвинется) · не-делать (что явно вне пака). Если эхо расходится с этим промтом — расхождение и есть первый вопрос.
§10. Obstacle — обязательная секция отчёта
«Что НЕ удалось и что НЕ проверено» — отдельной секцией, не россыпью. Названный пробел стоит дёшево; необъявленная ошибка автора — это находка, которой нет, и она всплывёт у приёмки дороже.
§11. Канал вопросов
Конфликт промта с кодом или доками — пинг оркестратору через владельца, НЕ интерпретация в свою
пользу и НЕ обход. Отчёт и вопросы пиши в docs/PROGRESS.md? — нет: у контрактной сессии зонного
журнала нет, поэтому отчёт кладёшь отдельным файлом docs/CONTRACT_MINOR_REPORT.md, а оркестратор
переносит существенное в журнал и в ноту при лендинге.
⚠ Тесты и гейты не подгонять под зелень. Гейт платформы обязан покраснеть — это ожидаемое поведение, описанное в §4, а не повод его править. Несогласие с гейтом — вопрос, не правка.