textmachine/docs/CONTRACT_MINOR_SESSION_PROMPT.md

31 KiB
Raw Blame History

Промт: контрактная сессия, минор 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, а не повод его править. Несогласие с гейтом — вопрос, не правка.