Remove the applied S3 contract patch: its blocks are in the canon companion and the file declared itself temporary
This commit is contained in:
parent
dde6d389a9
commit
483ea6eb12
1 changed files with 0 additions and 165 deletions
|
|
@ -1,165 +0,0 @@
|
|||
# Передача оркестратору: контракт 0.2.0 — что применить в каноне
|
||||
|
||||
> **Это не носитель состояния, а патч.** Файл существует ровно до лендинга S3: оркестратор
|
||||
> переносит правку спеки и вписывает блоки §2–§4 в компаньон канона, после чего файл удаляется.
|
||||
> Сессия фронта канон (`docs/architecture/14-api-contract/`) не правит — это чужая зона.
|
||||
>
|
||||
> Написано фронт-сессией S3, 08.08.2026.
|
||||
|
||||
## 1. Спека: зонная копия впереди канона
|
||||
|
||||
| Что | Где |
|
||||
|---|---|
|
||||
| Правленая копия | `frontend/docs/api-contract/openapi.yaml` — **version `0.2.0`** |
|
||||
| Канон | `docs/architecture/14-api-contract/openapi.yaml` — всё ещё `0.1.0-draft` |
|
||||
|
||||
Байт-сверка (пункт Д промта) **даёт расхождение, и это ожидаемо**: правки заказаны промтом S3,
|
||||
а канон правит оркестратор. Диффом:
|
||||
|
||||
```
|
||||
diff -u docs/architecture/14-api-contract/openapi.yaml frontend/docs/api-contract/openapi.yaml
|
||||
```
|
||||
|
||||
После пере-ратификации копии обязаны снова стать байт-равными; шапка `info` в зонной копии это
|
||||
прямо говорит, чтобы следующая сессия не приняла временное расхождение за дефект.
|
||||
|
||||
**Что сделано по списку промта** (каждый пункт — в спеке, а не в намерении):
|
||||
|
||||
| Пункт | Исполнено |
|
||||
|---|---|
|
||||
| А-нуль | `version: 0.2.0`. ⚠ **Отступление, называю вслух:** промт говорил «твоё там одно — бампнуть version», но в шапке стояла фраза «version всё ещё `-draft` — S3 бампнет первым действием», которая после бампа становилась ложной. Заменил её на правду о том, что копия сейчас впереди канона. Баннер «DRAFT … not a ratified contract» НЕ возвращён |
|
||||
| А.1 К-1 | статусы не тронуты |
|
||||
| А.2 К-8 | `BookStatus` += `paused`; в описании прямо: стоп по потолку это `paused`, не `failed` |
|
||||
| А.3 К-5 | `Progress.eta_seconds`, опциональное и nullable |
|
||||
| А.4 К-3 | `Chapter.number` и `Chapter.heading` — оба обязательны и оба допускают `null`; запрет синтеза «Глава {n}» записан нормативом. Фикстуры проверены и правлены (см. §5) |
|
||||
| Б.5 К-4 | ревизия пер-книжная; правила «монотонна в скоупе», «отбрасывание — обязанность клиента», «докачка `>= R`, не `>`», «полная замена ⇒ `resync_required`» — прозой в `Revision` |
|
||||
| Б.6 К-7 | `?limit=&cursor=` и `next_cursor` на ВСЕХ пяти списочных ответах; дефолты 5000/1000/500; курсор привязан к структурной эпохе, отклонение протухшего — MUST сервера, ответ `400` |
|
||||
| Б.7 К-12 | `202` + обязательный `Location`; `Retry-After` объявлен ЯВНО заголовком 200-ответа со ссылкой на то, почему RFC 9110 сам этого не даёт |
|
||||
| Б.8 CSRF | `X-TM-Client` в описании `sessionCookie`: несёт ПРИСУТСТВИЕ, значение любое, обязателен и на same-origin |
|
||||
| Б.9 | `PausedReason` = `credit_exhausted`, `Run.paused_reason` обязателен и nullable |
|
||||
| В.10–11 | `GET /usage` + схема `Usage`. ⚠ Форма из промта записана в 3.0-синтаксисе (`nullable: true`), в 3.1 он невалиден — выражено через `oneOf` с `type: 'null'`, как везде в этой спеке |
|
||||
| В-бис | в `openapi.yaml` НЕ потащено, как и велено; текст для компаньона — §2 ниже |
|
||||
| Г.12–15 | пер-главное чтение юнитов с запретом пер-книжной ручки · экспорт-артефакт с явным запретом сборки текста на клиенте · право сервера склеивать кадры + обязанность клиента терпеть скачки · `maxLength` у `heading` |
|
||||
| В-трис.16–18 | `GET /books/{bookId}/run-options` → `CeilingBounds`; `RunRequest.ceiling_chapters`; `Run.ceiling_chapters`. Обоснование формы — §3 |
|
||||
| В-кватер | раздел «Transport» в шапке: тот же origin как факт, CORS-слоя нет, кросс-origin не проектируется. В коде — прокси dev-сервера (`vite.config.ts`, включается `TM_PLATFORM`) |
|
||||
|
||||
## 2. Для компаньона: поверхность входа (пункт 15-бис)
|
||||
|
||||
Вставить в компаньон канона (`docs/architecture/14-api-contract/README.md`) новым разделом.
|
||||
|
||||
> ### 2.14. Поверхность входа — вне версионного префикса
|
||||
>
|
||||
> Платформа построила вход через OIDC (P1). Четыре ручки живут ВНЕ `/v0`, как `/healthz`, потому
|
||||
> что это механика сессии, а не контрактная поверхность, и в `openapi.yaml` они намеренно не
|
||||
> тащатся (решение оркестратора как владельца контракта).
|
||||
>
|
||||
> | Ручка | Метод | Что делает |
|
||||
> |---|---|---|
|
||||
> | `/auth/login` | GET | начинает вход, редиректит к провайдеру; принимает `?return_to=<путь на этом сайте>` |
|
||||
> | `/auth/callback` | GET | завершает вход, ставит сессионную куку, редиректит на `return_to` либо на дефолт |
|
||||
> | `/auth/logout` | POST | завершает ЭТУ сессию |
|
||||
> | `/auth/logout-all` | POST | завершает ВСЕ сессии пользователя |
|
||||
>
|
||||
> Клиенту нужно знать три вещи, и только три. Куда вести на вход. Что `return_to` принимает
|
||||
> **только путь этого сайта** — чужой сервер молча заменит на дефолт, открытый редирект закрыт.
|
||||
> И что обе `POST`-ручки лежат на cookie-пути, то есть требуют `X-TM-Client` (§2.8 спеки).
|
||||
> Отказ входа — `problem+json`, как везде; различать причины отказа клиент не может и не должен.
|
||||
|
||||
## 3. Для компаньона: управляемый потолок прогона — форма и обоснование
|
||||
|
||||
Единственное место списка правок, где решение было за сессией. Форма и почему именно она:
|
||||
|
||||
> ### 2.15. Потолок прогона — ◆ предложено фронтом
|
||||
>
|
||||
> Ратифицировано владельцем 07.08: шкала в интерфейсе от минимума до максимума, ноль выбрать
|
||||
> нельзя, единица — ГЛАВЫ, максимум — доступный остаток (баланс минус холды), потолок принадлежит
|
||||
> ПРОГОНУ. Ручки, отдающей границы шкалы, в контракте не было; она объявлена этой правкой.
|
||||
>
|
||||
> **Отдельный ресурс `GET /books/{bookId}/run-options`, а не поле карточки книги.** Два довода, и
|
||||
> оба про правду на экране. Максимум зависит от АККАУНТА — баланс минус открытые холды — и
|
||||
> двигается, когда книга не меняется: холд, взятый под другую книгу, опускает остаток. Карточка
|
||||
> книги кэшируется и переиспользуется библиотекой, то есть назвала бы максимум, которого уже нет,
|
||||
> ровно в тот момент, когда пользователь двигает ползунок. Второй довод дешевле, но тоже
|
||||
> настоящий: граница нужна один раз, перед стартом, а поле на карточке заставило бы КАЖДОЕ чтение
|
||||
> библиотеки нести состояние счёта.
|
||||
>
|
||||
> **Три числа, а не два.** `min_chapters` объясняет себя единицей — одна глава. `max_chapters`
|
||||
> отдаётся уже подрезанным и по остатку, и по тому, сколько в книге осталось непереведённого:
|
||||
> клиенту запрещено подрезать второй раз, иначе правило живёт в двух местах и расходится.
|
||||
> `default_chapters` отдаёт платформа, потому что выбор предустановленного значения — это
|
||||
> продуктовая политика («потратить всё» ↔ «одна глава»), а не презентация; клиент, выбравший его
|
||||
> сам, принял бы это решение молча.
|
||||
>
|
||||
> **`max_chapters: 0` — легальный ответ** и значит «прогон начать нельзя вовсе». Клиент показывает
|
||||
> исчерпанное состояние вместо шкалы. Это единственный случай, когда `default_chapters` тоже `0`.
|
||||
>
|
||||
> **Пересчёта «главы → деньги» на проводе нет ни в каком виде** (D39.84). Он живёт на платформе по
|
||||
> оценке движка.
|
||||
>
|
||||
> **`ceiling_chapters` — обязательное поле старта и поле прогона.** Обязательное, потому что прогон
|
||||
> без объявленного потолка тратит мимо той границы, которую человек вправе поставить ДО, а не
|
||||
> узнавать после. Поле на `Run` — чтобы перезагруженный экран мог назвать выбранный колпак, а не
|
||||
> забыть его.
|
||||
>
|
||||
> **409 на старте** отвечает и на «потолок больше не помещается»: границы читаются отдельным
|
||||
> вызовом и могут сдвинуться между чтением и стартом.
|
||||
|
||||
## 4. К-таблица компаньона: статусы после этой правки
|
||||
|
||||
Заменить таблицу §4 компаньона на эту (изменения — колонка «Статус»):
|
||||
|
||||
| # | Кому | Статус после 0.2.0 |
|
||||
|---|---|---|
|
||||
| К-1 | владелец | **ЗАКРЫТ** (D39.100) — десять статусов приняты; одиннадцатым добавлен `paused` по К-8 |
|
||||
| К-2 | автор контракта + бэкенд | **ОТКРЫТ** — кто ПОРОЖДАЕТ титул, по-прежнему не решено; К-3 закрыл продуктовую половину (метка из данных), механическая осталась |
|
||||
| К-3 | владелец | **ЗАКРЫТ** (D39.100) — метка из данных книги; спека запрещает синтез «Глава {n}», `number` и `heading` оба nullable |
|
||||
| К-4 | платформа | **ЗАКРЫТ** — счётчик пер-книжный, одно число на все книго-скоупные чтения и кадр потока; выражено прозой в `Revision` |
|
||||
| К-5 | владелец | **ЗАКРЫТ** (D39.100) — `Progress.eta_seconds`, опциональное |
|
||||
| К-6 | владелец | **ОТКРЫТ** — принцип принят (две ступени), карта «причина → фраза» ждёт В-3 |
|
||||
| К-7 | платформа | **ЗАКРЫТ** — keyset-курсор на всех списках, дефолты 5000/1000/500, привязка к структурной эпохе |
|
||||
| К-8 | владелец | **ЗАКРЫТ** (D39.100) — `paused` + `Run.paused_reason: credit_exhausted` |
|
||||
| К-9 | владелец + автор контракта | **ОТКРЫТ** — отказ прескрина по-прежнему не выразим ни одним статусом |
|
||||
| К-10 | автор контракта + бэкенд | **ОТКРЫТ** — `Chapter.units_done` так и не разведён по фазам; дерево глав показывает ноль всю черновую волну |
|
||||
| К-11 | автор контракта | **ОТКРЫТ**, и половина стала фактом: `Note` по-прежнему не требует ни `chapter_id`, ни `unit_id`; сужение `BankDecision` для экрана — работа S5 |
|
||||
| К-12 | платформа | **ЗАКРЫТ** — опрос, `202` + `Location`, `Retry-After` объявлен явно |
|
||||
| **К-13** | **владелец + платформа** | **НОВЫЙ.** Достижение СОБСТВЕННОГО потолка прогона (`ceiling_chapters`) и исчерпание кредита аккаунта — это одно `paused_reason` или два? Сегодня значение ровно одно, `credit_exhausted`, и оно про кредит. Платформа берёт холд на сумму потолка до спавна, поэтому механически они могут совпасть — но это надо СКАЗАТЬ, а не вывести: пользователь, упершийся в собственный колпак, и пользователь, у которого кончились деньги, находятся в разных положениях, и фраза у них разная. Записано пометкой в `EventCeiling` |
|
||||
|
||||
## 5. Что фронт исправил в фикстурах и почему это надо знать автору контракта
|
||||
|
||||
- **Снят неснятый маркер `第二节:` из колонки оригинала.** Компаньон §2.3 фиксирует замером: чанкер
|
||||
ВЫРЕЗАЕТ маркер из текста, который видит модель. Формы, которую рисовала прежняя фикстура,
|
||||
движок для zh→ru не порождает — это был четвёртый экземпляр класса «фикстура выдумала форму».
|
||||
- **Титул больше не вклеен в текст первого юнита фикстуры.** Метка живёт в `Chapter.heading`.
|
||||
Это НЕ решение К-2: фикстура просто перестала утверждать что-либо про открытый вопрос.
|
||||
- **Ключ строки банка — непрозрачный `id` контракта.** Прежняя заплата из (`src`,`dst`,`kind`)
|
||||
роняла вторую строку полисемичного термина; в фикстуре теперь есть настоящая пара таких строк
|
||||
(青茅 в двух смыслах с разными окнами), и без `id` она видна как дефект сразу.
|
||||
|
||||
## 5-бис. Три правки спеки СВЕРХ списка промта — с основанием на каждую
|
||||
|
||||
Все три сняты адверсариальным ревью диффа как утверждения, переставшие быть правдой; ни одна не
|
||||
меняет форму, все три меняют то, что файл о себе говорит.
|
||||
|
||||
1. **`Problem.title` получил то же ограничение, что и `detail`.** Было: запрет движкового текста
|
||||
стоял только на `detail`. Но клиенту при отказе нечего показать, кроме этих двух полей, и
|
||||
`title`, написанный для разработчика, становится фразой, которую читает человек. Теперь оба поля
|
||||
объявлены продуктовым языком: `title` — класс отказа, `detail` — конкретная фраза.
|
||||
2. **«vocabulary of version 0.1» → «of THIS version».** Предложение осталось от 0.1.0 и после бампа
|
||||
стало враньём о собственном файле.
|
||||
3. **`servers.description` перестал называть базовый путь непроверенным предложением фронта.**
|
||||
Платформа заехала, и её собственная ручка в промте записана как `GET /v0/usage`, то есть префикс
|
||||
подтверждён. Заодно сказано главное: фиксирован только префикс версии, хост — тот origin, что
|
||||
отдал приложение, и клиент, зашивший конкретный хост, никуда не разворачивается.
|
||||
|
||||
⚠ Пункт 3 — единственное место, где я опираюсь на промт, а не на код платформы: в её зону мне
|
||||
ходить не велено. Если префикс на самом деле другой, это правится одной строкой.
|
||||
|
||||
## 6. Зависимости, которые эта правка НЕ закрыла
|
||||
|
||||
Прежний §3 компаньона в силе целиком, плюс одно уточнение:
|
||||
|
||||
- **Механизма поднятия потолка по-прежнему нет,** и теперь это записано в спеке жёстче:
|
||||
`ceiling_chapters` едет со СТАРТОМ прогона, изменить его контракт не умеет вовсе. Значит
|
||||
`resume` после стопа по потолку возвращает прогон в то же состояние, и спека прямо запрещает
|
||||
клиенту предлагать `resume` как лекарство от `paused`. Строка единого бэклога (126) остаётся
|
||||
открытой и становится блокером экрана S4.
|
||||
Loading…
Add table
Reference in a new issue