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