Ratify contract 0.4.0 and record the P7 acceptance with the second-rubezh corrections, seven new backlog rows and the fix list carried to the zone
This commit is contained in:
parent
9b23e8c024
commit
6e845744e7
7 changed files with 710 additions and 79 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,4 +1,4 @@
|
|||
# Реестр D-нот — карта актуальности v2 (D1–D39.151; строка 167; титул — носитель головы, бампать при каждом аппенде — испр. оркестратором №17 15.08 по аудиту: отставал на четыре ноты)
|
||||
# Реестр D-нот — карта актуальности v2 (D1–D39.153; строка 167; титул — носитель головы, бампать при каждом аппенде — испр. оркестратором №17 15.08 по аудиту: отставал на четыре ноты)
|
||||
|
||||
> Одна строка на КАЖДУЮ ноту журнала решений: № · дата · суть · статус · где тело · темы. Ведёт оркестратор при лендингах: новая нота = новая строка ТЕМ ЖЕ коммитом (полноту сторожит `docs/scripts/counts.py --check`). Суть и статус здесь — НАВИГАЦИЯ, не контракт: при конфликте побеждает ТЕЛО ноты (живой [`05-decisions-log.md`](05-decisions-log.md) → слайсы `../archive/architecture/05-decisions-*.md`). Несущие эрраты к фактам старых нот живут в шапке живого D-лога — их читать ОБЯЗАТЕЛЬНО, реестр их не дублирует. Темы (для грепа оси «какой закон по X»): деньги · шов · банк · промпт · судья · гейты · контракт · платформа · фронт · полигон · ToS · общность · процесс · нарезка · голос · инфра · модели.
|
||||
> Родословная: прозаическая карта D1–D39.28 (ревизии D31–D38.2) пересобрана в реестр докс-паком 167 (09.08.2026, №16): извлечение 6+6 агентов по worksheet `../archive/reports/DOC_AUDIT_INVENTORY_2026-08-09.md`, полнота сверена скриптом против заголовков всех шести файлов. «тело: жив» = живой D-лог; «слайс N» = `../archive/architecture/05-decisions-N.md`.
|
||||
|
|
@ -204,3 +204,5 @@
|
|||
| D39.149 | 17.08 | Приёмка пака «честные числа и статусы» (панель 5 линз + пере-раны оркестратора): ПРИНЯТ С ФИКС-ЛИСТОМ ФЧ-1…ФЧ-8, регрессов нет; ратифицированы паника→exit 1 (PD-212 со стороны движка закрыт, полосы не двинуты), PanicError без Unwrap, пере-прайс фактическими токенами через шов сеттла с починкой обеих половин гейта, выборочный порог в риге (4/5 PASS); деньги пробы $0.005582424 сошлись двумя независимыми путями; строки 176/181/187 закрыты, 172-г запрещено закрывать этим прогоном, новые 194 (деньги банковых ролей вне согласия) · 195 (маркер поколения — схема, на владельце) · 196 (fatal error residual); владельцу — ось МОДЕЛИ в числе согласия | ЖИВОЕ: фикс-лист ФЧ и два решения владельца | жив | деньги приёмка шов бэкенд |
|
||||
| D39.150 | 17.08 | Ось МОДЕЛИ в числе согласия — решение владельца: прайсить по модели, которую стадия резолвит СЕЙЧАС, + округление вверх + оговорка в тексте согласия; обещание механизма переформулировано с точности на НАПРАВЛЕНИЕ ошибки («никогда не занизить молча»), обещание «status не может назвать другую сумму» снимается при исполнении; фикс-лист ФЧ-1…ФЧ-8 записан строкой 197 без заказа сессии; маркер поколения (195) отложен и вынесен на внешнее мнение — найдено, что это четвёртый жилец семьи 49/124/195 с общим корнем «денежная таблица не помнит конфигурацию»; ПТ-14 понижен ✅→🔶, промт пака в архив с баннером-исходом | ЖИВОЕ: строка 197; вопрос 195 у внешнего ревьюера | жив | деньги владелец бэкенд процесс |
|
||||
| D39.151 | 17.08 | Владелец выбрал вариант А: маркер поколения отдельной колонкой на `checkpoints` НЕ строится, строка 195 закрыта как самостоятельная стройка и переселена осью (г) «идентичность оплаченной работы» в дизайн-пак 124 (гейт — его триггеры: 185/МАСШТАБ/внешний потребитель числа); внешний разбор опроверг две посылки оркестратора — колонка НЕ даёт тождества ($0-пере-пин законно несёт старую цену на новом снапшоте, бэкфилла нет ⇒ вывод остаётся навсегда) и «семья 49+124+195» слишком широка (49 = легальность переиспользования, 195 = честность учёта); настоящий корень — отсутствие связи «строка ↔ оплатившие её вызовы»; срочности нет под контрактом D39.150, остаток чинится без схемы строками 197/194 (194 миграции НЕ требует — RoleSpentUSD ключуется по роли) | ЖИВОЕ: ось (г) пака 124; строки 197/194 | жив | деньги владелец бэкенд |
|
||||
| D39.152 | 20.08 | Контракт API v0 **0.4.0 РАТИФИЦИРОВАН** (синк с платформой по её записке, 14 пунктов A–N; author≠ratifier соблюдён контрактной сессией; условие закрытия PD-327 наступило, САМА строка регистра ждёт правки зоной — пункт 10 фикс-листа); рубеж исполнен ревьюером другой модели (чтение обеих версий целиком, «ратифицировать с правками», корневой enum 16 значений не тронут); четыре текстовые правки внесены оркестратором при ратификации (сеттер `stop_requested` в `stopRun` · стухшая фраза компаньона §6в A против его же аддендума · «cleared by УСПЕШНЫЙ resume» · приписка третьего случая к таблице резюма); пункт G («сделано» = последний проход этого деплоя) ратифицирован как шаг-независимый — рамка снята владельцем 20.08 требованием, чтобы форма пайплайна не была видна клиенту; ⚠ клейм зоны «ломающая правка одна» верен для диффа ТИПОВ и неточен поведенчески — против 0.3.0 расходятся ЧЕТЫРЕ места; оговорка про пер-термные решения расширена с `decline` на всю ручку (временная, снимается строкой 199а); следующий минор (сквозная полоса, 200) не втиснут — пока фронт заморожен, миноры бесплатны | ЖИВОЕ: канон 0.4.0 | жив | контракт платформа фронт владелец |
|
||||
| D39.153 | 20.08 | Приёмка и лендинг P7 платформы (пять актов, читающая поверхность): **ПРИНЯТ С ФИКС-ЛИСТОМ**; шесть линз в изоляции + пере-раны и 8 собственных посадок оркестратора (поймано 7); батарея 18 пакетов EXIT=0 скипов 0, реестр 327/65 открытых без major, тестов 532 (не 533), семь удалённых имён имеют преемников; замер разворота 00016→00022 пере-выведен независимо (джойн 14.6 · счётчик 5.3 · без счётчика 2.9 мс) — разворот законен, но процент назван тремя носителями по-разному; НЕ поймана дыра: снятие `structure_version`/`revision` из `emitFrame` проходит всю батарею; ратифицированы долг-с-арендой на материализацию (§33/§34), `edit_wave` как монотонное свойство КНИГИ от объявления движка (§35), владелец клейма идемпотентности + тождество дайджестом, halt с аккаунта, счётчик замечаний на главе, демонтаж гейта полноты банка; новые строки 198–203; решения владельца — sqlc берём (+ разбор собранных запросов Postgres'ом, которого в предложении зоны не было), живой прогон ПОСЛЕ холодного, сквозная полоса прогресса; ⚠ эррата к D39.144 п.2 (пер-термная формула рождена в ДВИЖКЕ 19.07 как лечение ливлока, контрактная линия изобрела из неё ГЕЙТ 04.08); `ContractVersion` оркестратором НЕ правился — первый пункт зонного фикс-листа | ЖИВОЕ: фикс-лист в пинге №18 зонного журнала; строки 198–203 | жив | платформа приёмка контракт шов деньги |
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Журнал решений оркестратора — контракт D1–D39.151 (живой файл: карта · эрраты · живые тела · голова D39.124+ (подрезка D39.139); тела закрытых эр — в слайсах `docs/archive/architecture/`, указатель ниже; реестр всех нот — `05-decisions-index.md`)
|
||||
# Журнал решений оркестратора — контракт D1–D39.153 (живой файл: карта · эрраты · живые тела · голова D39.124+ (подрезка D39.139); тела закрытых эр — в слайсах `docs/archive/architecture/`, указатель ниже; реестр всех нот — `05-decisions-index.md`)
|
||||
|
||||
> **⟶ КАРТА АКТУАЛЬНОСТИ (ревизия D31, продлена до D38.2 [12.07]; исторические записи ниже НЕ переписываются — дисциплина D23.3).** Работая с контрактом (греп номера: живой файл → слайсы, целиком НЕ читать — D39.125), держи под рукой, что чем перекрыто:
|
||||
> ⚠ **Эррата 09.08 (D39.125):** D39.111 п.1 предписывал промту S3 «максимум = баланс МИНУС открытые холды» — формула ОШИБОЧНА (вычитание дважды), исправлена D39.115 п.2(а): максимум = Balance КАК ЕСТЬ; тело D39.111 живёт ниже в этом файле (голова D39.106+).
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
> ⚠ **Эррата 15.08-в (D39.136 п.5):** форма контракт-ревью «фазный воркфлоу оркестратора» ОТМЕНЕНА словом владельца тем же днём — исполняет ОТДЕЛЬНАЯ СЕССИЯ со своим онбордингом (норма D39.120 п.2; оркестратор — автор части ратификаций 0.2.3, author≠reviewer); запущенная воркфлоу-фаза 1 остановлена, её результаты выброшены; промт — `docs/CONTRACT_REVIEW_SESSION_PROMPT.md`.
|
||||
> ⚠ **Эррата 16.08-д (D39.144 п.3):** клейм «`bank_decisions_incomplete` в файле 0» проверял ТОКЕН, а не ПРАВИЛО — четыре места канона/компаньона продолжали нести снятый гейт полноты прозой (`BankDecisionsResult.complete` «stop clears ONLY on a complete set» · `BankPage.complete` «can be cleared» · компаньон §2.14 и А-2); дочищены 16.08 при пересборке промта P7 (нашёл опровергатель промта). Плюс факт, который D39.144 не назвала: гейт полноты УЖЕ ОТЛИТ в коде платформы (`internal/runs/reconcile.go:934-938`) — его ДЕМОНТАЖ заказан P7 явным пунктом.
|
||||
> ⚠ **Эррата 16.08-г (D39.138 п.2а, часть «TermStatus снять с провода»):** ОПРОКИНУТА пост-ревью АВТОРА отчёта research/28 (по заказу владельца, речек оркестратора уликами): обоснование Б-0 «ни один экран их не рисует» подменяло «экрана подписи ещё НЕТ» — ось статуса ПРОДУКТОВАЯ (спека `:1070-1072`: «главный фильтр работы» S5; шов клиента уже потребляет — `frontend/src/api/vocabulary.ts:159-163`). Читать так: ось `TermStatus` ОСТАВИТЬ, движковые слова значений переименовать в продуктовые; «`TermOrigin` снять/переименовать» (`ruby` — паро-специфика) — В СИЛЕ. Контекст метода: Б-0/Б-11а/Б-23 добавлены в отчёт ПОСЛЕ раунда адверсариального судейства сессии — их основания проверять строже (помечено в ревью-шапке отчёта).
|
||||
> ⚠ **Эррата 20.08 (D39.153 п.7б):** D39.144 п.2 атрибутирует пер-термный гейт полноты банка «изобретению КОНТРАКТНОЙ линии (Ф-14 → спека → 0.3.0)» — верно про ГЕЙТ и **неверно про ФОРМУЛУ**. Формула «стоп очищается, когда каждый терм промоутнут ИЛИ отклонён» родилась 19.07 В ДВИЖКЕ как лечение ливлока R1-FL-B (отклонённый терм переспрашивался каждый прогон — тело D39.19, слайс `../archive/architecture/05-decisions-D39-arch-reset.md:22`) и живёт там до сих пор (`backend/internal/pipeline/mining.go:80-82,243`); контрактная линия 04.08 переписала её дословно и вывела из неё ЭКРАННЫЙ гейт — счётчик «решено N из M» и запрет продолжить (коммит `16e20e5`, `frontend/docs/API_CONTRACT_INPUT.md:258-264`). **Следствие, из-за которого эррата несущая:** снимая гейт, D39.144 адресовала только контракт, поэтому движковая половина осталась БЕЗ явной отмены — она сегодня ОБОЙДЕНА платформой (флаг не передаётся на resume), а не устранена; носитель — строка 191(б), развилка — строка 203(з).
|
||||
> ⚠ **Навигация (актуализация 07.08, эра D39.1xx):** append-only-дисциплина (D23.3) означает, что
|
||||
> НЕВЕРНЫЙ ФАКТ внутри старой ноты не переписывается, а получает эрратау — и тогда он опасен ровно
|
||||
> тем, что грепающий его найдёт ПЕРВЫМ, а эрратау ниже может не дочитать. Поэтому поправки к фактам
|
||||
|
|
@ -517,3 +518,47 @@
|
|||
**3. Настоящий корень назван (для будущего пака 124).** Не «денежная таблица не помнит конфигурацию», а «в сторе нет первоклассной связи *строка диспозиции ↔ вызовы, которые её оплатили*»: `checkpoints` append-only на жизнь книги, `chunk_status.cost_usd` перезаписывается каждым поколением, и единственный пер-поколенный авторитет — деньги самой строки. Снапшот на чекпоинте — прокси этой связи, а не примитив; класть в схему надо провенанс, а не флаг конфигурации.
|
||||
|
||||
**4. Почему срочности нет.** Под контрактом D39.150 («гарантируется НАПРАВЛЕНИЕ ошибки, не точность») ни один исход нынешнего механизма не занижает молча: нормальный случай точен, недостача несётся историческими деньгами С РАСКРЫТИЕМ, пойманный откат откатывается к устаревшей цене ВСЛУХ. Остаточные нарушения контракта чинятся БЕЗ схемы и уже имеют носителей — **197** (прибить гарантии тестами и довести раскрытие до `status`) и **194** (деньги банковых ролей в число согласия). ⚠ Уточнение цены 194, сверенное оркестратором: миграция ей НЕ нужна — `store.RoleSpentUSD` уже ключуется по РОЛИ чекпоинта, а не по колонке класса (`internal/store/ledger.go:105-118`, докстринг прямо: «a new call class needs no migration to become countable»). (17.08.2026, оркестратор №18) ✅
|
||||
|
||||
## D39.152 — КОНТРАКТ API v0 **0.4.0 РАТИФИЦИРОВАН** (синк с платформой, 14 пунктов; author≠ratifier соблюдён сессией); четыре текстовые правки внесены при ратификации; клейм «ломающая правка одна» верен для типов и неточен поведенчески (20.08). ✅
|
||||
|
||||
**1. Что ратифицируется.** `docs/architecture/14-api-contract/openapi.yaml` + компаньон, `info.version: 0.4.0`. Автор — ОТДЕЛЬНАЯ контрактная сессия (20.08) в ответ на записку зоны платформы `platform/docs/CONTRACT_SYNC_FROM_PLATFORM.md` (14 пунктов A–N); разбор каждого — §6в компаньона. Сессия ратификацию за собой не записала и сдала текст оркестратору — норма author≠ratifier исполнена ею самой, отмечаю как правильное поведение. **0.3.0 действовал до этой ноты**; зависимость зоны (PD-327) закрывается ею.
|
||||
|
||||
**2. Рубеж перед ратификацией исполнен** (норма: тяжёлая ратификация — контракт — смотрится ревьюером другой модели, мнение владельцу ДО). Fable-5, чтение обеих версий ЦЕЛИКОМ: вердикт «ратифицировать с правками». Пере-проверено ИМ независимо дважды: структурный дифф схем и дифф генерённых типов дают ровно одну строку `stop_requested`. Корневой `ErrorCode` — 16 значений, байт-в-байт не тронут; все открытые уровни (`ErrorCause`, `ErrorItem.code`, `Note.code`, три reason-словаря) объявлены открытыми явно. Все 14 пунктов записки имеют диспозицию, остатков прежних формулировок ноль (грепы по снятым фразам = 0).
|
||||
|
||||
**3. Четыре правки внесены оркестратором при ратификации** (микро-дефекты доков своей зоны; в yaml — без маркера, описания компилируются в типы фронта): (а) `stopRun` не называл СЕТТЕРА нового поля и отправлял клиента хранить состояние у себя — прямо против описания самого поля («survives a reload, which a client's own pending state does not»); теперь названо, что стоп-вызов и есть сеттер и `202` уже несёт `true`. (б) Компаньон §6в A нёс «пустой СЧЁТ — не ответ этого вызова», что опровергается его же аддендумом и каноном (`credit_unavailable` — ровно ответ этого вызова); исправлено с пометкой. (в) «cleared by `resume`» уточнено до УСПЕШНОГО resume — отвергнутый буквально тоже resume. (г) Третий случай `resumeRun` приписан к таблице явно, как это сделала вторая ось: судится ПОСЛЕ таблицы и только там, где та сказала `202`.
|
||||
|
||||
**4. Пункт G («сделано» = последний проход, который книга на ЭТОМ деплое реально получает) — ратифицирован, и вот на каком основании.** У G, в отличие от J и A, записанного слова владельца не было, и зона сама пометила его продуктовым. Развилка снята владельцем 20.08 иначе, чем спрашивалось: он отверг саму рамку «какая фаза считается» и потребовал, чтобы форма пайплайна вообще не была видна клиенту. Формулировка канона этому УЖЕ удовлетворяет дословно — «the last pass the book actually gets, **whatever that is**» — и потому ратифицируется как есть: она не связывает пайплайн, а альтернатива (счёт по редакторской волне) — живой денежный дефект, на деплое без редактора ни одна глава не сделана никогда и шкала покупки не подрезается. Признак монотонен ⇒ книга, прошедшая редактирующий пайплайн, назад не откатывается.
|
||||
|
||||
**5. Клейм зоны «ломающая правка ровно одна» — верен для ТИПОВ, неточен поведенчески; записано, чтобы следующая приёмка на него не опёрлась.** Дифф генерённых типов слеп ко всему, что не двигает тип. Против 0.3.0 расходятся ПЯТЬ мест (⚠ первая редакция этой ноты называла четыре — пятое принесла проверка записей при лендинге): `Run.stop_requested` (типовое) · тождество интейка по дайджесту СОДЕРЖИМОГО против дословного 0.3.0 «compared over the DECLARED parts… and **never over the bytes themselves**» · `409 ceiling_reached` на исчерпанном прогоне там, где таблица §resumeRun 0.3.0 говорит «continues it — `202`» · валидатор/`304` на `getRunOptions` и `getUsage` сверх объявленных · **`410 Gone`**: 0.3.0 отвечает им «главе, которая БЫЛА и больше нет» и прямо противопоставляет `404` («would mean a typo»), 0.4.0 — «whether or not the id was ever real» (носитель расхождения назван зоной сама: `platform/docs/DEFECT_REGISTER.md` PD-253 — «расхождение снято НЕ кодом, канон изменён в нашу сторону»). Все пять 0.4.0 благословляет ⇒ цена уплачена этой ратификацией, дефекта пака здесь нет.
|
||||
|
||||
**6. Оговорка про пер-термные решения расширена с `decline` на ВСЮ ручку — внесена в канон при ратификации.** Прежний текст предупреждал только про `decline`, а инертны одинаково `approve`, `decline` и впечатанный `dst`; у `dst` не было ни слова, и грепающий канон расширения не увидел бы (поймано проверкой записей при лендинге — клейм ноты опережал текст). Теперь `BankDecision.action` несёт «NOTHING recorded here reaches the work itself yet», `dst` — отсылку к ней, и там же сказано, что́ работает СЕГОДНЯ: сама подпись, один `resume` над всем банком при любом состоянии решений. Оговорка ВРЕМЕННАЯ и объявлена свойством деплоя, а не обещанием контракта — снимается исполнением строки 199(а).
|
||||
|
||||
**7. Почему следующий минор не втиснут сюда.** Требование сквозной полосы прогресса (строка 200, слово владельца 20.08) — новое обязательное поле, то есть ещё один минор и ещё одна перегенерация клиента. Пока фронт заморожен и не потребляет ничего, миноры бесплатны: оба уедут в ОДНУ перегенерацию при разморозке. Значит нет причины вскрывать уже принятый пак платформы и заставлять зону доделывать на ходу. Зеркало фронта отстаёт ратифицированно (D39.142 п.5), при разморозке сверяется `cmp`. (20.08.2026, оркестратор №18) ✅
|
||||
|
||||
## D39.153 — ПРИЁМКА И ЛЕНДИНГ P7 ПЛАТФОРМЫ (пять актов, читающая поверхность): **ПРИНЯТ С ФИКС-ЛИСТОМ**; шесть линз + собственные посадки, одна дыра пинов найдена оркестратором; новые строки 198–203; сессия закрыта владельцем (20.08). ✅
|
||||
|
||||
**1. Приёмка исполнением.** Панель шести линз в изолированных копиях, каждой свой каталог, бэкап чужого незакоммиченного ДО запуска (D39.113): слепая (заказ+дифф ДО отчёта) · контракт-конформность (пофайловый дифф wire-структур против ОБЕИХ версий канона) · деньги (два независимых пути из сырого леджера + живой стенд) · шов (инвентарь ВСЕХ каналов движка чтением чужого кода) · вне карты · ревью канона 0.4.0 другой моделью. Ни одной REJECT, регрессов против HEAD нет. **Пере-проверено рукой оркестратора:** `make check` с обоими гейтами — 18 пакетов, EXIT=0, **скипов 0**, линтер 0 issues · реестр 327 строк / 65 открытых (13 minor, 52 info), major и BLOCKER нет · оба док-гейта зелёные · миграционный манифест сходится, released-миграции не тронуты · `tmplatformctl books --migratable` живой. ⚠ Тестовых функций **532**, не 533 (команда — `grep -rh '^func Test' platform --include='*_test.go' | wc -l`; норма «дифф `^func Test` исполнением, не памятью» — `docs/ORCHESTRATOR_SESSION_PROMPT.md` §кодовый промт, со ссылкой на D39.121; в теле самой D39.121 команды нет — испр. при проверке записей); удалённых имён семь — каждое проверено, у каждого есть преемник, и преемники СИЛЬНЕЕ либо перевёрнуты по ратифицированному решению (гейт подписи банка → `TestResumeLiftsABankStopWithTheDecisionsAsTheyStand` плюс НОВЫЙ встречный `TestTheReconcilerDoesNotLiftABankStopNobodySigned`).
|
||||
|
||||
**2. Замер, обосновавший разворот схемного решения ВНУТРИ пака (00016 → 00022), пере-выведен независимо.** Свой бенчмарк на трёх вариантах в копии дерева: джойн **14.6 мс** · счётчик **5.3 мс** · без счётчика **2.9 мс**. Порядок и вывод подтверждаются, разворот законен: 00016 снёс колонку с доводом «её никто не пишет», 00022 вернул её ВМЕСТЕ с писателем. ⚠ Процент назван ЧЕТЫРЬМЯ носителями (журнал зоны 83% · `platform/internal/pgstore/perf_test.go:13` 96% · миграция 00022 «16.8 против 3.4, джойн сам 9 мс» · регистр PD-306 повторяет 83% и 16.8/3.4) — распад нормы «один носитель на факт» (D39.112) внутри одного пака. ⚠ **Первая редакция этой ноты писала «96% не выводится ни из чего» — ЭТО НЕВЕРНО и снято проверкой записей при лендинге:** число выводится точно, просто из ДРУГОГО замера — `platform/docs/P7_ACT5_FIX_PLAN.md:439-441`, «636 мс против 24 мс» на холодном корпусе акта 4, (636−24)/636 = 96.2%. То есть носители меряли разное (холодный корпус против вакуумированного) и оба честны; сводить их надо указанием УСЛОВИЙ замера, а не удалением одного как фантома — иначе следующая сессия снесёт верное число.
|
||||
|
||||
**3. Собственные посадки ВНЕ списка автора — 8, поймано 7.** Долг не поставлен третьей концовкой · `edit_wave` не монотонен · снят `Vary` · HEAD лишён валидатора · `reopen` снимает стоп банка кому угодно · погашение долга без сверки метки · вынос метки долга из закрывающей транзакции во второй оператор (техника `xmin` из их §36 работает — проверено исполнением, а не принято на слово). **НЕ поймано:** снятие `structure_version` И `revision` из `emitFrame` проходит ВСЮ батарею — два поля, которые канон требует на КАЖДОМ кадре `EventBase`. По норме самой зоны (PD-1) свойство без пинящего теста считается незакрытым.
|
||||
|
||||
**4. Что ратифицируется этой нотой.** (а) **Долг на материализацию — колонка с арендой** (`books.read_model_owed_at`), ставится в ТОЙ ЖЕ транзакции, что закрывает границу работы, гасится сверкой метки, берётся в аренду тем, кто платит; очередь в памяти процесса и второй бэкстоп удалены — один механизм вместо двух. Правило «каждая транзакция, ЗАКАНЧИВАЮЩАЯ прогон, оставляет книгу должной» (их §33) принято вместе с формой его записи одним фрагментом на все три концовки. (б) **«Есть ли у пайплайна редактор» — свойство КНИГИ, монотонное, от объявления движка** (§35): читать с последнего прогона нельзя — счётчик ходил назад в момент допуска нового прогона, что канон запрещает. Половина «форму объявляет движок» проверена чтением ЧУЖОГО кода (`beginWaves`: знаменатель ноль у волны, которой нет). (в) **Владелец у клейма идемпотентности + тождество интейка дайджестом содержимого** — закрывает класс «застрявшая попытка удаляет строку преемника / пишет квитанцию поверх чужой». (г) **Остановка АККАУНТА читается с аккаунта**, а не с потолка одного прогона; аномалия «97% остатка рядом с `credit_exhausted`» закрыта СТРУКТУРНО (единственная точка присваивания под `!Spendable`), пере-проверено денежной линзой живьём. (д) **Счётчик замечаний вернулся на главу** — по замеру п.2. (е) **Демонтаж гейта полноты банка** в платформе — исполнение D39.144; согласованность с движком проверена чтением его кода: движок уходит по авто-ветке при снятом флаге, зависания нет.
|
||||
|
||||
**5. Фикс-лист и долг — где живут.** Зонный фикс-лист (десять пунктов, первым — `ContractVersion` 0.3.0 при формах 0.4.0) — **пинг оркестратора №18 в `platform/docs/platform-PROGRESS.md`**; зона заводит из него строки СВОЕГО бэклога и регистра. ⚠ Константу версии оркестратор НЕ правил: зонное правило «код не правишь, находки в фикс-лист» держится и при закрытой сессии; практического вреда сегодня нет (фронт заморожен на 0.2.3, 0.3.0-клиент лишнее поле игнорирует), но деплой объявляет версию, которую не отдаёт. Новые строки ЕДИНОГО бэклога: **198** (апгрейд движка стирает замечания и счётчики безвозвратно — композиция зачистки при ре-кате и announce-once движка; гейт холодного прогона) · **199** (канал доставки правок банка) · **200** (сквозная полоса прогресса) · **201** («Глава N» внутри текста экспорта) · **202** (живой прогон насквозь) · **203** (хвосты контракта, релеи §7 зоны).
|
||||
|
||||
**6. Решения владельца 20.08, записанные этой нотой.** (а) **sqlc БЕРЁМ** («я вообще за») — отступление P7 от D39.132 п.2б закрыто решением, а не оправданием; ⚠ оркестратор добавил к заказу то, чего в предложении зоны не было: все рантайм-ошибки «нет такой колонки» случились в СКЛЕЕННОМ SQL read-модели, куда sqlc по построению не дойдёт (⚠ счёт: РЕПОЗИТОРИЙ несёт две — `platform/docs/DEFECT_REGISTER.md` PD-44 и журнал зоны, «дважды укусил этот пак»; ещё две — `column c.note_count does not exist` и `missing FROM-clause entry for table "b"` — зона назвала в ответе владельцу 20.08, пересланном оркестратору, и в доки зоны они не попали: проверяемого носителя у них нет, поэтому опираться следует на две), поэтому тем же паком — разбор каждого СОБРАННОГО запроса Postgres'ом против мигрированной схемы и ответ на вопрос «есть ли запрос, которого не касается ни один тест». (б) **Живой прогон книги насквозь — ПОСЛЕ холодного прогона движка** («гонять одну неготовую систему против другой смысла нет»), строка 202. (в) **Сквозная полоса прогресса вместо пофазной** — строка 200; заодно снята рамка вопроса о «сделано» (см. D39.152 п.4).
|
||||
|
||||
**7. Метод-заметки, купленные этой приёмкой.** (а) **Композиция двух известных фактов — класс, который карта отчёта не содержит по построению.** Строка 198 сложена линзой «вне карты» из двух вещей, каждая известна автору ПО ОТДЕЛЬНОСТИ (зачистка ратифицирована PD-264, «движок не переанонсирует» установлено PD-298) — никто не сложил. (б) **Ошибка оркестратора, названная вслух:** пер-термную ручку банка я подал владельцу как продуктовую дыру; гейт был снят четырьмя днями раньше D39.144 в том самом разговоре, на который владелец ссылался, а контракт с 16.08 сам называет пер-термные решения ОПЦИОНАЛЬНЫМ путём правки. Расследование (5 читающих линз + сводка + два опровергателя) установило провенанс: формула «стоп очищается, когда каждый терм промоутнут ИЛИ отклонён» родилась 19.07 В ДВИЖКЕ как лечение ливлока R1-FL-B (отклонённый терм переспрашивался каждый прогон), то есть как условие выхода из паузы ОПЕРАТОРА CLI; 04.08 контрактная линия переписала её в спеку дословно и вывела продуктовое правило, которого никто не заказывал — счётчик «решено N из M» и запрет продолжить (коммит `16e20e5`, `frontend/docs/API_CONTRACT_INPUT.md:258-264`; журнал фронта сам пишет, что этого «в Ф-14 не было»). ⚠ **Эррата к D39.144 п.2:** атрибуция «гейт был изобретением контрактной линии» верна про ГЕЙТ и неверна про ФОРМУЛУ — она старше контракта на три недели и рождена в движке, поэтому движковая половина осталась без явной отмены (строка 191б, живёт в `mining.go:80-82,243` и сегодня ОБОЙДЕНА платформой, а не устранена). (в) **Опровергатели поймали ошибку собственной сводки:** она описывала РАБОЧЕЕ ДЕРЕВО, выдавая его за заленденный код — то есть нарушила ровно то правило «объявленное ≠ построенное», на котором ловила контрактную линию. Формулировка исправлена: до этого лендинга «один ОК» владельца не «доезжал пустым», а был ОТКАЗАН на первом звене (`HEAD:reconcile.go:934-938`).
|
||||
|
||||
**8. ВТОРОЙ РУБЕЖ — приёмка приёмки (слово владельца 20.08: «сначала приёмка твоей же работы, потом коммит»).** Четыре линзы прочитали не пак, а РАБОТУ ОРКЕСТРАТОРА: широкий инвентарь доков (другая модель, чтение всей документации четырёх зон целиком) · claim-fidelity обеих нот и всех сегодняшних записей · поиск того, что панель пропустила · полнота выгрузки долга и зонная дисциплина. Результат оправдал остановку коммита.
|
||||
|
||||
**(а) Пять овер-атрибуций в первой редакции ЭТИХ нот — снято, каждая проверена автором лично.** «96% не выводится ни из чего» — неверно: число выводится точно из ДРУГОГО замера зоны (`P7_ACT5_FIX_PLAN.md:439-441`, 636 мс против 24 мс на холодном корпусе), и удаление его как фантома снесло бы верное измерение · расхождений с 0.3.0 не четыре, а ПЯТЬ (пятое — семантика `410 Gone`, носитель PD-253) · «четыре рантайм-ошибки» проверяемого носителя не имеют, репозиторий несёт две · клейм «оговорка расширена на всю ручку» опережал текст канона — оговорка внесена только сейчас (D39.152 п.6) · «фикс-лист из восьми пунктов» устарел на собственной правке. **Класс один и тот же — верное утверждение приписано не тому источнику**, и это ровно тот провал, против которого написана дисциплина «эрудиция ≠ улика».
|
||||
|
||||
**(б) Долг, живший только внутри зоны автора, вынесен наружу.** Строка в бэклог ДВИЖКА про публикацию причин флагов данными (релей §7в) жила только в регистре платформы, который движок не читает — заведена строка **204** · вопрос платформы К ВЛАДЕЛЬЦУ про норму плотности комментариев (PD-255) был объявлен несуществующим её же хендоффом («вопросов на владельце нет») — вынесен в обязательства CURRENT-STATE · норма «жалуйся владельцу, не обходи молча» для фронтового фрагмента хука жила только в моей прозе — написан пинг в зонный журнал фронта. Плюс сняты три строки бэклога (**176/181/187**), закрытые ещё D39.149: очередь всё это время звала 181 свободной работой, то есть направляла сессию чинить построенное.
|
||||
|
||||
**(в) Найден жёсткий блокер ВНЕ пака, которого диффовая линза увидеть не могла по построению** (проверен мной построчно): `Sweep` платформы имеет бюджет прохода 2 минуты (`cmd/tmplatformd/runner.go:189`) при бюджете одного прогона 60 с (`internal/runs/reconcile.go:70`) — **двух медленных прогонов достаточно, чтобы проход кончился до `UnsettledRuns`**, единственного ретрая отложенного расчёта, а живые прогоны ниже по списку не реконсилировались и не спавнились; `ListLiveRuns` сортирует по `started_at`, поэтому заклиненный прогон стоит в голове и голодит остальных детерминированно, а ручки нет — `runs.Config.RunBudget` объявлен и в `startRunner` не присваивается. Цена: холд не возвращается, `runs_one_live_per_book` не даёт запустить новый прогон книги. Код приехал в P6, дифф `Sweep` против HEAD идентичен ⇒ ни одна линза приёмки P7 его видеть не могла. ⚠ **`PD-169` стоит `fixed(P5)` и этим ЛЖЁТ приёмке** — его пин гоняет `Sweep` без дедлайна прохода, то есть доказывает пер-прогонный бюджет, а не выживание прохода; пере-открыть. Носитель — первый пункт фикс-листа в пинге №18. **Тот же класс, что строка 198: композиция двух известных по отдельности фактов.**
|
||||
|
||||
**(г) Побочно получен ответ на вопрос, который я поставил открытым в фикс-листе:** собранный SQL read-модели извлечён через `go/types` и прогнан `EXPLAIN (GENERIC_PLAN)` против мигрированной схемы — 141 запрос, все планируются чисто, тестом недостижимы десять, и ни один из десяти не трогает схему 00016–00024. Дыры сегодня нет; гейт нужен как ПОСТОЯННЫЙ, потому что именно он делает этот ответ воспроизводимым.
|
||||
|
||||
**9. Что НЕ проверено (obstacle приёмки).** Живого перевода через платформу не было НИ РАЗУ — всё после «прогон стартовал и остановился» покрыто батареей против живого Postgres, но с движком-заглушкой (строка 202). Четыре оси не смотрел никто и никогда: деньги и леджер целиком · вход/сессии/CSRF · очередь и джобы · метрики — пак их не менял, а все приёмки смотрели дифф; это отдельная работа, не довесок. PD-297 зона не взяла осознанно (мерить на корпусной книге). Сессия P7 ЗАКРЫТА владельцем 20.08; промт отработан → `platform/docs/archive/` с баннером-исходом. (20.08.2026, оркестратор №18) ✅
|
||||
|
|
|
|||
|
|
@ -1,13 +1,24 @@
|
|||
<!-- ======================================================================
|
||||
РЕВЬЮ-ШАПКА (ратификация D39.99, 04.08.2026, оркестратор №12;
|
||||
батч 0.3.0 — D39.138, 16.08.2026, оркестратор №17)
|
||||
батч 0.3.0 — D39.138, 16.08.2026, оркестратор №17;
|
||||
синк с платформой 0.4.0 — 20.08.2026, контрактная сессия; РАТИФИЦИРОВАН D39.152, 20.08.2026, оркестратор №18)
|
||||
|
||||
СТАТУС: РАТИФИЦИРОВАН как контракт API v0. Нормативная поверхность —
|
||||
СТАТУС: РАТИФИЦИРОВАН как контракт API v0 по 0.4.0 включительно (D39.152). Нормативная поверхность —
|
||||
openapi.yaml РЯДОМ. ⚠ Зонная копия frontend/docs/api-contract/ ВРЕМЕННО ОТСТАЁТ (0.2.3 при каноне
|
||||
0.3.0) — ратифицировано D39.142 п.5 на время фриза фронта; синк байт-в-байт + перегенерация типов =
|
||||
0.4.0) — ратифицировано D39.142 п.5 на время фриза фронта; синк байт-в-байт + перегенерация типов =
|
||||
первое касание зоны при разморозке; после него правило прежнее: расхождение = дефект лендинга.
|
||||
Генерация типов фронта после этой ратификации идёт из ЭТОЙ копии.
|
||||
|
||||
20.08.2026: СИНК С ПЛАТФОРМОЙ (запрос platform/docs/CONTRACT_SYNC_FROM_PLATFORM.md, 14 пунктов) —
|
||||
канон 0.3.0 → 0.4.0, разбор и диспозиция КАЖДОГО пункта в §6в, отдельным пунктом §6в п.0 — сверка
|
||||
0.2.3 → 0.3.0 на молча срезанные при резке прозы правила (найдена одна, восстановлена).
|
||||
Ратификацию этой редакции контрактная сессия за собой НЕ записывает — это акт оркестратора:
|
||||
автор и ратификатор одного и того же текста совпадать не должны. Файлы уже несли 0.4.0 к моменту сдачи; в силу
|
||||
редакция вступила лендингом 20.08 — РАТИФИЦИРОВАНА D39.152 (там же: четыре текстовые правки,
|
||||
внесённые оркестратором при ратификации, и разбор того, что против 0.3.0 расходится ЧЕТЫРЕ места,
|
||||
а не одно). Строку в 05-decisions-log сессия тоже не писала по той же причине — готовый текст ноты
|
||||
был сдан оркестратору вместе с этой редакцией.
|
||||
|
||||
Приёмка 0.2.0: три адверсариальных воркфлоу-пасса (черновик · дофикс round-1 · round-2),
|
||||
19 подтверждённых находок, 0 опровергнутых, все вправлены; батарея фронта с
|
||||
контракт-шагом (spectral + дрифт-тест генерённых типов) зелёная исполнением.
|
||||
|
|
@ -42,8 +53,23 @@ cmp-сверка обязательна (D39.138 п.3).
|
|||
>
|
||||
> **Статус: РАТИФИЦИРОВАН.** 0.2.0 (D39.115, 08.08) · 0.2.1 (D39.123, 09.08) · 0.2.2 (D39.129,
|
||||
> 10.08) · 0.2.3 (D39.135, 15.08) · **0.3.0 (D39.138, 16.08) — ломающий минор по целостному
|
||||
> ревью research/28**. Дом канона — этот каталог; `frontend/docs/api-contract/openapi.yaml` —
|
||||
> байт-зеркало.
|
||||
> ревью research/28** · **0.4.0 (D39.152, 20.08) — синк с платформой, §6в**. Дом канона — этот каталог;
|
||||
> `frontend/docs/api-contract/openapi.yaml` — байт-зеркало.
|
||||
>
|
||||
> ⚠ **0.4.0 ломающий ровно по одному месту, и это ЗАМЕРЕНО, а не объявлено.** Дифф генерённых
|
||||
> типов 0.3.0 → 0.4.0 (`openapi-typescript@7`, комментарии отброшены) — **одна строка:**
|
||||
> `Run.stop_requested: boolean`. Всё остальное в этой редакции — проза канона, одна запись в
|
||||
> таблицу `resumeRun` и одно значение ВТОРОГО уровня (`cause.code: credit_unavailable`, а он по
|
||||
> замыслу не закрыт и типов не двигает): корневой enum не сужен и не расширен, обязательных полей
|
||||
> больше не появилось. Линтер зоны (`spectral --ruleset frontend/.spectral.yaml`) — 0 ошибок.
|
||||
>
|
||||
> ⚠ **Две границы этого замера, названные ревью диффа и обе честные.** (1) База — 0.3.0, а зонное
|
||||
> зеркало фронта стоит на 0.2.3: при разморозке фронт платит переход 0.2.3 → 0.4.0 целиком, и это
|
||||
> совсем другой объём (шапка ниже про отставание зеркала). Замер отвечает «что добавил ЭТОТ синк»,
|
||||
> не «что стоит фронту разморозка». (2) Гейт §5 на утечку конвейерного словаря пере-ран и чист по
|
||||
> сути, но «ноль вхождений» сказать нельзя: слово `edit` встречается дважды как обычный английский
|
||||
> глагол («not an edit», «not a row edit»), `stage` — один раз в самой формулировке запрета. Ни
|
||||
> одного нового вхождения этот синк не внёс.
|
||||
>
|
||||
> **0.x ломает миноры и будет ломать весь бета-период** (решение владельца 16.08, §8 п.17
|
||||
> research/28): 0.3.0 — обычный ломающий минор, а НЕ последний перед 1.0. Окно 1.0 — после беты.
|
||||
|
|
@ -280,6 +306,13 @@ drop юнит несёт причину ПЕРВОГО выпавшего чле
|
|||
→ `given` (пришло с книгой), `ruby` → `annotated` (сам текст книги сказал, как читать), `mined` →
|
||||
`found` (сервис нашёл в тексте). Проекция трёх пар — работа платформы.
|
||||
|
||||
**Правило пустого `kind` — и его проекция** (было в 0.2.3, при резке прозы уцелело в каноне и
|
||||
пропало здесь; возвращено синком 20.08). `kind` присутствует всегда и допускает `null`; `null`
|
||||
значит «сервис не решил», строка при этом остаётся подписываемой, и клиенту запрещено и выбрасывать
|
||||
её, и додумывать тип за движок (канон, схема `BankTerm.kind`). У движка та же строка несёт `Type:
|
||||
""` (`membank/memseed.go:323-326`), поэтому **проекция `""` → `null` — работа платформы**; это
|
||||
единственное место, где пустая строка и `null` означают одно, и потому оно записано.
|
||||
|
||||
**Фантом `auto` в провенансе убран ещё в 0.2.0:** комментарий схемы движка (`migrate.go:193`:
|
||||
`seed|ruby|auto`) устарел — `"auto"` пишет СТАТУС, не провенанс (`membank/memseed.go:328`:
|
||||
`Status:"auto", Source:"ruby"`), майнинг ставит `mined` (`pipeline/mining.go:424`).
|
||||
|
|
@ -473,7 +506,32 @@ column» (`00002_readmodel.sql:188-199`).
|
|||
|
||||
`return_to` принимает ТОЛЬКО путь этого сайта, и чужой путь сервер молча заменяет дефолтом
|
||||
(`login.go:safeReturnTo`). Обе `POST`-ручки лежат на cookie-пути, то есть требуют `X-TM-Client`.
|
||||
Отказ входа — `problem+json`.
|
||||
|
||||
**Отказ входа — `problem+json`, как везде; различать причины отказа клиент не может по замыслу.**
|
||||
⚠ Эта фраза стояла здесь в 0.2.3, была молча срезана коммитом лендинга батча `8d82096` (ловится
|
||||
`git log -S`) и **восстановлена синком 20.08** — не как переоткрытый спор, а как правило, которое
|
||||
никто не отменял. Разбор потери — §6в п.0.
|
||||
|
||||
**Что это значит для клиента, дословно (синк 20.08, п. C записки платформы).** `/auth/*` отвечает
|
||||
тем же конвертом, что и `/v0`, но БЕЗ машинного `code` — и это решение, а не пробел: клиент
|
||||
диспетчеризует по статусу и показывает одну нейтральную фразу. Особый случай ровно один — `429`
|
||||
с `Retry-After`, который вход действительно отдаёт (`login.go:173,233`, `dev.go:138`); ветвление по
|
||||
нему это механизм HTTP по назначению, а не запашок.
|
||||
|
||||
Арифметика остатка, из-за которой словарь здесь не нужен: из шести статусов входа четыре уже имеют
|
||||
точные соответствия в `ErrorCode` (401 → `unauthenticated`, 400 → `invalid_request`,
|
||||
404 → `not_found`, 503 → `service_unavailable`), а из двух оставшихся `405` — баг клиента, не
|
||||
пользовательский случай. Обсуждался по существу ОДИН код.
|
||||
|
||||
**Проводного словаря в этом файле не объявляется, и это принципиально.** Компаньон объявлен не
|
||||
нормативным для формы; словарь, записанный здесь, сделал бы его нормативным с чёрного хода — форму
|
||||
стало бы нечем ни сгенерировать, ни отвалидировать, ни удержать линтером. Механическая половина
|
||||
разбора уже исполнена платформой без разрешения и правильно: `codeForStatus` снят
|
||||
(`grep -rn "codeForStatus" --include=*.go platform/` → 0).
|
||||
|
||||
**Триггер пересмотра, записанный явно:** в день, когда на входе появится ВТОРОЙ пользовательски
|
||||
осмысленный случай — «аккаунт отключён» против «провайдер лёг», — эта поверхность получает
|
||||
собственный нормативный документ (маленький OpenAPI на `/auth/*`), а не таблицу в компаньоне.
|
||||
|
||||
### 2.15. Потолок прогона — ◆ форма, РАТИФИЦИРОВАНА оркестратором №15 (08.08)
|
||||
|
||||
|
|
@ -584,6 +642,12 @@ the original request was never applied». Каждый `POST /books` созда
|
|||
Окно 24 часа — не замер, а достаточная граница для клиентского ретрая; сокращать его дешевле, чем
|
||||
удлинять, поэтому взята нижняя обещаемая граница («не менее»).
|
||||
|
||||
**0.4.0 сменил ОДНУ половину — что такое «тот же запрос» на multipart** (§6в E): не объявленные
|
||||
части плюс длина, а метаданные + имя файла + СОДЕРЖИМОЕ, и **сервер, который тождества установить не
|
||||
может, не реплеит, а отвечает `409`**. Остальное выше в силе. ⚠ Формулировка §6б ниже («тождество
|
||||
считается по объявленным частям», дофикс ФБ-8 16.08) этой правкой ПЕРЕКРЫТА и оставлена только как
|
||||
история — читать по канону.
|
||||
|
||||
---
|
||||
|
||||
## 3. Зависимости: чтение → источник → строка бэклога
|
||||
|
|
@ -598,7 +662,7 @@ the original request was never applied». Каждый `POST /books` созда
|
|||
| `GET /books`, `GET /books/{id}` | read-модель платформы | ПОСТРОЕНО | — |
|
||||
| `GET /books/{id}/run-options`, `POST /runs` | шкала + холды | ПОСТРОЕНО | — |
|
||||
| `POST /runs/{id}/stop` | реконсилятор | ПОСТРОЕНО (кнопки на экране нет) | зона фронта |
|
||||
| `POST /runs/{id}/resume` | реконсилятор | ПОСТРОЕНО, но **поведение на паузе по потолку 0.3.0 МЕНЯЕТ**: сегодня платформа отвечает `202` и возвращает прогон в то же состояние (`runs/reconcile.go:906` — сама `Resume`; ветка `case "paused"` `:919` доходит до `reopen`, 409 только на дневном потолке движка `:931`), а контракт теперь требует `409` `run_not_resumable` · `cause.code: ceiling_reached`. Молчаливый `202` на действие, которое ничего не сделало, — ровно то, от чего предупреждает собственный комментарий платформы; клиенту нечем отличить успех от no-op. Полная таблица по статусам — в описании `resumeRun` | **вход P7** (правка построенного пути, не только читающей поверхности) |
|
||||
| `POST /runs/{id}/resume` | реконсилятор | ПОСТРОЕНО, но **0.3.0 сменил поведение на паузе по потолку, а 0.4.0 — на ИСЧЕРПАННОМ прогоне**. Пауза: платформа уже отвечает `409` `ceiling_reached` (`runs/reconcile.go`, ветка `case "paused"` в `Resume`) — совпало. Исчерпанность: платформа отвечает `202` и возвращает прогон в то же состояние (`reconcile.go`, оба выхода `exhausted` из `reopen` → ветка `exhausted` в `Resume` → `httpapi/v0.go`, хендлер `resumeRun`), а контракт теперь требует `409` `run_not_resumable` · `cause.code: ceiling_reached`. Молчаливый `202` на действие, которое ничего не сделало, — ровно то, от чего предупреждает собственный комментарий платформы; клиенту нечем отличить успех от no-op, а на `awaiting_bank` это ещё и не вызывает `ReleaseBankStop`. Полная таблица по статусам — в описании `resumeRun` | **вход P7** (правка построенного пути, не только читающей поверхности) |
|
||||
| `GET /usage` | кредиты | ПОСТРОЕНО, **не читается ни одним экраном** | зона фронта |
|
||||
| `GET /capabilities` | конфигурация деплоя | НЕ ПОСТРОЕНО (заведено 0.3.0) | вход P7 |
|
||||
| `PATCH`/`DELETE /books/{id}`, `GET /runs/{id}` | колонки есть | НЕ ПОСТРОЕНО (заведено 0.3.0) | вход P7 |
|
||||
|
|
@ -615,6 +679,10 @@ the original request was never applied». Каждый `POST /books` созда
|
|||
| `Note.code` как enum спеки | карта приложения А | не enum, пока не написаны фразы | строка 148 (фразы владельца) |
|
||||
| Настоящие названия глав (`Chapter.heading` ≠ null) | парсер структуры | НЕ ПОСТРОЕНО | строка 160 (Этап 0) |
|
||||
| `title_raw` / `kind` (глава ↔ фрагмент) | дизайн-пак структуры глав | передано паку, аддитивно | строка 161 |
|
||||
| `ErrorCode.content_refused` (400) **и** `RejectReason.content_refused` | прескрин злоупотреблений | НЕ ПОСТРОЕН ни на одной стороне: в платформе только объявление константы (`httpapi/problem.go:42,94`), `ContractRejectReason` (`ingest/vocabulary.go:59-69`) его не отображает; в движке отказ провайдера живёт как ПРИЧИНА ЗАМЕЧАНИЯ (`disposition.go:60-63` → `Note.code: content_withheld`) и в exit-контракт не выходит — мостá между двумя словарями нет | **строка 94 (ПТ-16)**; там же ограничение числа попыток аккаунта — обязанность падает ВМЕСТЕ с производителем, не раньше |
|
||||
| `decline` в подписи банка доезжает до работы | движок читает файл `mined_rejects` (`pipeline/mining.go`), платформа его не пишет | решение ЗАПИСЫВАЕТСЯ (таблица `bank_decisions`) и на следующем прогоне НЕ применяется — отклонённый термин уезжает авто-строкой | **строка 192** (отложена владельцем); канон предупреждает на `BankDecision.action` |
|
||||
| Снятие замечания (переход «флаг снят») | движок | **НЕДОСТИЖИМО сегодня, проверено чтением движка** — п. H §6в | **PD-298** регистра платформы (`platform/docs/DEFECT_REGISTER.md`) — там строка и живёт; механизма не строим |
|
||||
| Счёт «сделанного» на деплое без второго прохода | платформа | канон 0.4.0 определил «сделано» = последний проход ЭТОГО деплоя; проекция платформы считает жёстко второй проход | **вход P7** (PD-202) |
|
||||
|
||||
⚠ **Три прежних предупреждения СНЯТЫ как устаревшие** (Б-7а), и это причина, по которой заведена
|
||||
таблица выше:
|
||||
|
|
@ -808,7 +876,7 @@ the original request was never applied». Каждый `POST /books` созда
|
|||
`required: true` стоит при живом исключении для bearer, потому что условной обязательности по схеме
|
||||
безопасности OpenAPI не выражает, и валидатор не должен читать законное отсутствие как нарушение ·
|
||||
список отказов интейка помечен неисчерпывающим (авторитет — `code` ответа) · правило резолюции
|
||||
`Location` · тождество `Idempotency-Key` на multipart считается по объявленным частям, а `408` не
|
||||
`Location` · ⚠ (ПЕРЕКРЫТО 0.4.0, см. §2.18) тождество `Idempotency-Key` на multipart считается по объявленным частям, а `408` не
|
||||
считается состоявшейся попыткой · `min_chapters` при `max_chapters: 0` — не диапазон, клиент проверяет
|
||||
максимум первым · порядок пяти носителей «нет кредита» · «carried forward marked as unverified» — снято
|
||||
как обещание без носителя, заменено на наблюдаемое (`BankPage.signed` против `total`) · `409` у
|
||||
|
|
@ -823,6 +891,405 @@ the original request was never applied». Каждый `POST /books` созда
|
|||
«companion К-6»): описания компилируются в исходники клиента, и ссылка на ревью в чужом репозитории
|
||||
там — мусор; носители остались здесь.
|
||||
|
||||
## 6в. Синк с платформой 20.08 → канон 0.4.0: что решено и почему
|
||||
|
||||
Вход — `platform/docs/CONTRACT_SYNC_FROM_PLATFORM.md`, четырнадцать мест, где провод сообщает
|
||||
состояние и не даёт клиенту действия. Записка честно разделяет ФАКТ / ⚠ МНЕНИЕ ПЛАТФОРМЫ / ГРАНИЦА,
|
||||
и разбор шёл по этой границе: ФАКТы перепроверены исполнением, МНЕНИЯ проверялись на ПОСЫЛКУ, а не
|
||||
только на предложенную форму. Два мнения посылку не выдержали (B, часть A), одно оказалось
|
||||
слабее собственного факта (L о метках глав).
|
||||
|
||||
⚠ **База ссылок этого раздела — РАБОЧЕЕ ДЕРЕВО 20.08, а не HEAD.** В `platform/` на этот момент 81
|
||||
незакоммиченный файл (P7 в работе), поэтому одна и та же строка в HEAD и в дереве разная, и ссылка
|
||||
без базы проверяема только случайно. `backend/` чист, его ссылки годятся в обоих. Там, где
|
||||
конструкция переживёт любую перенумерацию, адрес дан ИМЕНЕМ функции или ветки — это дешевле и
|
||||
надёжнее номера. Поймано ревью диффа: первая редакция §6в смешивала обе базы в одной строке таблицы.
|
||||
|
||||
### п.0. Сверка на потери прозы 0.2.3 → 0.3.0 — исполнена; найдена ОДНА потеря, она и была заявлена
|
||||
|
||||
Метод: полный дифф лендинга `8d82096` по обоим файлам (300 удалённых строк компаньона, 597 канона),
|
||||
чтение ВСЕХ удалённых строк, выделение из них утверждений-правил и проверка каждого на наличие
|
||||
смыслового двойника в живых файлах.
|
||||
|
||||
⚠ **Отдельно про метод, потому что он чуть не дал ложный результат.** Первый проход греп-проверки
|
||||
шёл построчно и дал шесть «пропаж», которых нет: правило переносится через перенос строки, и
|
||||
`grep "fewer rows than asked"` не находит текст, где `fewer` заканчивает строку. Сверка пере-ранена
|
||||
на файлах, склеенных в одну строку (`tr '\n' ' '`), — и все шесть нашлись на месте. Тот же класс
|
||||
ошибки в этой сессии уже ловился однажды; ставлю его сюда как метод, а не как случай.
|
||||
|
||||
Итог: **единственная потерянная НОРМА — фраза §2.14** («различать причины отказа клиент не может по
|
||||
замыслу»), восстановлена. Остальное удалённое — три класса, и ни один не является потерей правила:
|
||||
|
||||
- **правило уцелело в КАНОНЕ, а канон нормативен** — `kind`/`null` («MUST NOT drop it or invent a
|
||||
kind»), «MUST NOT clamp it again», «stale read is the CLIENT's duty», «catch-up reads `>=`»,
|
||||
«assembling a book's text on the client is forbidden», «pairs are read PER CHAPTER», уникальность
|
||||
термина по пятёрке, «a phrase MUST exist for a reason we do not know yet», `no-store`, CSRF,
|
||||
запрет реплея истории, «`max_chapters: 0` = экран исчерпанности». Один из них — правило пустого
|
||||
`kind` — уцелел в каноне, но потерял здесь ПРОЕКЦИЮ (`""` → `null` — работа платформы); клауза
|
||||
возвращена в §2.8;
|
||||
- **утверждение снято ОСОЗНАННО и с записанным основанием** — «carried forward marked as
|
||||
unverified» (ФБ-8, обещание без носителя), «замена в `failed` запрещена» (ужато до «не `failed`»),
|
||||
«Required: HTTP/2 at the edge» (Б-16, RFC 9205), пофазные счётчики, `EventCeiling`, `genre`,
|
||||
`finalizing`, пример `«CJK leak … 第一节»` (он и был утечкой, §2.12);
|
||||
- **пропала МОТИВИРОВКА, а не норма** — «1.9 юнита на главу», «иначе строки выглядят дубликатами и
|
||||
удаляются», «portable to the desktop client», `Intl.DisplayNames`. Это цена резки прозы, принятая
|
||||
осознанно; ни одно не меняет того, что обязана делать сторона.
|
||||
|
||||
**Вывод, который стоит записать отдельно.** Приёмка батча сверяла мультимножество модальных глаголов
|
||||
и этот класс структурно не ловила — но пере-сверка показала, что улов класса равен одному. Отсюда
|
||||
дешёвый гейт вместо дорогого: при следующей резке прозы сверять не модальность, а **список
|
||||
собственных имён формы** (полей, значений, кодов, заголовков) — норма почти всегда стоит рядом с
|
||||
именем, а фраза §2.14 пропала именно там, где имени не было (`/auth/*` — не поле). Носитель: эта
|
||||
запись.
|
||||
|
||||
### A. Прогон с исчерпанным бюджетом (PD-282) — ПРАВЛЮ КАНОН; половину мнения отклоняю
|
||||
|
||||
**Факт подтверждён и оказался шире записки.** `reopen` даёт `exhausted` в ДВУХ местах, а не одном
|
||||
(`runs/reconcile.go`): `remaining <= 0` и `ErrInsufficientCredit` от `RestartRun` — то есть
|
||||
«кончился потолок прогона» и «нечем взять холд на счёте» уже сегодня схлопнуты в один вердикт, и
|
||||
`Resume` отвечает на оба одинаково: `202` + неизменённый `Run` (`httpapi/v0.go`, хендлер
|
||||
`resumeRun`). `paused` до этой ветки не доходит — он отказан выше, поэтому молчаливый `202` живёт
|
||||
ровно на `stopped` и `awaiting_bank`. Худший из двух — `awaiting_bank`: человек подписал банк,
|
||||
нажал «продолжить», получил «принято», а `ReleaseBankStop` не вызывался (зона это записала сама,
|
||||
`P7_ACCEPTANCE_HANDOFF.md` §7(и)).
|
||||
|
||||
⚠ **Номера строк в чужой зоне в этом разделе сняты намеренно** (находка ревью): дерево платформы
|
||||
на 20.08 держит незакоммиченные правки, поэтому одна и та же строка в HEAD и в рабочем дереве
|
||||
разная, и половина ссылок первой редакции указывала в одно дерево, половина — в другое. То же
|
||||
лечение, что у приложения А: адресуемся именем функции и ветки.
|
||||
|
||||
**Правка канона (§resumeRun).** Записана ВТОРАЯ ОСЬ: исчерпанность лимита прогона перебивает обе
|
||||
строки `202`; ответ — `409` `run_not_resumable`, `cause.code: ceiling_reached`, лечение — новый
|
||||
прогон. Новых значений enum не заводится: `ceiling_reached` уже есть и его собственный текст этот
|
||||
случай описывает дословно. Тем же ходом канон получил обратную гарантию, которой не имел:
|
||||
**`202` теперь означает, что работа действительно переоткрыта**, и клиенту не нужно второе чтение,
|
||||
чтобы отличить успех от no-op. И записано разделение, о котором предупреждает сам канон у
|
||||
`AccountHaltReason`: пустота СЧЁТА не сворачивается в `ceiling_reached` — у неё своя причина
|
||||
`credit_unavailable` (аддендум ниже), а состояние аккаунта ЦЕЛИКОМ по-прежнему говорит `Usage`, не
|
||||
этот вызов. *(Первая редакция раздела писала здесь «пустой счёт — не ответ этого вызова», что
|
||||
противоречит и аддендуму, и канону; испр. оркестратором при ратификации.)*
|
||||
|
||||
**Признак «этот прогон ещё можно продолжить» на `Run` — ОТКЛОНЁН, с оговоркой.** Посылка «платформа
|
||||
знает его точно» верна, посылка «дёшево» — нет: `ReadRun` — один запрос по `books join runs`
|
||||
(`pgstore`, функция `ReadRun`), а флагу нужны ТРИ чтения, которых в нём нет — `RunSpent`,
|
||||
`AttemptReservationOpen` и баланс счёта. `Run` сериализуется в карточке книги, а карточку канон
|
||||
велит перечитывать НА КАЖДЫЙ кадр потока («it is still read on a frame, on navigation and on
|
||||
focus»), то есть цена платится на горячем пути ради факта, который
|
||||
(а) устаревает к моменту клика и (б) уже отвечен ратифицированным порядком чтения пяти мест
|
||||
(§RunOptions): `max_chapters == 0` + `blocked` — это ровно «стоит ли предлагать старт сейчас и что
|
||||
мешает», и рисует его тот же экран. Авторитет по-прежнему у мутации, как канон и объявляет.
|
||||
⚠ Поправка ревью к предыдущему абзацу: сам `Run` в КАДРЕ не едет — ни один payload его не несёт;
|
||||
горячий путь создаёт не кадр, а предписанная им перечитка карточки. Вывод не меняется, носитель
|
||||
цены — другой; первая редакция называла его неверно.
|
||||
**Триггер пересмотра:** замер, показывающий, что мёртвый клик частый, ЛИБО сворачивание трёх чтений
|
||||
в коррелированные подзапросы того же запроса (агент проверил — возможно). Тогда форма — `Run`,
|
||||
поле, минор.
|
||||
|
||||
### B. Суточный потолок движка — ОТКЛОНЯЮ ОБЕ ФОРМЫ; посылка не выдержала проверки
|
||||
|
||||
Записка просит слово или признак для паузы, которую деньги не лечат. Проверка посылки:
|
||||
|
||||
1. **Владелец уже решил этот вопрос — 15.08, D39.132 п.2а.** `day_usd` УБРАН из платформенного
|
||||
шаблона книги; трата прогона ограничена купленным объёмом (холд + `--ceiling-usd`), дневная ось
|
||||
на платформе объявлена дублирующей, в движке остаётся ОПЕРАТОРСКОЙ опцией, а обработка
|
||||
`daily_ceiling`/409 — предохранитель. Там же дословно: «**PD-199 закрыт этим же решением: `null`
|
||||
на проводе подтверждён, слово в контракт не заводится**». Заводить его сейчас значило бы
|
||||
опрокинуть ратификацию пятидневной давности ради случая, который зона сама пометила ГРАНИЦЕЙ
|
||||
(«живьём не воспроизводилось»). Проверено: платформа это поле **не заводит** — её рендер
|
||||
`book.yaml` проставляет ровно пять строковых ключей, «the whole of what this platform claims
|
||||
about the engine's schema» (`books/render.go`, функция `render`), и потолки в их число не входят.
|
||||
⚠ Уточнение ревью, и оно важнее исходной формулировки: «не заводит» ≠ «не пишет». Шаблон деплоя
|
||||
пере-маршалится целиком, поэтому оператор, положивший `ceilings.day_usd` в СВОЙ шаблон, получит
|
||||
это поле в каждой книге, которую платформа создаст (запинено `render_test.go` — «the operator's
|
||||
ceilings did not survive»). Именно поэтому триггер ниже сформулирован через шаблон, а не через
|
||||
код платформы: настройка живёт в артефакте деплоя, и вернуться она может без единой правки Go.
|
||||
2. **Предложенная форма неверна по СМЫСЛУ, даже если бы случай был.** «Лечится покупкой / не
|
||||
лечится» — булево, а суточный потолок это ОКНО: он снимается сам в полночь UTC
|
||||
(`store/ledger.go:60-72`, `todayUTC()`). Клиент, прочитавший «не лечится», спрячет кнопку
|
||||
навсегда там, где честный ответ — «не сейчас». Булево, у которого один из двух исходов врёт,
|
||||
хуже, чем отсутствие поля.
|
||||
3. **Второе значение enum — та же проблема плюс своя:** оно называет настройку чужого файла на
|
||||
проводе, что запрещает правило §5 («на проводе нет движковых словарей»), и зона сама этого не
|
||||
хочет.
|
||||
|
||||
**Что записано вместо этого — одна фраза в каноне (§resumeRun).** «Новый прогон легален при любом
|
||||
`paused`» осталось нормой, но перестало читаться как обещание прогресса: деплой может держать
|
||||
СВОИ лимиты, которых этот контракт словом не называет, и прогон под таким лимитом останавливается
|
||||
так же; клиент всё равно предлагает новый прогон — лучшего действия у него нет, — но не подаёт его
|
||||
как гарантию, а повторный `paused` это законный исход, а не сбой. Цена ошибки при этом наблюдаемая
|
||||
и самокорректирующаяся: холд возвращается, провайдерских денег не теряется (ФАКТ записки).
|
||||
|
||||
**Триггер пересмотра, записанный явно:** день, когда `day_usd` вернётся в платформенный шаблон
|
||||
книги. Тогда это настоящая дыра и контракт открывается заново.
|
||||
|
||||
⚠ **Попутно найдено и стоит знать зоне:** суточный потолок — НЕ единственный стоп, который деньги
|
||||
не лечат. Их четыре класса (`cmd/tmctl/main.go:52-79`): подпись банка (exit 3), graceful stop
|
||||
(exit 5), полоса отказов 10–19 и суточный потолок (exit 4, scope `day`). Просто три из четырёх уже
|
||||
имеют свою проекцию (`awaiting_bank`, `stopped`, `failed` + `RunFailureReason`), и «денег не
|
||||
хватает» из них не следует ни для одного. То есть класс закрыт, а дыра — только в четвёртом.
|
||||
|
||||
### C. Вход `/auth/*` — ИСПОЛНЕНО (§2.14)
|
||||
|
||||
Фраза восстановлена, вариант A уточнённый записан целиком, триггер пересмотра зафиксирован, словарь
|
||||
в компаньоне НЕ объявлен. Разбор потери — п.0 выше. Возражение зоны про «нормативность с чёрного
|
||||
хода» принято дословно и стало основанием.
|
||||
|
||||
### D. `Note.code` против пустой клетки — ПРАВЛЮ ОБА ФАЙЛА
|
||||
|
||||
Дыра реальна и была острее, чем в записке: канон не просто требует поле, он называет `Note.code`
|
||||
«a closed vocabulary of this version», а карта в приложении А оставляла клетку пустой — то есть
|
||||
сервер обязан прислать значение, которого словарь не содержит. `unspecified` ратифицирован как
|
||||
ОБЯЗАННОСТЬ сервера (не строка словаря причин), записан и в канон, и в приложение А, п.5. Довод
|
||||
записки про независимый выпуск принят. Прецедент D39.144 опорой не служит — согласен, его довод
|
||||
(«спека ещё никем не потреблена») больше не верен.
|
||||
|
||||
**Родственный вопрос про ступени — НЕ решаю:** К-6 открыт, слово владельца 16.08 — «решим потом».
|
||||
Но провизорность построенного зафиксирована в приложении А, п.6, чтобы `ingest/notes.go` не
|
||||
читался как ратифицированная карта.
|
||||
|
||||
### E. Отпечаток интейка (PD-262) — ПРАВЛЮ КАНОН, форму НЕ трогаю
|
||||
|
||||
ФАКТ подтверждён дословно: `intakeFingerprint` берёт `r.ContentLength` (`httpapi/v0.go:511-517`), и
|
||||
комментарий рядом (`:503-510`) сам называет оба остатка. Из двух исходов один безопасный (ложный
|
||||
`key_reused` на другой границе multipart — честный ретрай отвергнут), второй нет: chunked-клиент
|
||||
даёт `"unknown"` обоим запросам, и **две разные книги под одним ключом реплеят первую** — то есть
|
||||
ключ возвращает ответ, принадлежащий чужому запросу. Это нарушение самой гарантии, ради которой
|
||||
ключ существует.
|
||||
|
||||
**Выбрано: привести фразу канона к тому, что сервер может сравнить, БЕЗ поля размера в форме.**
|
||||
Записано: «тот же запрос» = те же метаданные + то же ИМЯ файла + то же СОДЕРЖИМОЕ; чем сервер
|
||||
устанавливает последнее — его дело (дайджест на лету стоит ноль, книгу в памяти держать не надо);
|
||||
обрамление multipart и длина тела запросом НЕ являются; **сервер, который не может установить
|
||||
тождество, не реплеит, а отвечает `409` `idempotency_conflict`**. Почему не поле размера: оно
|
||||
дало бы клиенту ещё одно обязательное поле, не закрыв случай «две разные книги одной длины», и
|
||||
всё равно потребовало бы сверки с фактически прочитанным. Механизм отката для этого уже построен
|
||||
и назван зоной верно (`trailingParts` `v0.go:536-563` → `books.go:190-204`); регистр зоны сам
|
||||
называет полное лечение «сверка принятого дайджеста на завершении с откатом» — контракт теперь это
|
||||
разрешает вместо того, чтобы описывать деталь, которой в форме нет.
|
||||
|
||||
### F. `ETag`/`304` на любом безопасном чтении — ПРИНЯТО, канон правится одной фразой
|
||||
|
||||
Зона права дважды: RFC 9110 валидатор на любом GET разрешает, и список исключений в слое записи
|
||||
протух бы за пак. Записано в шапке канона: валидатор на любом безопасном чтении легален и
|
||||
объявления не требует; операции, которые его объявляют, — те, где клиенту есть смысл им
|
||||
пользоваться, а не исчерпывающий список. Плюс две границы, чтобы фраза не стала лицензией:
|
||||
клиент НИКОГДА не обязан слать `If-None-Match`, а `304` бывает ответом только на присланный.
|
||||
`getUsage`/`getRunOptions` в объявленные НЕ добавлены сознательно — оба читаются ровно перед
|
||||
действием, и условное чтение там не даёт ничего, кроме риска показать устаревшую шкалу.
|
||||
|
||||
### G. Draft-only конвейер (PD-202) — ОТВЕЧАЮ: ДА, начерновленная глава «сделана»; правлю канон
|
||||
|
||||
Проверено чтением обеих зон, и цена выше заявленной. У движка деплой без второго прохода — не
|
||||
экзотика: разбиение выводится из РОЛЕЙ стадий (`pipeline/waverun.go:43-53`), конфиг требует лишь
|
||||
«хотя бы одна стадия» (`config/pipeline.go:841`), ветка живая и покрыта тестом
|
||||
(`contractblockers_test.go:115-136`); в репозитории такого конфига пока нет, но выбирает его
|
||||
оператор, и платформа шаблон не читает. У платформы `units_done` — жёстко второй проход
|
||||
(`pgstore/sink.go:252-263`, `readmodel.go:200-208`).
|
||||
|
||||
**Следствие, которого в записке нет:** ноль навсегда — это не только короткая полоса.
|
||||
`Book.chapters_done` и `ChaptersLeft` (`pgstore`, функции `chaptersDone` и `BookRunContext`) читают
|
||||
ту же колонку, а `ChaptersLeft` подрезает ШКАЛУ покупки. То есть на таком деплое сервис бесконечно
|
||||
предлагает купить главы, которые уже переведены. Это выводит вопрос за рамки «косметика полосы» и
|
||||
делает ответ обязательным.
|
||||
|
||||
⚠ **Первая редакция этого абзаца дописывала «и берёт за них деньги» — снято ревью как неверенное,
|
||||
и снято ИЗ КАНОНА тоже** (там оно уже стало нормативным текстом, компилирующимся в JSDoc клиента).
|
||||
Что проверено: платформа держит ПОТОЛОК, а не цену, и рассчитывается по ЗАМЕРЕННОЙ трате, а
|
||||
неизменённая уже начерновленная книга переигрывается с чекпойнтов за $0 — то есть в обычном случае
|
||||
холд возвращается целиком и не берётся ничего. Плата возникает только если между прогонами сдвинулся
|
||||
снапшот (слаг модели, промпт, langpack, флип гейта), и тогда пере-покупка действительно оплачивает
|
||||
переведённое заново. Довод «предлагают уже сделанное» стоит сам по себе и в поправке не нуждается;
|
||||
денежный довод был сильнее фактов, и это ровно тот класс ошибки, который у меня уже ловился.
|
||||
|
||||
**Ответ контракта.** «Сделано» = последний проход, который эта книга на ЭТОМ деплое реально
|
||||
получает. Записано в `Progress` и в `Book.chapters_done` — вместе с доводом: контракт не говорит,
|
||||
сколько проходов бывает, значит и назвать один из них для счёта не может, а обещание «дробь
|
||||
доходит до единицы» — это обещание, а не проходы. Имён волн в тексте нет (гейт §5 пере-ран, чисто).
|
||||
Форма не меняется, генерённые типы не двигаются; работа — проекция платформы, вход P7.
|
||||
|
||||
### H. Снятие замечания (PD-298) — СВЕРЕНО С ДВИЖКОМ ПЕРВЫМ, как просила зона; механизма НЕ строим
|
||||
|
||||
Порядок соблюдён: сначала чтение движка. Результат — **переход достижим у драйвера и НЕ достижим на
|
||||
проводе**. `flagged` вычисляется на каждой эмиссии, а не хранится (`pipeline/events.go:351,377-379`,
|
||||
`waverun.go:139-140,205-206`), и `redrive`/`--resnapshot`/правка исходника пере-атакуют юнит, так
|
||||
что второе разрешение законно может выйти `flagged=false`. Но эмиссия идёт через
|
||||
announce-once-леджер: ключ `unit:<book>:<wave>:<chapter>:<unit>` не несёт ни снапшота, ни прогона
|
||||
(`events.go:399-401`), `EnqueueOnce` вторую строку не пишет (`store/outbox.go:74-106`), леджер
|
||||
переживает прогоны (`outbox.go:205-212`) и запинен тестом
|
||||
(`runevents_test.go:242-246`). Единственное окно — падение между записью строки в журнал и
|
||||
пометкой (`events.go:154-164,200-202`); плюс `redrive` платформа не вызывает вовсе
|
||||
(`runner/engine.go:83-124`).
|
||||
|
||||
**Поэтому: строка «недостижимо» в регистр, а не механизм** — ровно тот исход, который зона назвала
|
||||
правильным. Но в каноне закрыт РАЗРЫВ ПРАВИЛА, который эта проверка обнажила: §AfterVersion говорил
|
||||
«удаление так не выразить» только про ОПТОВУЮ замену, а про исчезновение ОДНОЙ строки не говорил
|
||||
ничего. Записано: строка, once delivered, поодиночке не отзывается; коллекция, которая обязана
|
||||
потерять строку, теряет её единственным выразимым способом — заменой целиком с `resync_required`.
|
||||
Это обязанность сервера, а не пожелание, и она делает поведение платформы выводимым: сегодня при
|
||||
`flagged=false` строка обновляется и ревизия двигается (`pgstore/sink.go:237-244`), дельта её
|
||||
прячет (`readmodel.go:725-735`), а `note_count` в кадре главы уже УМЕНЬШАЕТСЯ (`sink.go:310-328`) —
|
||||
то есть клиент увидел бы счётчик, противоречащий списку. Теперь ясно, что должно произойти вместо
|
||||
этого.
|
||||
|
||||
### I. `content_refused` — forward-looking, НО с носителем; правлю канон и §3
|
||||
|
||||
Ответ на вопрос зоны: **forward-looking, долг зона не заводит.** Значение ратифицировано К-9
|
||||
(владелец 16.08) до того, как появился производитель, и это сознательно: форму отказа лучше решить
|
||||
не под давлением. Проверено — производителя нет ни на одной стороне (в платформе только константа и
|
||||
строка таблицы; в движке отказ провайдера живёт как ПРИЧИНА ЗАМЕЧАНИЯ и уезжает как
|
||||
`Note.code: content_withheld`, `ingest/notes.go:45-48`, — другой словарь, моста нет).
|
||||
|
||||
Правки: (а) оба значения помечены в каноне как объявленные ДО производителя, с причиной;
|
||||
(б) **обязанность ограничивать число попыток аккаунта пере-привязана**: она падает ВМЕСТЕ с
|
||||
производителем, а не висит на сервере, который отказа не выдаёт, — иначе это обязательство без
|
||||
адресата, ровно тот класс, из-за которого заведено правило Б-21; (в) §3 получил строку с носителем
|
||||
(строка 94, ПТ-16). Клиент обрабатывает код с сегодня — старый клиент, встретивший его впервые, и
|
||||
есть та беда, ради которой значение объявлено заранее.
|
||||
|
||||
### J. «Остановлено по вашей просьбе» — ПОЛЕ ЗАВЕДЕНО; форма отличается от предложенной зоной
|
||||
|
||||
Решение владельца 17.08 принято, форму делегировали контрактной сессии
|
||||
(`P7_ACCEPTANCE_HANDOFF.md:452`). Заведено `Run.stop_requested` — **обязательное булево, а не
|
||||
необязательное.** Довод: функция тотальна — прогон, который никто не просил остановить, это `false`,
|
||||
а не «поля нет»; необязательность дала бы два написания одного факта, и это ровно тот дефект, который
|
||||
контракт уже чинил у `sense` (0.2.2), у `reject_reason` и у `kind`. Ценой стал ломающий минор —
|
||||
`Run` получил обязательное поле, — и он объявлен честно в шапке компаньона, вместо того чтобы
|
||||
называть правку «аддитивной» и оставить генератор доказывать обратное. В описании записано и то,
|
||||
чего в заказе не было: флаг — запись ПРОСЬБЫ, а не исхода, и остаётся `true` на прогоне, который
|
||||
успел доработать, потому что это и есть случай, который экран обязан объяснить.
|
||||
|
||||
### K. `--verify-bank` — НЕ КОНТРАКТ, релей в зоны; но контрактно видимая половина закрыта
|
||||
|
||||
Расхождение «D39.144 (подпись = один акт) против пер-термного гейта движка» — ратификация и
|
||||
поведение движка, обе вне этого файла: контрактная половина уже исполнена (D39.144/145 сняли гейт
|
||||
полноты из канона и компаньона, `409` `bank_decisions_incomplete` удалён). Обход зоны (не передавать
|
||||
флаг на `resume`) — её решение и её же декларация.
|
||||
|
||||
**Контрактно видимая половина — вторая, и она молчала.** Канон обещал `decline` — «leave it out»,
|
||||
а решение до движка не доезжает (`mined_rejects` платформа не пишет), то есть отклонённый термин
|
||||
возвращается предложенным. Это обещание пользователю, которого система не держит. Записано
|
||||
предупреждением на `BankDecision.action` + строкой §3 с носителем — строка 192, отложенная
|
||||
владельцем. Форму не меняю: обещание верное, не выполнена реализация.
|
||||
|
||||
### L. Мелкое
|
||||
|
||||
- **Ссылка приложения А протухла** — ИСПРАВЛЕНО, и не новым номером, а именем функции
|
||||
(`flagReasonSeverity`): номер строки в чужой зоне протухает за пак, что здесь и произошло.
|
||||
- **`heading` всегда `null`** — посылка «у читателя НЕТ меток глав вообще» **неверна**, и это тот
|
||||
случай, где сказалась объявленная зоной граница (фронт не смотрели). 0.3.0 снял запрет клиентской
|
||||
служебной метки: «Глава N» рисует КЛИЕНТ, в локали интерфейса, за $0 (§2.3, решение владельца
|
||||
09.08). Без меток остаётся только книга, у которой их нет в данных, — а это легальная книга.
|
||||
Продуктовый пробел «настоящих названий нет» держится видимым строкой §3 (строка 160) и никуда не
|
||||
делся; менять нечего.
|
||||
- **`410` на опечатку в id главы (PD-253)** — **канон изменён в сторону зоны, а не наоборот.**
|
||||
Требование различать «была и исчезла» от «такой не было» невыполнимо: id непрозрачны, после
|
||||
пере-разбора не хранятся, и доказать, что id никогда не минтился, можно только кладбищем всех
|
||||
выданных. Записано: `410` — ответ на id, которого нет в ТЕКУЩЕЙ структуре, был он когда-то или
|
||||
нет; `404` остаётся за книгой. Лечение у клиента в обоих случаях одно — перечитать дерево.
|
||||
Расхождение перестаёт быть расхождением.
|
||||
|
||||
### M. Что зона сделала сама — сверено, возражений нет
|
||||
|
||||
Все восемь пунктов сверены с каноном: область `Idempotency-Key` `(принципал, метод, путь)`,
|
||||
подрезание `limit`, `resync_required` вместо тихого старта, `blocked` только когда холд реально
|
||||
укорачивает шкалу, `heading: null`, `finalizing` снят, полоса и её база на одном проходе, снятие
|
||||
`codeForStatus`. Совпадает.
|
||||
|
||||
### N. Границы этой сессии
|
||||
|
||||
Закрыто из того, что зона объявила своей границей: пункт H сверен с движком (чтением), пункт G
|
||||
сверен с обеими зонами, совместимость с генерённым фронтом проверена исполнением
|
||||
(`openapi-typescript@7` — чисто; единственная ломающая правка названа). Пункт B живьём тоже не
|
||||
воспроизводился — и не требовался: он закрыт ратификацией, а не замером. Не проверялось: как
|
||||
экраны фронта рисуют новые ответы (зона заморожена), и поведение построенного пути `resume` после
|
||||
правки таблицы — это работа P7.
|
||||
|
||||
**Что забирает зона (P7):** `409` `ceiling_reached` на исчерпанном прогоне вместо `202` (A) ·
|
||||
`unspecified` как обязанность, а не самодеятельность (D) · тождество интейка по содержимому с
|
||||
откатом вместо `Content-Length` (E) · «сделано» = последний проход этого деплоя (G) ·
|
||||
`Run.stop_requested` в `projectRun` (J) · при снятии флага — `resync_required`, а не тихое
|
||||
исчезновение (H). **Что закрыто одной фразой в тексте и работы зоны не требует:** C, F, I, L.
|
||||
|
||||
### Ревью диффа этой зоны (исполнено этой же сессией, 20.08) — что оно изменило
|
||||
|
||||
Четыре независимые линзы по диффу: ХОЛОДНЫЙ ПОТРЕБИТЕЛЬ (модель другого семейства — fable; читал
|
||||
ТОЛЬКО финальный канон, без диффа, без компаньона, без записки платформы, и пытался реализовать
|
||||
шесть изменённых мест) · опровергатель ПОСЫЛОК диспозиций · регрессионная линза (умерло ли правило
|
||||
вместе с удалённой строкой + независимая пере-сверка 0.2.3 → 0.3.0 ДРУГИМ срезом: не по удалённым
|
||||
строкам, а по перечислению всех схем/полей/операций 0.2.3) · линза стандартов (RFC по
|
||||
первоисточникам). Каждая находка потом адверсариально верифицировалась отдельным агентом с
|
||||
установкой «по умолчанию опровергнуто». **31 находка, 5 подтверждено, 26 опровергнуто; 35 агентов,
|
||||
0 упавших** (важно: упавший агент — это НЕ «опровергнуто»).
|
||||
|
||||
Ни одна из пяти не опрокинула диспозицию — все пять о точности МОЕГО текста, и это правильный
|
||||
результат для ревью, которое ищет не «согласен ли я», а «проверяемо ли написанное»:
|
||||
|
||||
1. **Денежный довод в пункте G был сильнее фактов — и уже стоял НОРМОЙ в каноне.** «Предлагает
|
||||
уже сделанное и берёт за них деньги»: вторая половина не проверена и для обычного случая неверна
|
||||
(потолок, а не цена; расчёт по замеренной трате; неизменённая книга переигрывается за $0). Снято
|
||||
и из канона, и отсюда; довод «предлагает уже сделанное» стоит сам.
|
||||
2. **«Платформа физически не пишет `day_usd`»** — неверный глагол: не ЗАВОДИТ, но шаблон деплоя
|
||||
пере-маршалится целиком, и оператор может внести поле без единой правки Go. Поправка усиливает
|
||||
триггер пересмотра, а не отменяет отказ.
|
||||
3. **«`Run` едет в кадре потока»** — ложный факт: ни один payload `Run` не несёт. Цена реальна, но
|
||||
её носитель другой — предписанная кадром перечитка карточки.
|
||||
4. **Носитель строки H («регистр движка») — документ, которого нет.** Живой носитель — PD-298
|
||||
регистра платформы. Ровно то нарушение правила Б-21, которое этот же файл и вводит.
|
||||
5. **Ссылки в чужую зону мешали ДВЕ базы** (HEAD и рабочее дерево с 81 незакоммиченным файлом
|
||||
платформы) в одной строке таблицы. Лечение — база названа явно, адресация именами функций.
|
||||
|
||||
⚠ **Три опровержения я отклонил и правку внёс.** Три линзы независимо споткнулись об один шов —
|
||||
что отвечает `resume`, когда у прогона лимит ещё есть, а СЧЁТ не тянет холд. Каждую формулировку
|
||||
верификатор опроверг по отдельности («тексты можно прочесть согласованно»), но холодный потребитель
|
||||
прямо написал, что решить не может, а платформа этот случай уже схлопывает в тот же вердикт.
|
||||
Сходимость трёх независимых линз на одном месте — сигнал сильнее трёх поштучных опровержений.
|
||||
Заведено `cause.code: credit_unavailable` (второй уровень не закрыт по замыслу — типы не двигаются),
|
||||
`ceiling_reached` пере-сформулирован как «ЭТОТ прогон закончен», и явно сказано, что подменять друг
|
||||
друга они не могут. Тем же ходом закрыт вопрос холодного потребителя про `stop_requested` после
|
||||
`resume` (снимается) и возвращена клауза «предел попыток не на проводе», умершая с моей же правкой.
|
||||
|
||||
### Второе ревью: велосипеды и объём прозы (заказ владельца 20.08)
|
||||
|
||||
Первое ревью проверяло текст на соответствие RFC. Владелец задал ДРУГОЙ вопрос — «а так вообще делают,
|
||||
или ты изобрёл своё», плюс «не раздул ли ты прозу». Отдельная проверка: линза охоты за велосипедами
|
||||
(модель другого семейства, с обязательным чтением первоисточников через WebFetch) + линза размещения
|
||||
прозы, которой прямо сказали, что автор диффа склонен переобъяснять. **20 находок, подтверждена 1.**
|
||||
|
||||
**Велосипедов не подтверждено ни одного.** Проверялись против настоящих спек: тождество интейка
|
||||
против `draft-ietf-httpapi-idempotency-key-header` и **RFC 9530 (Digest Fields)** — формулировка
|
||||
«чем сервер устанавливает тождество, его дело» черновику СООТВЕТСТВУЕТ, а `Repr-Digest` предписать
|
||||
клиенту мы не можем и не должны · `credit_unavailable` под `409` — 402 зарезервирован, двухуровневый
|
||||
код конвенционален (Microsoft `innererror`, `google.rpc.ErrorInfo`) · `stop_requested` булевым против
|
||||
конвенции таймстемпа (k8s `deletionTimestamp`, AIP-165) — отклонено · `410` на никогда не
|
||||
существовавший id против RFC 9110 §15.5.11 — отклонено · «валидатор легален на любом безопасном
|
||||
чтении» против OAS 3.1 — отклонено · пер-строчные tombstone по образцу JMAP (RFC 8620) — не наш
|
||||
случай.
|
||||
|
||||
**По объёму — замер стоит, классификация не устояла.** Верификатор пере-ран замер пином зоны
|
||||
(`openapi-typescript@7.13.0`) и воспроизвёл его до байта: канон дал **+10 546 байт и +144 строк
|
||||
комментариев** в исходники фронта. Но гипотеза «62 % этого — обоснование, которому место здесь»
|
||||
пере-проверку не прошла: из тринадцати заявленных блоков выжил ОДИН — довод в `Progress` («контракт
|
||||
не говорит, сколько проходов бывает…»), который (а) дословно уже записан здесь, в §6в G, и (б)
|
||||
локально пере-выводит глобальное правило шапки канона. Срезано; правило осталось одной фразой.
|
||||
|
||||
Своим решением, а не по находке, срезан ещё денежный хвост у `Book.chapters_done` — он ничего не
|
||||
меняет в поведении клиента. Итог трима: 152 758 → 152 002 байта. **Урок для следующей правки канона:
|
||||
норма — в канон, довод — сюда; проверять не глазом, а диффом генерённого клиента.**
|
||||
|
||||
### Ратификация этой редакции — D39.152 (20.08, оркестратор №18)
|
||||
|
||||
> Текст ноты, сданный контрактной сессией, **исполнен и заменён живым**: ратификация записана в
|
||||
> `docs/architecture/05-decisions-log.md`, нота **D39.152**, строка реестра — `05-decisions-index.md`.
|
||||
> Черновик ноты, живший здесь, снят при лендинге (испр. оркестратором №18): он расходился с
|
||||
> итоговым текстом в двух местах, и две копии одной ноты — ровно тот распад «один носитель на
|
||||
> факт», против которого написана норма D39.112. **Что в живой ноте отличается от черновика:**
|
||||
> (1) внесены ЧЕТЫРЕ текстовые правки при ратификации (сеттер `stop_requested` в `stopRun` ·
|
||||
> стухшая фраза §6в A против собственного аддендума · «cleared by a resume that SUCCEEDS» ·
|
||||
> приписка третьего случая `resumeRun` к таблице); (2) клейм «ломающая правка ровно одна» уточнён:
|
||||
> он верен для диффа генерённых ТИПОВ и неточен поведенчески — против 0.3.0 расходятся ЧЕТЫРЕ
|
||||
> места (см. D39.152 п.5).
|
||||
---
|
||||
|
||||
## 7. Эксплуатационные примечания — НЕ норма контракта
|
||||
|
||||
Вынесено из спеки в 0.3.0 (Б-16). RFC 9205 §4.1 прямо про наш случай: «Requiring a particular version
|
||||
|
|
@ -881,6 +1348,16 @@ heartbeat · форма `id` кадра. Сжатие как ТРЕБОВАНИ
|
|||
карты.
|
||||
4. Последняя строка — не формальность: контракт обязан иметь фразу для причины, которой ещё не
|
||||
существует, и она обязана читаться нейтрально, а не как «ошибка».
|
||||
5. **Клетка кода последней строки БОЛЬШЕ НЕ ПУСТА — `unspecified`** (синк 20.08, п. D записки).
|
||||
Пустая клетка была невыполнима: `Note.code` в каноне обязателен и `minLength: 1`, движок и
|
||||
платформа выпускаются независимо, поэтому окно «пришла причина, которой этот билд не знает» —
|
||||
штатное. Платформа уже отдавала стабильный плейсхолдер `unspecified` (`ingest/notes.go`), не
|
||||
имея на него ратификации; синк её ратифицировал и записал в канон обязанностью сервера, а не
|
||||
строкой словаря. Бампа схемы не требует — `Note.code` объявлен `type: string`.
|
||||
6. **Столбец «Ступень» пуст, а платформа уже провела границу.** `ingest/notes.go` раскладывает
|
||||
девять движковых рангов на два проводных значения по правилу «потерял ли читатель текст». Это
|
||||
ПРОВИЗОРНО и ратификацией не является: К-6 открыт, ответ владельца 16.08 — «решим потом». Строка
|
||||
записана здесь, чтобы построенное не читалось как решённое.
|
||||
|
||||
| Причина движка | Ранг | Код контракта ◆ | Продуктовая фраза | Ступень |
|
||||
|---|---|---|---|---|
|
||||
|
|
@ -896,10 +1373,12 @@ heartbeat · форма `id` кадра. Сжатие как ТРЕБОВАНИ
|
|||
| `empty` | 6 | `empty_answer` | ⬜ | ⬜ |
|
||||
| `sanitizer_stripped` | 7 | `markup_cleaned` | плейсхолдер: «Служебная разметка вычищена автоматически» | ⬜ |
|
||||
| `upstream_not_ok` | 8 (по умолчанию) | `unavailable` | ⬜ | ⬜ |
|
||||
| незнакомая причина | 8 (по умолчанию) | — (клиент рисует нейтральную фразу по правилу схемы) | ⬜ нейтральная, НЕ «ошибка» | ⬜ |
|
||||
| незнакомая причина | 8 (по умолчанию) | **`unspecified`** (обязанность сервера, п.5; клиент рисует ту же нейтральную фразу, что и на незнакомый код) | ⬜ нейтральная, НЕ «ошибка» | ⬜ |
|
||||
|
||||
Причин пятнадцать; `upstream_not_ok` не имеет своей ветки в `flagReasonSeverity` и падает в ранг по
|
||||
умолчанию (`pipeline/status.go:174`), как и любая будущая причина.
|
||||
Причин пятнадцать; `upstream_not_ok` не имеет своей ветки в `flagReasonSeverity`
|
||||
(`backend/internal/pipeline/status.go`, функция `flagReasonSeverity`) и падает в ранг по умолчанию,
|
||||
как и любая будущая причина. ⚠ Номер строки здесь намеренно не ставится: прежняя ссылка `:174`
|
||||
протухла за один пак (там теперь `GlossaryMissFlagged`) — поймано синком 20.08, п. L записки.
|
||||
|
||||
### Приложение А-2. Карта кодов ОШИБОК — заполнена (0.3.0)
|
||||
|
||||
|
|
@ -912,12 +1391,13 @@ heartbeat · форма `id` кадра. Сжатие как ТРЕБОВАНИ
|
|||
| `unauthenticated` | 401 | гард сессии (`server.go:109-110`) |
|
||||
| `forbidden` | 403 | `server.go:99` + `auth/csrf.go` (отсутствие `X-TM-Client` / чужой origin) |
|
||||
| `not_found` | 404 | `pgstore.ErrNoBook`/`ErrNoAccount`/`ErrNoRun` · охраняемый catch-all `server.go:134` |
|
||||
| `gone` | 410 | `pgstore.ErrNoChapter` → `v0.go:761` — глава, которой нет в ТЕКУЩЕЙ структуре книги (была она когда-то или нет — не различается по замыслу, `openapi.yaml` §`gone`; лечение клиента одно: перечитать дерево). ⚠ Строка дописана оркестратором №18 при ратификации 0.4.0: карта объявляла себя «заполненной», а несла 15 значений из 16 корневых — производитель у `gone` живой с P7 |
|
||||
| `request_timeout` | 408 | `os.ErrDeadlineExceeded` → `v0.go:417` |
|
||||
| `payload_too_large` | 413 | `*http.MaxBytesError` → `v0.go:409` |
|
||||
| `run_in_flight` | 409 | `pgstore.ErrRunInFlight` (`v0.go:548`) |
|
||||
| `book_not_ready` | 409 | `runs.ErrBookNotReady` (`v0.go:550`) |
|
||||
| `run_not_stoppable` | 409 | `runs.ErrNotStoppable` (`v0.go:554`) |
|
||||
| `run_not_resumable` | 409 | `runs.ErrNotResumable` (`v0.go:556`); `cause`: `ceiling_reached` (⚠ `bank_decisions_incomplete` удалён D39.144 — гейта полноты нет; построенный гейт `reconcile.go:934-938` ДЕМОНТИРУЕТСЯ в P7) |
|
||||
| `run_not_resumable` | 409 | `runs.ErrNotResumable`; `cause`: `ceiling_reached` — **с 0.4.0 это же ответ на ИСЧЕРПАННЫЙ прогон в `stopped`/`awaiting_bank`, где платформа сегодня молча отвечает `202`** (A, PD-282: `runs/reconcile.go`, ветка `exhausted` функции `reopen` и её чтение в `Resume`). ⚠ `bank_decisions_incomplete` удалён D39.144 — гейта полноты нет; построенный гейт полноты (`reconcile.go`, ветка `case "awaiting_bank"`) ДЕМОНТИРУЕТСЯ в P7 |
|
||||
| `ceiling_unavailable` | 409 | `runs.ErrCeilingOutOfBounds` + `pgstore.ErrInsufficientCredit` (`v0.go:558`); `cause`: `bounds_moved` · `credit_held`; несёт `blocked` |
|
||||
| `idempotency_conflict` | 409 | форма заведена батчем; реализация — P7 |
|
||||
| `content_refused` | 400 | К-9; прескрин не построен (ПТ-16, строка 94). Отказ целой КНИГИ приходит не сюда, а состоянием `rejected` + `reject_reason` |
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ openapi: 3.1.0
|
|||
|
||||
info:
|
||||
title: TextMachine API
|
||||
version: 0.3.0
|
||||
version: 0.4.0
|
||||
summary: Ratified contract between the frontend and the TextMachine platform.
|
||||
description: |
|
||||
**RATIFIED contract.** Canonical copy: `docs/architecture/14-api-contract/`;
|
||||
|
|
@ -47,6 +47,12 @@ info:
|
|||
Every collection read and the book card answer an `ETag` and honour `If-None-Match` with `304`. A
|
||||
client is expected to use them: a frame says only THAT something changed.
|
||||
|
||||
A validator on any OTHER safe read of this surface is legal too and needs no declaration here —
|
||||
the operations that declare one are those where a client gains by using it, not an exhaustive
|
||||
list of where a server may send one. Two rules bound that: a client is never REQUIRED to send
|
||||
`If-None-Match`, and `304` is only ever the answer to one it did send. Stated so that a server
|
||||
can put a validator in one place instead of keeping a list of exceptions that rots.
|
||||
|
||||
An `ETag` is bound to the principal and to the representation; a response negotiated on
|
||||
`Accept-Encoding` MUST carry `Vary: Accept-Encoding`.
|
||||
|
||||
|
|
@ -198,12 +204,19 @@ paths:
|
|||
all (`intake_enabled`) · `408` the body did not finish in time, retry · `413` over
|
||||
`intake_max_bytes`.
|
||||
|
||||
**`Idempotency-Key` on a multipart body.** "The same request" is compared over the DECLARED
|
||||
parts — the metadata and the file's name and size — and never over the bytes themselves: a
|
||||
server does not hold a book in memory to compare it, and a retry of an interrupted upload
|
||||
re-sends the same file. Consequence, and it is the case that matters: **a `408` is not a
|
||||
completed attempt**, so the same key may be presented again; the retry is a repeat of the
|
||||
first call, not a second book.
|
||||
**`Idempotency-Key` on a multipart body.** "The same request" means the same metadata parts,
|
||||
the same file NAME, and the same file CONTENT. How a server establishes the last of those is
|
||||
its own affair — it need not hold a book in memory, and a digest taken while the bytes stream
|
||||
past costs it nothing — but the guarantee is the one that matters: **a key never answers with
|
||||
a response that belongs to a different request.** A server that cannot establish sameness
|
||||
MUST NOT replay: it answers `409` `idempotency_conflict` rather than the first response.
|
||||
|
||||
The framing is NOT the request. Two uploads of one file differ in their multipart boundaries
|
||||
and in the length of the body carrying them, and a server that compared either would refuse
|
||||
the honest retry this key exists to serve.
|
||||
|
||||
Consequence, and it is the case that matters: **a `408` is not a completed attempt**, so the
|
||||
same key may be presented again; the retry is a repeat of the first call, not a second book.
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/ClientHeader'
|
||||
- $ref: '#/components/parameters/IdempotencyKey'
|
||||
|
|
@ -350,8 +363,13 @@ paths:
|
|||
introduced, and the client's whole memory model stands on that. Page size default:
|
||||
`GET /capabilities`.
|
||||
|
||||
**`410 Gone`** answers a chapter that existed and no longer does: a book cut again leaves the
|
||||
old ids gone rather than absent, and the client re-reads the tree. `404` would mean a typo.
|
||||
**`410 Gone`** answers a chapter id this book's current structure does not hold: a book cut
|
||||
again leaves the old ids gone rather than absent, and the client re-reads the tree.
|
||||
|
||||
It is the answer whether or not the id was ever real. Ids here are opaque and are not kept
|
||||
after a re-cut, so "gone" and "never existed" are not two states a server can tell apart
|
||||
without keeping a graveyard of every id it ever minted — and a client's remedy is the same in
|
||||
both cases anyway. `404` stays for the BOOK, which a server does know about.
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/Limit'
|
||||
- $ref: '#/components/parameters/Cursor'
|
||||
|
|
@ -656,8 +674,10 @@ paths:
|
|||
|
||||
**The `202` does not mean the run has stopped** — the `Run` it returns still carries a live
|
||||
status. Stopping is asynchronous and there is no `stopping` value in `RunStatus`: the run
|
||||
reaches `stopped` when the work winds down, and the `status` frame says so. Between the two
|
||||
the client shows its own pending state. `409` answers a run that is not running at all.
|
||||
reaches `stopped` when the work winds down, and the `status` frame says so. This call is what
|
||||
SETS `stop_requested`, and the `Run` in this `202` already carries it `true` — so the state
|
||||
between the two survives a reload instead of living only in the client. `409` answers a run
|
||||
that is not running at all.
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/ClientHeader'
|
||||
responses:
|
||||
|
|
@ -690,6 +710,28 @@ paths:
|
|||
| `failed`, `ready` | `409` `run_not_resumable`, no `cause` — it is not a stopped run | a NEW run |
|
||||
| `translating` | `409` `run_not_resumable`, no `cause` | nothing; it is already running |
|
||||
|
||||
⚠ **The limit of the run is a SECOND axis, and it overrides the two `202` rows above.** A run
|
||||
that has spent everything its `ceiling_chapters` bought has nothing left to continue WITH —
|
||||
there is no work this call could pay for — and that is true whether it stands in
|
||||
`awaiting_bank` or in `stopped`, and however much credit the account holds. Such a call is
|
||||
`409` `run_not_resumable`, `cause.code: ceiling_reached`, exactly as `paused` is, and the
|
||||
remedy is the same: a NEW run. **A `202` therefore means work was actually reopened**, and a
|
||||
client may render it as "continuing" without a second read.
|
||||
|
||||
**The account is a THIRD case, and it is answered separately.** Continuing costs money, so a
|
||||
run with room left in its limit still cannot be reopened while the account cannot cover the
|
||||
rest of it: that is `409` `run_not_resumable`, `cause.code: credit_unavailable`, and the
|
||||
remedy is to top up rather than to start anything. It is deliberately not folded into
|
||||
`ceiling_reached` — that one says "this run is finished with", and saying it to a user whose
|
||||
run has chapters left would send them to buy a new run they do not need. The reverse mistake
|
||||
is the one the canon already warns about at `AccountHaltReason`: an account-wide state lit
|
||||
from a fact about one run tells a user with money that they have none.
|
||||
|
||||
Like the second axis, this one is judged AFTER the table and only where the table said
|
||||
`202`: a row that already answers `409` keeps the `cause` the table gives it, so a `paused`
|
||||
run on an empty account is still `ceiling_reached` — the remedy there is a new run either
|
||||
way, and the account's own emptiness is what `Usage` is for.
|
||||
|
||||
⚠ **A NEW run is legal from ANY paused book, whatever `paused_reason` says — `null`
|
||||
included.** The limit travels with the START of a run and nothing changes it afterwards, so a
|
||||
run stopped at one is never continued by this call; the reason is a hint about WHY the work
|
||||
|
|
@ -698,6 +740,12 @@ paths:
|
|||
bought again. A client that waits for a particular reason value before offering that strands
|
||||
the user on the commonest stop there is.
|
||||
|
||||
That legality is not a promise of progress: a deployment may hold limits of its OWN that no
|
||||
purchase lifts and this contract has no word for, and a new run under one stops the same way
|
||||
it did before. The remedy a client offers is still the new run — it has no better one — but
|
||||
it does not present it as a guarantee, and a `paused` run that returns immediately to
|
||||
`paused` is a legal outcome rather than a fault to report.
|
||||
|
||||
**503 answers a deployment that cannot run at all** — continuing a run is starting a process.
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/ClientHeader'
|
||||
|
|
@ -945,6 +993,13 @@ components:
|
|||
A DELETION cannot be expressed this way. Two answers close that: `resync_required` on the
|
||||
stream when a collection is replaced wholesale, and `400` with
|
||||
`cause.code: version_too_old` for a watermark that predates such a replacement.
|
||||
|
||||
⚠ **A row already delivered is therefore never withdrawn ONE AT A TIME.** A delta read has no
|
||||
shape for "this one is gone", so a row that stops belonging to its collection would sit on the
|
||||
client for as long as the tab is open — and a count beside it would already disagree, which is
|
||||
the visible half of the same fault. A collection that must lose a row loses it the only way
|
||||
this contract can express: replaced wholesale, announced with `resync_required`. A server that
|
||||
finds itself retracting a single row owes that frame; it does not simply stop sending it.
|
||||
schema: { $ref: '#/components/schemas/Revision' }
|
||||
Limit:
|
||||
name: limit
|
||||
|
|
@ -1031,8 +1086,10 @@ components:
|
|||
schema: { $ref: '#/components/schemas/Problem' }
|
||||
Gone:
|
||||
description: |
|
||||
The object existed and does not any more; its identifier will not be reissued. A client
|
||||
holding a reference re-reads the collection it came from.
|
||||
The identifier does not belong to the current structure of this book and will not be
|
||||
reissued. A client holding a reference re-reads the collection it came from. It is also the
|
||||
answer to an identifier that never existed, on a collection whose identifiers a server cannot
|
||||
prove it never minted — the operation says which.
|
||||
headers:
|
||||
X-Request-Id: { $ref: '#/components/headers/RequestId' }
|
||||
content:
|
||||
|
|
@ -1171,7 +1228,7 @@ components:
|
|||
The version this deployment serves — the only place a non-streaming client learns it. A
|
||||
client generated against a different one REFUSES to work and says so: while the major is
|
||||
`0` a differing minor carries breaking changes by design.
|
||||
examples: ['0.3.0']
|
||||
examples: ['0.4.0']
|
||||
language_pairs:
|
||||
type: array
|
||||
description: |
|
||||
|
|
@ -1240,6 +1297,9 @@ components:
|
|||
reaches one. The book-wide figure is `Book.chapters_done` against `Book.chapter_count` and
|
||||
answers a different question.
|
||||
|
||||
⚠ **"Finished" means the work THIS deployment does on a chapter is finished** — the last pass
|
||||
the book actually gets, whatever that is.
|
||||
|
||||
No ready-made percentage is shipped: how a fraction is drawn is a product decision.
|
||||
required: [done, total, eta_seconds]
|
||||
properties:
|
||||
|
|
@ -1314,6 +1374,8 @@ components:
|
|||
- `content_refused` — the service will not translate this book. **One coarse reason for a
|
||||
whole class**: it never says which check refused, never varies between attempts, and gives
|
||||
nothing to search against. A client shows one neutral phrase and does not invite a retry.
|
||||
⚠ **Declared ahead of any producer**, on the same footing and for the same reason as
|
||||
`ErrorCode.content_refused`; the companion carries who owes the check.
|
||||
|
||||
A client MUST tolerate an unknown value under a minor bump and MUST render a rejected book
|
||||
that carries no reason at all: `null` is legal.
|
||||
|
|
@ -1402,10 +1464,14 @@ components:
|
|||
type: integer
|
||||
minimum: 0
|
||||
description: |
|
||||
Chapters fully translated. Against `chapter_count` this is the book's own progress —
|
||||
Chapters fully translated — "finished" as `Progress` defines it, the last pass this
|
||||
deployment gives a chapter. Against `chapter_count` this is the book's own progress —
|
||||
what a library row shows — and it never moves backwards WITHIN one `structure_version`;
|
||||
cutting the book again recomputes both numbers. The bar of a RUNNING run is
|
||||
`Run.progress`, which measures what that run bought.
|
||||
|
||||
⚠ It is also the number a service reads to decide **how much of the book is left to
|
||||
buy**, so a count that cannot reach `chapter_count` keeps offering work already finished.
|
||||
character_count:
|
||||
type: [integer, 'null']
|
||||
minimum: 0
|
||||
|
|
@ -1460,6 +1526,7 @@ components:
|
|||
- revision
|
||||
- status
|
||||
- stop_for_signing
|
||||
- stop_requested
|
||||
- ceiling_chapters
|
||||
- progress
|
||||
- paused_reason
|
||||
|
|
@ -1479,6 +1546,25 @@ components:
|
|||
type: boolean
|
||||
description: |
|
||||
The run was started with a stop for the book's terms to be signed.
|
||||
stop_requested:
|
||||
type: boolean
|
||||
description: |
|
||||
The user asked this run to stop. **It answers "was this what I asked for", which no
|
||||
`status` can answer**: stopping is not instant, so a stop asked for during translation can
|
||||
meet the run reaching a stop of its own — the bank signature — and the run then ends in
|
||||
`awaiting_bank`, a status that offers to continue on a click that meant "stop". A client
|
||||
that reads this flag says "stopped — the work is parked at the signature, continuing is
|
||||
cheap" instead of offering the wrong action, and it survives a reload, which a client's
|
||||
own pending state does not.
|
||||
|
||||
Required and never absent, `false` for a run nobody asked to stop: absence would be a
|
||||
second way of writing `false` and the reader would have to handle two.
|
||||
|
||||
It is a record of the REQUEST, not of the outcome — it stays `true` on a run that went on
|
||||
to finish anyway, because that is exactly the case the screen has to explain. It is
|
||||
cleared by a `resume` that SUCCEEDS: continuing IS the withdrawal of the request, and a
|
||||
run that was asked to continue is no longer a run someone asked to stop. A refused
|
||||
`resume` clears nothing — nothing was withdrawn.
|
||||
ceiling_chapters:
|
||||
type: integer
|
||||
minimum: 1
|
||||
|
|
@ -1695,9 +1781,16 @@ components:
|
|||
in the companion's appendix A. A client MUST show a neutral phrase — never the word
|
||||
"error" — for a code it does not know.
|
||||
|
||||
Not enumerated here: the map is a table the contract's owner fills, and freezing a list
|
||||
in the schema before the words exist would make it a second copy. It becomes an enum when
|
||||
appendix A is written.
|
||||
⚠ **`unspecified` is reserved and is the server's answer when it has no code for what it
|
||||
was told.** The service that produces notes and the one that serves them ship apart, so a
|
||||
reason newer than this build is an ordinary event, not a fault — and this field is
|
||||
required, so a server has to write something. Writing `unspecified` is the obligation;
|
||||
inventing a word for the reason is forbidden, because an invented word is a vocabulary
|
||||
nobody agreed to. A client treats it exactly as it treats a code it does not know.
|
||||
|
||||
Not otherwise enumerated here: the map is a table the contract's owner fills, and freezing
|
||||
a list in the schema before the words exist would make it a second copy. It becomes an
|
||||
enum when appendix A is written.
|
||||
chapter_id:
|
||||
$ref: '#/components/schemas/Id'
|
||||
description: |
|
||||
|
|
@ -1862,12 +1955,27 @@ components:
|
|||
action:
|
||||
type: string
|
||||
enum: [approve, decline]
|
||||
description: '`approve` — take the term into the book (with a translation in `dst`); `decline` — leave it out.'
|
||||
description: |
|
||||
`approve` — take the term into the book (with a translation in `dst`); `decline` — leave
|
||||
it out.
|
||||
|
||||
⚠ **NOTHING recorded here reaches the work itself yet** — not `decline`, not `approve`,
|
||||
not the translation typed into `dst`. A deployment stores the decisions and answers the
|
||||
receipt; the producer of the bank reads its own files and this call does not write them.
|
||||
So a term left out here can come back proposed on the next run, and a corrected
|
||||
translation does not change the text. Both halves are read from `BankPage` and neither
|
||||
is guessed; the companion carries who owes the missing half.
|
||||
|
||||
This is a TEMPORARY property of the deployment and not a promise of the contract: the
|
||||
call exists so the correction has somewhere to go the day the delivery is built, and a
|
||||
client may show what it recorded. What DOES take effect today is the signature itself —
|
||||
one `resume` over the whole bank, whatever state the decisions are in.
|
||||
dst:
|
||||
type: string
|
||||
description: |
|
||||
Translation. **Required and non-empty when `action` is `approve`** — in words as well as
|
||||
in the constraint above, because a generator ignores `if`/`then`.
|
||||
in the constraint above, because a generator ignores `if`/`then`. ⚠ See the caveat on
|
||||
`action`: today this text is recorded and not applied.
|
||||
|
||||
BankDecisionsRequest:
|
||||
type: object
|
||||
|
|
@ -2196,8 +2304,8 @@ components:
|
|||
properties:
|
||||
contract:
|
||||
type: string
|
||||
description: Contract version this deployment serves, e.g. `0.3.0`.
|
||||
examples: ['0.3.0']
|
||||
description: Contract version this deployment serves, e.g. `0.4.0`.
|
||||
examples: ['0.4.0']
|
||||
|
||||
EventStatus:
|
||||
allOf:
|
||||
|
|
@ -2371,9 +2479,10 @@ components:
|
|||
presented by cookie, or the request came from an origin this deployment does not accept;
|
||||
- `not_found` (404) — no such object, or one this account may not see, or a path this
|
||||
deployment does not serve;
|
||||
- `gone` (410) — the object existed and does not any more, and its identifier will not be
|
||||
- `gone` (410) — the identifier does not belong to the current structure and will not be
|
||||
reissued. Told apart from `not_found` because the remedy differs: re-read the collection,
|
||||
rather than check the address;
|
||||
rather than check the address. The operation says where it is also the answer to an
|
||||
identifier that never existed;
|
||||
- `request_timeout` (408) — the body did not arrive whole in time. Retrying is the remedy;
|
||||
- `payload_too_large` (413) — over `intake_max_bytes`;
|
||||
- `run_in_flight` (409) — this book is already being translated;
|
||||
|
|
@ -2381,11 +2490,13 @@ components:
|
|||
being cut, or was rejected;
|
||||
- `run_not_stoppable` (409) — this run is not running;
|
||||
- `run_not_resumable` (409) — this run cannot be continued. `cause.code` says why:
|
||||
`ceiling_reached` — the run stopped at a limit, the chapters it bought or the credit
|
||||
behind them, and the remedy is a NEW run rather than this call. A run that is not a
|
||||
stopped run at all carries no `cause`. An incomplete set of bank decisions is NOT a
|
||||
reason: signing is one act over the whole bank, and `resume` lifts that stop with the
|
||||
decisions as they stand;
|
||||
`ceiling_reached` — THIS RUN is finished with: it stopped at the limit it was given, or has
|
||||
spent all of it. The remedy is a NEW run rather than this call, and it is the answer in
|
||||
every status, not only `paused` (see `resumeRun`). `credit_unavailable` — the run has room
|
||||
left but the ACCOUNT cannot cover the rest of it; the remedy is to top up, and no new run
|
||||
would help either. The two never stand in for one another. A run that is not a stopped run
|
||||
at all carries no `cause`. An incomplete set of bank decisions is NOT a reason: signing is
|
||||
one act over the whole bank, and `resume` lifts that stop with the decisions as they stand;
|
||||
- `ceiling_unavailable` (409) — the limit asked for cannot be started. `cause.code`: `bounds_moved`
|
||||
— the bounds changed between the read and this call; `credit_held` — the account's credit
|
||||
is held elsewhere, and `blocked` names the book holding it;
|
||||
|
|
@ -2395,9 +2506,18 @@ components:
|
|||
- `content_refused` (400) — the service will not do this work. **One coarse code for a whole
|
||||
class** and deliberately so: it does not say which check refused, does not vary between
|
||||
attempts, and carries neither `cause` nor `errors`. A client shows one neutral phrase and
|
||||
does not invite a retry, and the server bounds how many times one account may try — that
|
||||
bound is the server's and is not on the wire. A refusal of a whole BOOK is not reported
|
||||
here at all: it is a state of the book, `rejected` with `reject_reason: content_refused`;
|
||||
does not invite a retry. A refusal of a whole BOOK is not reported here at all: it is a
|
||||
state of the book, `rejected` with `reject_reason: content_refused`.
|
||||
⚠ **Declared ahead of any producer** — the check that would raise it is not built, and
|
||||
nothing on either side of this boundary emits it today. It is here because the shape of the
|
||||
answer was settled before the check was, and settling it later would have meant settling it
|
||||
under pressure. The obligation that comes with it — that whatever raises it also bounds how
|
||||
often one account may try, since an unbounded refusal is an oracle for guessing past it —
|
||||
falls due WITH the producer, not before; the companion carries who owes both. That bound is
|
||||
the server's and is never on the wire in any form: a number a client can read is a number a
|
||||
client can plan around, which is the whole of what the bound exists to stop. A client
|
||||
handles the code from today: an old client meeting it for the first time is the failure this
|
||||
note exists to prevent;
|
||||
- `service_unavailable` (503) — the deployment cannot do this right now;
|
||||
- `internal_error` (500) — a defect on our side. Nothing about it is actionable by a client
|
||||
beyond quoting `request_id`.
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@
|
|||
| ПТ-22 | Настройки от человека: 18+, жанр и т.п. (V0-п.22) | 🔶 | `book.yaml` (labels) данными; UI = Ф3. ⚠ ЖАНР из продукта ВЫКИНУТ словом владельца 16.08 (D39.138 п.2в, строка 184: ни на чём не ветвится, влияние не измерено; снимается из промптов/брифа/контракта/формы) |
|
||||
| ПТ-23 | Веб-ПЕРВЫЙ фронт; SEO/ранжирование Google — куки, страницы-сироты, актуальные алгоритмы (V5-п.2); продукт = **SaaS**, не локальное приложение (решение владельца 02.08, D39.81 — натив-прицел V0-п.26 СНЯТ этим решением, вернуть только новым словом владельца); SEO только под Google, Яндекс снят; рынок глобальный; десктоп = PWA первым шагом, Tauri по явным триггерам (решения владельца 02.08, D39.84) | ⭕ | строка 71 (Ф3-бриф); SEO вошёл в стек-дизайн ДО выбора (D39.84, требование исполнено): двухконтурный деплой — SPA `app.<домен>` целиком под noindex + статический публичный контур ≤3 кликов от главной (лечение страниц-сирот), `frontend/docs/STACK_DECISIONS.md` §4; SSR-вопрос закрыт этим решением |
|
||||
| ПТ-24 | Цикл согласования глоссария с профпереводчиком/редактором (V0-п.23) | 🔶 | подписной цикл терминолога в бэкенде есть; UI = Ф3. Модель цикла записана словом владельца 16.08 (D39.144 + строка 192): черновой перевод и черновой банк появляются вместе → ОДИН ОК всему банку (не пер-термная масс-подпись) → доработка → пост-ридинговые точечные правки с пере-генерацией по вхождениям (механика — после полигона) |
|
||||
| ПТ-33 | Интерфейс НЕ раскрывает конвейер: ни моделей, ни стадий, ни внутренней терминологии (решение владельца 02.08; граница пере-читана 15.08 — охранять АЛГОРИТМЫ, не минорную механику, D39.136 п.4а). ⚠ «Лестница» фаз «загрузка → … → финал → готово» — устная фраза владельца, НЕ норма продукта (его слово 16.08, D39.138: `finalizing` сворачивается) | 🔶 | инвариант D39.81; словарь статусов принят (D39.100/К-1); карта «вердикт → фраза» — приложение А компаньона (В-3), после D39.138 пишется по словарю машинных кодов Б-1. ⚠ Ревью 28 (Б-0) ОПРОВЕРГЛО исполнением ответ §5 компаньона «сменится стадия — фронт не правится»: волны draft/edit утекали в `Progress` до React-компонента; снято ИСПОЛНЕННЫМ батчем 0.3.0 (D39.142/143: Progress — один счётчик в каноне, анти-утечка-греп чист); машинный ГЕЙТ на утечку — фронту при разморозке (пинг 16.08) |
|
||||
| ПТ-33 | Интерфейс НЕ раскрывает конвейер: ни моделей, ни стадий, ни внутренней терминологии (решение владельца 02.08; граница пере-читана 15.08 — охранять АЛГОРИТМЫ, не минорную механику, D39.136 п.4а). ⚠ «Лестница» фаз «загрузка → … → финал → готово» — устная фраза владельца, НЕ норма продукта (его слово 16.08, D39.138: `finalizing` сворачивается) | 🔶 | инвариант D39.81; словарь статусов принят (D39.100/К-1); карта «вердикт → фраза» — приложение А компаньона (В-3), после D39.138 пишется по словарю машинных кодов Б-1. ⚠ Ревью 28 (Б-0) ОПРОВЕРГЛО исполнением ответ §5 компаньона «сменится стадия — фронт не правится»: волны draft/edit утекали в `Progress` до React-компонента; снято ИСПОЛНЕННЫМ батчем 0.3.0 (D39.142/143: Progress — один счётчик в каноне, анти-утечка-греп чист); машинный ГЕЙТ на утечку — фронту при разморозке (пинг 16.08). ⚠ **ОСТАТОК, названный владельцем 20.08 (D39.153, строка 200):** конвейер не утекает СЛОВАМИ, но утекает ФОРМОЙ прогресса — доля считается по отрезкам и обнуляется при снятии стопа, то есть пользователь видит 100% после черновика и старт заново; и фаза, которая глав не заканчивает (сбор банка), сегодня полосой невыразима — знаменателя она не объявляет; может ли объявлять, это открытый вопрос движку в той же строке 200. Требование: ОДНА сквозная доля на всю работу прогона, считает СЕРВЕР (иначе клиент снова узнаёт про фазы), плюс подпись «что делается сейчас» и неопределённый индикатор там, где доли в главах нет; веса не подбираются — знаменатель берётся из объявленного движком плана проходов |
|
||||
| ПТ-34 | Пользовательский перевод НИКОГДА не попадает на публично индексируемый URL — ни витрин примеров, ни «поделиться главой», ни публичных ссылок чтения (жёсткий продуктовый запрет, решение владельца 02.08; авторское право на исходники + политика Google по масштабируемому машин-контенту действуют на весь домен) | ⭕ | инвариант D39.84; носители: двухконтурный деплой `frontend/docs/STACK_DECISIONS.md` §4 (SPA под `X-Robots-Tag: noindex` на каждом HTML-ответе; публичный контур без контента переводов) + контракт 14 (`architecture/14-api-contract/`, D39.99) + платформенный бэклог П-1; правило доступа к экспорт-ссылке (срок/подпись) — батч 183 (research/28 Б-4) |
|
||||
| ПТ-35 | Лимиты «как в Claude Code» (решение владельца 04.08, D39.100): страница лимитов/использования в настройках (статус, НЕ суммы — запрет денег на экране в силе); стоп по потолку = статус `paused` + оповещение «перевод остановлен: лимиты исчерпаны» | 🔶 | контракт К-8 закрыт, спека несёт `paused`/`paused_reason` (0.2.0+, D39.115); ручки /v0 — платформа-раннер P4 (D39.123); статус `paused` по потолку ЧЕСТЕН с P6 (D39.132: exit 4/событие → `paused`, не `failed`; дневной потолок оператора различим и из платформенного шаблона убран — решение владельца 15.08); остаток: страница лимитов и оповещение на фронте (после P7) · точное продуктовое слово — В-3 на владельце · ~~К-13~~ ЗАКРЫТ (D39.132 п.2а: `null` на проводе; пере-суждён ревью 28 §4 №9 и НЕ опрокинут — с уходом дневной оси различие перестало быть продуктовым) |
|
||||
| ПТ-36 | Интерфейс МУЛЬТИЯЗЫЧНЫЙ, русский не хардкодится в разметке (слово владельца 10.08; язык интерфейса — отдельная ось от языков перевода). ⚠ До 10.08 требование в реестре отсутствовало — пропуск поймал владелец (D39.127 п.2г) | 🔶 | ПОСТРОЕНО S3.7 (принят D39.129): каталог `frontend/src/i18n/ru.ts` (157 ключей) · механизм `@internationalized/string` · ESLint-гейт «текст местом» · стор языка с персистом · `<html lang>` следует выбору; содержимое — только ru; продуктовый список языков — 🔒 владелец (В-9 зоны фронта) |
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue