108 KiB
Отчёт: батч 0.3.0 — ломающая правка контракта API v0
Сессия по промту
docs/CONTRACT_BATCH_SESSION_PROMPT.md(выдан оркестратором №17 16.08.2026 по ратификации D39.138 п.2/п.3, строка 183). Носитель заказа —docs/research/28-contract-review.md§5 · §5а · §5б · §8 · §8а + ревью-шапка приёмки; при расхождении с ней побеждала нота D39.138.Зона записи — три файла, других не тронуто:
docs/architecture/14-api-contract/openapi.yaml·docs/architecture/14-api-contract/README.md· этот отчёт. Зеркалоfrontend/docs/api-contract/openapi.yamlНЕ тронуто (зона фронта заморожена, D39.136 п.2) — расхождение канона и зеркала на момент сдачи известно и ратифицируется лендингом. Git не трогался: ниadd, ниcommit, ниreset, ниcheckout. Дерево оставлено незакоммиченным.
0. Что сделано, одним экраном
| было (0.2.3) | стало (0.3.0, после дофикса) | |
|---|---|---|
| операций / путей | 16 / 15 | 20 / 17 |
строк файла (wc -l) |
1444 | 2463 |
| форма (структурные строки) | 760 | 1183 |
| проза (строки внутри описаний) | 540 | 1055 |
| доля прозы | 42 % | 47 % |
| причина отказа на проводе | английская фраза, показываемая «as-is» | машинный code (16 корневых) + cause + request_id + errors[] |
| прогресс | {draft{done,total}, edit{done,total}} по всей книге, в юнитах |
ОДНА полоса до ближайшей остановки, в главах, знаменатель — купленный объём |
| поток | GET /runs/{runId}/events |
GET /books/{bookId}/events + кадр end + 204 |
| условные чтения | нет ни одного | ETag/If-None-Match/304 на 7 чтениях + дельта ?after_version= |
| «что умеет деплой» | нигде | GET /capabilities |
Команды, которыми получены числа (пере-ран после ПОСЛЕДНЕЙ правки дофикса — требование ФБ-10):
grep -c 'operationId:' openapi.yaml # 16 → 20
grep -cE '^ /' openapi.yaml # 15 → 17
wc -l openapi.yaml # 1444 → 2463
awk-счёт значений ErrorCode # → 16
python3 /tmp/prose_form.py (скрипт приложен в §4.4) # проза/форма обеих версий
git show 1022567:…openapi.yaml > /tmp/old.yaml # снимок «было» для всех сверок
Форма выросла на 56 %, проза — на 96 %; доля прозы выросла с 42 % до 47 %. Это ответ на
вопрос §8 п.3 «не переусложнён ли контракт», и он честнее прежнего: батч добавил четыре операции и
шестнадцать схем, а каждая находка трёх линз селф-ревью и десять пунктов дофикса закрываются
ПРАВИЛОМ — а правило это проза. Прежний вывод «доля не сдвинулась» держался на мерке, где строка
description: | считалась формой; скрипт теперь приложен (§4.4), число можно оспорить командой, и
утверждать «вернул к уровню 0.2.3» я больше не могу. Что резалась вода, а не норма, доказано отдельно
и от мерки не зависит: типами и мультимножеством модальных глаголов (§4.4).
1. Записка-план: каждый ID заказа с диспозицией
Комплектность сверялась механически против носителя, не по памяти.
1.1. Ядро — research/28 §5, пункты 0–4
| ID | Заказ | Диспозиция | Где |
|---|---|---|---|
| §5.0 / Б-0 | Progress в один счётчик до остановки |
ИСПОЛНЕНО. Progress{done,total,eta_seconds} в ГЛАВАХ, сегментная логика, знаменатель — купленный объём; Counter снят; Progress переехал с Book на Run |
§Progress, §Run.progress |
finalizing свернуть |
ИСПОЛНЕНО. Значение снято из BookStatus; в новый RunStatus не попало |
§BookStatus, §RunStatus |
|
verify_bank → stop_for_signing без «before the final pass» |
ИСПОЛНЕНО на RunRequest и на Run |
§RunRequest, §Run.stop_for_signing |
|
TermOrigin снять/переименовать |
ПЕРЕИМЕНОВАНО, ось сохранена: seed·ruby·mined → given·annotated·found |
§TermOrigin |
|
TermStatus ОСТАВИТЬ осью, переименовать движковые значения (эррата 16.08-г) |
ИСПОЛНЕНО. Ось на месте; auto·draft·approved → proposed·in_progress·approved (переименован и draft — он читается как имя волны и ловится анти-утечка-грепом) |
§TermStatus |
|
Unit описать без «edit unit» и «1.9 на главу» |
ИСПОЛНЕНО | §Unit |
|
| из ВСЕХ описаний вычистить конвейерные слова | ИСПОЛНЕНО, проверено грепом (§4.2) | весь файл | |
| счётчик ГЛАВЫ той же сегментной логикой | ИСПОЛНЕНО. ChapterProgress.units_done — «в текущем сегменте», с явным «иначе дерево читало бы ноль всю первую волну» |
§ChapterProgress.units_done |
|
| завести гейт на утечку | ЧАСТИЧНО — по границе зон, как заказано промтом §3. Правило записано в компаньон как ревью-вопрос каждой правки со списком грепа; ТЕСТ — половина фронта, носитель — пинг фронту 16.08, исполнение при разморозке | компаньон §5 | |
| §5.1 / Б-1 | модель ошибок, вариант B | ИСПОЛНЕНО ПЕРВОЙ (порядок §3 промта соблюдён: словарь кодов определён до формулировок Б-2/Б-3/Б-8/Б-14а). code (16 корневых, закрытый) + cause{code} (второй уровень, НЕ закрытый) + request_id + errors[]{pointer,code} + localized + blocked; title/detail объявлены developer-facing и неотображаемыми, title получил minLength:1 |
§Problem, §ErrorCode, §ErrorCause, §ErrorItem |
| два класса конкретности §8а | ИСПОЛНЕНО. Класс 1 — максимальная конкретика (errors[] + cause); класс 2 — ОДИН код content_refused без cause, без errors, без вариации между попытками; тем же правилом заведено значение RejectReason.content_refused (К-9) |
§ErrorCode.content_refused, §RejectReason |
|
| §5.2 / Б-15 | X-TM-Client машинно · 403 · WWW-Authenticate · множество методов · servers.url · судьба bearer |
ИСПОЛНЕНО ПОЛНОСТЬЮ. Заголовок — параметр на 9 небезопасных операциях; 403 — компонент-ответ; 401 несёт обязательный WWW-Authenticate; методы приведены к коду и RFC (OPTIONS освобождён); servers[0].url: /v0; bearer ОСТАВЛЕН с явной записью «сервер принимает, выдать нечем» + носитель |
§ClientHeader, §responses.Forbidden, §responses.Unauthorized, §sessionCookie, servers, §bearerToken |
| §5.3 / Б-2 | GET /capabilities |
ИСПОЛНЕНО. contract_version · language_pairs[] со статусом · intake_max_bytes · export_formats[] · page_size_default. Один плоский ответ на деплой (предупреждение судьи о соразмерности учтено: ни ETag-мультитенантности, ни пер-аккаунтных оверрайдов) |
/capabilities, §Capabilities |
| §5.4 / Б-7а | три устаревших предупреждения снять; resume-абзац переписать | ИСПОЛНЕНО. «канала банка нет», «движок не эмитит note», «зависит от эмиттера» — сняты; resume после потолка → 409 cause.code: ceiling_reached + названо работающее лечение (новый прогон с бОльшим потолком) в startRun И в resumeRun |
/runs/{runId}/resume, /books/{bookId}/runs, компаньон §3 |
1.2. Пока дёшево (Ц0) — §5, пункты 5–10
| ID | Заказ | Диспозиция | Где |
|---|---|---|---|
| §5.5 / Б-6 | перевесить поток на книгу | ИСПОЛНЕНО — GET /books/{bookId}/events, streamBookEvents |
/books/{bookId}/events |
| кадры конца разбора и конца прогона | ОТСТУПЛЕНИЕ, аргумент: конец разбора — обычная смена статуса на книжном потоке (кадр не нужен, и Б-6(а) сам это говорит: «чинить не кадром, а перевеской»); конец ПОТОКА — новый кадр end |
§EventEnd |
|
204 при переподключении к завершённому |
ИСПОЛНЕНО с точной границей: 204 только на запрос С Last-Event-ID; без него всегда открывается новый поток |
/books/{bookId}/events → 204 |
|
развести id кадра и ревизию |
ИСПОЛНЕНО. id — позиция потока, форма зафиксирована ^[0-9]+$; revision — в data каждого кадра через EventBase |
§EventEnvelope.id, §EventBase |
|
| правило докачки — одно | ИСПОЛНЕНО: короткий живой буфер разрешён, реплей истории запрещён, размер не объявляется | /books/{bookId}/events → Reconnect |
|
запрет склейки note |
ИСПОЛНЕНО | /books/{bookId}/events → Coalescing |
|
| (отступление) прогонный поток «оставить узким видом» | ОТКАЗ, аргументирован — §3.1 ниже | — | |
| §5.6 / Б-4 | экспорт: state, failure_code, expires_at, эхо формата, правило доступа |
ИСПОЛНЕНО ПОЛНОСТЬЮ + Export.revision (одиночная правка §5) |
§Export |
| §5.7 / Б-9+Б-14а | id, created_at, обязательная адресация; pending_decisions/complete в чтение банка |
ИСПОЛНЕНО. Note.id/created_at/chapter_id обязательны; unit_id оставлен необязательным (ровно то, что просил Б-9); банк отвечает pending_decisions+complete+total+signed |
§Note, §BankPage |
| §5.8 / Б-19 | снять запрет клиентской служебной метки СЕЙЧАС | ИСПОЛНЕНО | §Chapter.heading |
title_raw, kind — формой ЛИБО передачей паку 161 |
ПЕРЕДАНО ПАКУ 161, аргумент — §3.2 ниже; запись в компаньоне §2.3 | компаньон §2.3, §3 | |
| структурная версия | ИСПОЛНЕНО (обязательна батчу) — structure_version в hello, ChapterPage, UnitPage, Book |
§StructureVersion и др. |
|
| §5.9 / Б-7 | версия в ответах и hello; 410; If-None-Match/304 |
ИСПОЛНЕНО. 410 Gone на listUnits; условные чтения на 7 операциях |
§responses.Gone, info §Conditional reads |
| §5.10 / Б-12 | правило согласования агрегатов со страницами | ИСПОЛНЕНО: агрегаты — на ПЕРВОЙ странице; правило согласовано с ревизией обхода (минимум по страницам), которая тоже записана впервые | §BankPage, §Revision |
1.3. Дёшево и на построенном (Ц1) — §5, пункты 11–16
| ID | Заказ | Диспозиция | Где |
|---|---|---|---|
| §5.11 / Б-3 | запрет молчаливой потери | ИСПОЛНЕНО: любая часть после файла → 400 + errors[], «Nothing is lost silently» |
/books POST |
порядок свойств BookIntake |
ИСПОЛНЕНО и ПРОВЕРЕНО ГЕНЕРАТОРОМ (§4.1): title, source_lang, target_lang, file |
§BookIntake |
|
Idempotency-Key (семантика — моя) |
ИСПОЛНЕНО, шесть правил: область (принципал+метод+путь) · повтор → исходный ответ · другие параметры → 409 key_reused · параллельный → 409 key_in_flight · окно ≥24 ч · длина ≤255 |
§IdempotencyKey |
|
Location на 201 |
ИСПОЛНЕНО | /books POST → 201 |
|
| §5.12 / Б-8+Б-13а | failure_reason у прогона |
ИСПОЛНЕНО. RunFailureReason: source_unreadable · service_error · interrupted — спроецировано с полосы отказов движка 10–19 (config_invalid/source_unreadable/project_locked/schema_mismatch, backend/internal/pipeline/refusal.go:29-46) и ветки outcome() платформы; таксономия НЕ выдумана |
§RunFailureReason |
| знаменатель прогона в главах | ИСПОЛНЕНО — Progress.total = купленный объём; плюс Book.chapters_done (величина «глав сделано», которой не было вовсе) |
§Progress, §Book.chapters_done |
|
| §5.13 / Б-13 | отдельный RunStatus |
ИСПОЛНЕНО (6 значений) | §RunStatus |
| правило старшинства | ИСПОЛНЕНО — записано в BookStatus |
§BookStatus |
|
book_id в Run |
ИСПОЛНЕНО | §Run.book_id |
|
судьба finalizing |
СНЯТ | — | |
| §5.14 / Б-5 | PATCH /books/{id} только title |
ИСПОЛНЕНО (merge-patch). ⚠ БЕЗ 409 при живом прогоне, вопреки скобке Б-5 — §3.9 | /books/{bookId} PATCH |
DELETE /books/{id} |
ИСПОЛНЕНО (204, 409 при живом прогоне) | /books/{bookId} DELETE |
|
GET /runs/{runId} |
ИСПОЛНЕНО | /runs/{runId} |
|
| записать явно: правка метаданных — ОТОБРАЖЕНИЕ | ИСПОЛНЕНО дословно, с причиной («конфигурация пишется один раз и не перезаписывается») | /books/{bookId} PATCH |
|
| §5.15 / Б-10+Б-11 | максимум limit и подрезание |
ИСПОЛНЕНО: maximum: 1000 (= maxPage, pgstore/books.go:587) + «CLAMPED DOWN, never answered with the default instead» |
§Limit |
| объявленный порядок коллекций | ИСПОЛНЕНО для всех пяти (библиотека · главы · юниты · замечания · банк) | описания пяти list-операций | |
maxItems у decisions |
ИСПОЛНЕНО (1000) | §BankDecisionsRequest |
|
| Б-23 / §3 промта | жанр — выкинуть из BookIntake и Book |
ИСПОЛНЕНО (слово владельца 16.08). Поля нет ни в интейке, ни в карточке; остальные половины удаления — снятие {{genre}} из промптов пары и из канона брифа — строка 184 и НЕ моя зона. ⚠ Строка отсутствовала в записке-плане первой редакции и дописана дофиксом (ФБ-10) |
§BookIntake, §Book |
| К-10 / D39.138 п.5 | пофазность у главы — НЕ строить | ИСПОЛНЕНО КАК «НЕ СТРОИТЬ». Фазы не выходят на провод ни у книги, ни у главы: ChapterProgress.units_done — один счётчик «в текущем сегменте». Колонки units_draft_done/units_edit_done read-модели остаются внутренним делом платформы. Строка дописана дофиксом (ФБ-10) |
§ChapterProgress |
| В-6 / D39.138 п.2з | blocked: {code, book_id} на run-options и 409 |
ИСПОЛНЕНО обоими носителями: RunOptions.blocked (required, nullable) и расширение Problem.blocked у ceiling_unavailable; cause.code: credit_held называет причину, book_id — держателя. Строка дописана дофиксом (ФБ-10) |
§RunOptions.blocked, §Blocked, §Problem.blocked |
| §5.16 / Б-14 | ОДНО правило кодирования отсутствия | ИСПОЛНЕНО: правило ЗАПИСАНО отдельным разделом шапки и применено к Usage.halt_reason, Run.finished_at/paused_reason/failure_reason, Book.character_count/reject_reason, Progress.eta_seconds, Unit.note, RunOptions.blocked, Export.* |
info §Absence of a value |
условная обязательность Unit.target |
ИСПОЛНЕНО — if/then + слова (К-11: генератор if/then игнорирует) |
§Unit.target |
|
| имена конвертов | ИСПОЛНЕНО: Library/ChapterList/UnitList/NoteList/Bank → BookPage/ChapterPage/UnitPage/NotePage/BankPage на общем Page через allOf |
§Page и др. |
1.4. Правки одной строкой — §5, хвост
| ID | Диспозиция | Где |
|---|---|---|
Б-17 TermOrigin → продуктовый словарь |
ИСПОЛНЕНО | §TermOrigin |
| Б-18 окно термина | ИСПОЛНЕНО: координаты structure_version записаны явно; два смысла нуля разведены на null (minimum: 1) |
§BankTerm.since_chapter |
| Б-16 требования деплоя — в примечание | ИСПОЛНЕНО: HTTP/2 at the edge и X-Accel-Buffering вынесены в компаньон §7; в спеке осталась НАБЛЮДАЕМАЯ норма «поток не буферизуется» + форма heartbeat |
/books/{bookId}/events, компаньон §7 |
Б-20 классы стабильности Id |
ИСПОЛНЕНО (три класса) | §Id |
| Б-22 роль имени файла | ИСПОЛНЕНО | §BookIntake.file |
описание paused_reason при null (К-13-остаток, D39.138 п.4) |
ИСПОЛНЕНО: «null in every other state» заменено на форму, допускающую paused + null |
§Run.paused_reason |
Usage.paused_reason (§4 №18) |
ИСПОЛНЕНО: → Usage.halt_reason со СВОИМ словарём AccountHaltReason (PD-203 развёл уровни), required+nullable |
§AccountHaltReason, §Usage.halt_reason |
Export.revision |
ИСПОЛНЕНО | §Export.revision |
| кэш-директивы в схему | ИСПОЛНЕНО: no-store объявлен на ВСЕХ ответах, как его и ставит middleware.go:38 |
info §Responses common to the whole surface |
1.5. §5а — резка
| Кандидат §5а | Диспозиция |
|---|---|
| экспорт (2 операции) | ОТОЗВАНО владельцем («экспорт будет») — сделан Б-4 |
Problem.instance |
СНЯТО |
Note.unit_id |
ОТКАЗ, аргументирован (§3.3) |
параметр limit |
ОТКАЗ, аргументирован (§3.4) |
EventCeiling |
СНЯТО целиком |
| нагрузка 4 кадров из 8 | ЧАСТИЧНО: сняты EventHello.run_id и EventResyncRequired.reason; нагрузка EventNote.note и счётчики EventBank СОХРАНЕНЫ — аргумент §3.5 |
Bank.signed |
ОТКАЗ, аргументирован (§3.6) |
слить формально пять конвертов на allOf |
ИСПОЛНЕНО |
EventChapter = подмножество Chapter |
ИСПОЛНЕНО: выделен ChapterProgress, Chapter и EventChapter собраны из него через allOf — переименования id→chapter_id больше нет, клиент не разбирает его руками |
не доводить до финала listBankTerms/listNotes |
УЧТЕНО: словарь ступеней не проектировался (К-6), поля BankTerm не добавлялись (D39.136 п.4б), Note.code НЕ сделан enum до написания фраз, число страницы замечаний не выдумывалось |
генезис-проза (semver-история, две апологии RFC 9110, объяснение имени source) |
УЖАТО НА МЕСТЕ, не перенесено — отступление от §5д промта, аргумент в §1.7(д) |
зеркальный дефект /usage построен и не читается |
ЗАПИСАНО в таблицу компаньона §3 |
1.6. §5б шаги 3–4 и Б-11а (заказ ПОВЕРХ §5)
| Что | Диспозиция | Где |
|---|---|---|
| правило кадра: применимая дельта ЛИБО счётчик + скоуп; не заставлять перечитывать коллекцию | ИСПОЛНЕНО, заменило необязательное «сервер МОЖЕТ склеивать» | /books/{bookId}/events → What a frame carries |
?after_version= контрактной формой |
ИСПОЛНЕНО на /notes и /bank; грунт — колонки revision и индексы notes_book_revision_idx/bank_terms_book_revision_idx, заведённые с комментарием «Delta reads on reconnect» |
§AfterVersion |
| разводка «норма vs эксплуатационное примечание» (не упереться в Б-16) | ИСПОЛНЕНО ПОСЛЕ ИСПРАВЛЕНИЯ. Первая редакция вынесла сжатие ЦЕЛИКОМ в примечание — нарушение D39.138 п.2(д); поймано опровергателем (A1), исправлено: обязанность честить Accept-Encoding и запрет сжимать SSE — норма спеки, слой исполнения — примечание. §3.7 |
info §Compression, компаньон §7 |
1.7. §5(а)–(д) промта — компаньон
| Подпункт | Диспозиция |
|---|---|
| (а) снять три опровергнутых утверждения | ИСПОЛНЕНО. Все три сняты и названы в ревью-шапке компаньона поимённо, с указанием, чем именно каждое было ложно. Ответ §5 не «поправлен», а ПЕРЕПИСАН: старый ответ приведён как то, что реально давал 0.2.3 (проверено исполнением), новый — как следствие снятия фаз с провода |
| (б) таблица «чтение → источник → строка бэклога» + правило «предупреждение несёт номер строки» | ИСПОЛНЕНО — компаньон §3, 19 строк; правило поставлено над таблицей |
| (в) провенанс-классы ✓/◆/○ под 0.3.0 | ИСПОЛНЕНО — компаньон §0, «Пере-разметка 0.3.0»: четыре класса сдвинуты, Progress ✓→◆ с объяснением, почему прежняя ✓ и была дефектом |
| (г) приложение А на словарь кодов Б-1, слова не заполнять | ИСПОЛНЕНО. Карта переведена на «причина движка → КОД контракта → фраза ⬜»; класс 2 схлопнут в один код (нормативно, §8а); коды помечены ◆ и в спеку как enum НЕ занесены. Добавлено приложение А-2 — карта кодов ОШИБОК, заполненная целиком с грунтом file:line на каждый |
| (д) генезис-проза из спеки — в компаньон | ОТСТУПЛЕНИЕ, аргумент. Фактически проза УЖАТА НА МЕСТЕ до ссылки на источник (semver §4, одна строка про Retry-After на 200), а не перенесена в компаньон: переносить в спутник абзац, от которого осталась ссылка, значит плодить третью копию. Проверяется грепом — этих абзацев в компаньоне нет. Ярлык «ИСПОЛНЕНО (27 блоков перенесено)» был неверен и исправлен дофиксом (ФБ-10); компаньон §6а поправлен там же |
2. Решения, которые я принял сам (промт §4)
2.1. Двухуровневый код: cause вложенным объектом
Ратифицировано «стабильный корневой + расширяемый ВЛОЖЕННЫЙ». Рассматривал плоский subcode
(проще на один объект) — выбрал вложенный: (а) буквально соответствует ратификации, не требуя пинга;
(б) граница «внутри версии / вне версии» видна структурно — всё, что в cause, по определению вне
закрытого словаря. Глубина ровно одна: рекурсии Microsoft-овского innererror нет и не нужна.
2.2. Счёт полосы — в ГЛАВАХ
Три довода в компаньоне §2.5. Коротко: «один счётчик» + «знаменатель — купленный объём» вместе
требуют одной единицы, а купленный объём объявлен в главах и другой единицы у него нет (пересчёт в
деньги на провод не идёт — D39.84; в юниты до разбора неизвестен). Возражение «ноль всю первую волну»
снимает сегментная логика, а не единица счёта, и обе величины у платформы уже есть
(00002_readmodel.sql:102-105). Новых колонок батч не требует.
2.3. Note.message снят, введён Note.code
Вариант B ратифицирован для Problem. Замечание — то же самое явление: сервер рисовал бы русскую
фразу, не имея ни Accept-Language, ни локалей (грепом ноль), а карта «причина → фраза» объявлена
принадлежащей контракту. Промт §5(г) требует карту «КОД → фраза» — у карты кодов должен быть носитель
кода на проводе. Поэтому code обязателен, message снят, а enum кодов в спеку НЕ занесён, пока
фразы не написаны (строка 148): иначе схема стала бы второй копией незаписанной карты.
Спорно; вариант «оставить message» отвергнут как сохранение ровно того дефекта, который Ф-61 и
называет.
2.4. promote → approve
Б-0 называет promote/decline глаголами оператора майнера (они дословно из
pipeline/mining.go:81,243). Плюс promote порождал status: approved — два слова на один акт.
decline оставлен: обычное слово, к именам файлов майнера не привязано.
2.5. Порядок работ
Б-1 исполнен ПЕРВЫМ (требование §3 промта и §8б): словарь кодов класса 1 выведен перечислением
реальных ветвей платформы ДО того, как писались Б-2 (unsupported_pair), Б-3 (missing_or_late),
Б-8 (RunFailureReason), Б-14а (bank_decisions_incomplete) и §8а (content_refused).
3. Спорные формы: варианты и выбор
3.1. Прогонный поток НЕ оставлен узким видом
Б-6 рекомендовал «перевесить на книгу, прогонный оставить узким видом». §5 п.5 (ратифицированный состав) говорит только «перевесить на книгу». Выбрал: одна операция. Второй канал с теми же кадрами — вторая реализация, второй источник расхождения и вторая поверхность там, где §5а требует резать. Адресация «кадры этого прогона» выводится клиентом, у которого id прогона уже есть из карточки. ⚠ Это единственное место, где я разошёлся с текстом буллета (не с ратификацией). Если оркестратор считает иначе — возврат стоит одной операции.
3.2. title_raw/kind — паку 161, а не формой сейчас
Промт даёт выбор с требованием аргумента. Выбрал передачу, три довода: производителя настоящих
названий нет (строка 160, Этап 0 не построен) · форма узла «глава ↔ фрагмент» и вердикт детекции —
ровно предмет дизайн-пака, заложенная до него форма заморозила бы догадку · добавление аддитивно
(минор), и смены СМЫСЛА heading не будет — он и сегодня, и после 160 означает метку из данных книги.
Контр-риск («смена смысла = мажор», контраргумент Б-19) закрыт нормой в спеке: деплою, чей парсер
названий не извлекает, ПРЕДПИСАНО отвечать null и ЗАПРЕЩЕНО класть сюда рендеренный порядковый.
3.3. Note.unit_id оставлен (отказ от резки §5а)
Основание резки — «только в фикстуре мока; экраны берут unit.note вложенно». Это дословно тот довод,
который эррата 16.08-г уже опрокинула на TermStatus: «ни один экран не рисует» подменяет «экрана
ещё нет» (замечания — S6). Собственная фраза операции: «A note addresses a unit or a whole chapter»;
из плоского списка перейти к МЕСТУ замечания больше нечем. Б-9 просил ровно «chapter_id обязателен,
unit_id опционален» — исполнено буквально, дефект «замечание, не адресующее ничего» закрыт.
3.4. Параметр limit оставлен (отказ от резки §5а)
§5 п.15 того же заказа требует объявить у limit maximum и подрезание вместо понижения — у
удалённого параметра максимума не объявишь; два пункта одного носителя противоречат друг другу, и
ратифицированный СОСТАВ (§5) весит больше кандидата на резку (§5а). Плюс «референсный клиент не шлёт
ни разу» — свойство одного клиента, а контракт пишется для второго (openapi.yaml:564-570 прежней
редакции декларировала именно портируемость).
3.5. Нагрузка кадров note и bank сохранена (частичный отказ)
§5а называет её мёртвой, потому что она «передаётся и игнорируется». Причину игнорирования батч
устранил: у замечания появился id, и кадр note стал ПРИМЕНИМОЙ ДЕЛЬТОЙ — первая ветка правила
Б-11а. Снятие нагрузки вернуло бы перечитывание всего списка, то есть ровно ту болезнь, которую
Б-11а лечит. Счётчики bank — вторая ветка того же правила («счётчик + скоуп»), строки читаются
дельтой. Сняты те две нагрузки, что мертвы и после батча: EventHello.run_id (поток стал книжным) и
EventResyncRequired.reason.
3.6. Bank.signed оставлен (отказ от резки §5а)
Тот же класс довода «нет экрана» (подпись — S5). total, signed, pending_decisions — три
НЕЗАВИСИМЫХ факта: строку можно решить и не подписать (отклонить), поэтому signed не выводится из
двух других.
3.9. PATCH без 409 при живом прогоне
Б-5 в скобке рекомендует «409 при живом прогоне». Холодный потребитель показал, что это противоречит
собственному обоснованию операции: если переименование не доезжает до движка и не трогает ничего, что
читает прогон, то конфликтовать не с чем, а клиенту нечего написать в подсказке к задизейбленной на
несколько часов кнопке. 409 снят с PATCH. У DELETE он остаётся — там конфликт настоящий:
удалить книгу, против которой идут траты, значит потерять их привязку. Отступление от скобки буллета,
не от ратификации; при несогласии возврат — одна строка.
3.7. Разводка «норма / примечание» по НАБЛЮДАЕМОСТИ
Норма в спеке — только то, что клиент видит и обязан уметь: ETag/If-None-Match/304, Vary на
согласованном представлении, no-store на всех ответах, форма heartbeat, форма id кадра, запрет
буферизации потока (наблюдаемое требование вместо вендорного X-Accel-Buffering). В примечание
компаньона ушло то, что клиент не наблюдает: HTTP/2 на edge, конкретный заголовок прокси, включение
gzip. Так Б-16 (требования деплоя вон из контракта) и §5б шаги 1–2 («записать В КОНТРАКТ») не
сталкиваются: в контракт записана СЕМАНТИКА условных чтений, в примечание — способ развернуть.
3.8. type остался about:blank
RFC 9457 §3.1.1 требует использовать type как первичный идентификатор. Рассматривал
urn:textmachine:error:<code>. Выбрал about:blank + code: URN был бы второй копией code, а
URL — обещанием документа, которого мы не серверим. Отклонение названо в спеке вслух. Зато отклонение
0.2.3 от §3.1.3 (title показывался пользователю вопреки «advisory … for offline log analysis»)
ИСПРАВЛЕНО.
4. Самопроверка исполнением (промт §6)
4.1. Линт и «второй клиент»
cd frontend && npx spectral lint ../docs/architecture/14-api-contract/openapi.yaml \
--ruleset .spectral.yaml --fail-severity=warn
→ "No results with a severity of 'warn' or higher found!" EXIT=0
Прогонялся 6 раз: базовый (на 0.2.3, тоже EXIT=0 — форма команды рабочая и на «до»), после первой
редакции, после де-утечки, после добавления правила отсутствия, после правок по селф-ревью и после
починки дубля ключа, который эти правки внесли (BankPage.allOf[1].properties — единственная
поломка, которую линт поймал за всю сессию, и он её поймал).
cd frontend && npx openapi-typescript ../docs/architecture/14-api-contract/openapi.yaml \
-o <scratchpad>/schema-probe.ts
→ EXIT=0; на заленденном каноне 2608 строк, после дофикса 16.08 — 2798
Прогонялся трижды: после первой редакции, после де-утечки, после правок по селф-ревью.
Класс PD-172 проверен генератором, а не мысленно, и проверен ДВАЖДЫ. Первый прогон: в генерённом
BookIntake порядок стал title? → source_lang → target_lang → file, файл ПОСЛЕДНИМ (в редакции
0.2.3 генератор ставил file вторым, впереди обоих обязательных языков, и произведённая им форма
получала 400). Второй прогон, после находки F1: title сделан обязательным, необязательных членов в
схеме не осталось — title · source_lang · target_lang · file, и теперь порядок не зависит от того,
эмитит генератор по properties или «сначала required». Проверил также, что $ref+description не
теряют описание (Run.book_id, Problem.cause, Blocked.book_id — на месте); что Unit.target
вышел target: string (не опциональным), то есть условная обязательность действительно НЕ доезжает
через if/then — К-11 подтверждён на новой схеме и потому продублирован словами; и что три
восстановленных enum (UnitState, RejectReason, EventEnvelope.event) вышли закрытыми юнионами.
4.4. Резка прозы — отдельный проход, проверенный типами
⚠ Пере-счёт 16.08 (ФБ-10): исходный вывод «доля прозы не сдвинулась» держался на методе, который не был приложен, и другим методом не воспроизводится. Скрипт приложен ниже, чтобы число можно было оспорить командой, а не поверить на слово:
# /tmp/prose_form.py — проза = строки блочных скаляров description/summary (и строка ключа),
# форма = всё прочее непустое; конец блока определяется по возврату отступа на уровень ключа.
import sys, re
def measure(path):
lines = open(path).read().split('\n'); prose = form = 0; in_block = False; key_indent = 0
for ln in lines:
if not ln.strip(): continue
indent = len(ln) - len(ln.lstrip())
if in_block:
if indent > key_indent: prose += 1; continue
in_block = False
m = re.match(r'^(\s*)(description|summary):\s*\|', ln)
if m: prose += 1; in_block = True; key_indent = len(m.group(1)); continue
if re.match(r'^\s*(description|summary):\s+\S', ln): prose += 1; continue
form += 1
return prose, form
0.2.3 : проза 540 · форма 760 · доля 42 %
0.3.0 заленденный: проза 930 · форма 1185 · доля 44 %
0.3.0 + дофикс : проза 1055 · форма 1183 · доля 47 %
Честный вывод вместо прежнего: доля прозы выросла — с 42 % до 44 % при батче и до 47 % после дофикса, потому что каждая находка холодного потребителя закрывается ПРАВИЛОМ, а правило это проза. Абсолютные числа (форма 760 → 1183) не спорны ни при каком методе; спорна именно доля, и утверждать «вернул к уровню 0.2.3» я больше не могу. Что резалась вода, а не норма, доказано отдельно — типами и мультимножеством модальных глаголов ниже, и эти два доказательства от метода мерки не зависят.
Первая редакция батча дала 2586 строк при доле прозы 50 % (приложенным скриптом). То есть я исполнил букву §5а (вынести названный генезис в компаньон) и провалил её предмет: объяснял ПОЧЕМУ прямо в описании поля, хотя рядом лежит компаньон ровно под это.
Проход по всему файлу: в описании остаётся правило и обязанность потребителя, «потому что» уходит в компаньон либо снимается. Итог прохода, ТЕМ ЖЕ скриптом: 2586 → 2315 строк, проза 1163 → 930 (−20 %), доля 50 % → 44 %. Дофикс потом вернул часть — 1055 и 47 %, — потому что каждый его пункт это новое ПРАВИЛО; ниже видно, что правил прибавилось, а не воды.
Что резалась именно вода, а не норма, проверено двумя способами исполнением:
# 1. Генерённые типы до и после резки — с вычеркнутыми JSDoc-комментариями
npx openapi-typescript … -o schema-probe.ts # до
npx openapi-typescript … -o schema-after-cut.ts # после
python: re.sub(r'/\*\*.*?\*/','',t,flags=re.S) на обоих → сравнить
→ TYPE SHAPE IDENTICAL: True
Ни одно поле, тип, enum, required или oneOf не сдвинулись — резка коснулась ТОЛЬКО текста.
# 2. Множество нормативных утверждений до и после
python: для каждого MUST / MUST NOT / MAY / SHOULD взять модал + 4 следующих слова, сравнить мультимножества
→ 42 → 41
Единственная потеря — цитата semver §4 («Major version zero … anything MAY change at any time») из раздела Versioning. Это ВНЕШНЯЯ цитата, а не наше правило; само правило («пока версия 0.x, минор — полоса ломающих правок») на месте. Правил не потеряно ни одного.
Что осталось прозой и почему это не вода: самые крупные блоки после резки — словарь ErrorCode
(42 строки), протокол потока (30), шапка info с четырьмя правилами документа (49), Problem,
Revision, схема безопасности, значения RejectReason/RunFailureReason/BookStatus. Это и есть
контракт: смысл значения словаря выразить схемой нечем.
4.2. Анти-утечка
grep -niE "draft|wave|stage names|mined|miner|ruby|finaliz|chunk|langpack|sanitiz|flagged|verdict|prompt|engine|pipeline|glossar|promote|snapshot|CJK" openapi.yaml
→ ровно 1 вхождение: строка 18 — сама формулировка запрета в шапке.
(пере-ран после дофикса 16.08: по-прежнему ровно одно, там же)
Список составлен из слов, названных Б-0, плюс найденные мной самостоятельно: glossar (два слова на
одну сущность — «bank» и «glossary»), snapshot (у движка это конкретный артефакт ре-биллинга),
engine, pipeline, promote. Пример «CJK leak in the ru output: 第一节», который в 0.2.3 уезжал в
frontend/src/api/schema.ts:1047, снят.
4.3. Код первичен: что перепроверял руками
Не доверял якорям research/28 — открывал места. Подтвердилось: fail() и прямые WriteProblem
(v0.go:238-250,333-345,356,409-423,531,542-574) · problem.go:14-28 (instance в структуре нет) ·
reqid.go:16,27 (X-Request-Id на каждом ответе) · middleware.go:38 (no-store на всех) ·
server.go:99,134 (403 и охраняемый 404) · csrf.go:51-62 (OPTIONS освобождён + карваут bearer) ·
pgstore/books.go:510-511,586-587 (defaultPage=100, maxPage=1000, понижение до дефолта) ·
pgstore/books.go:677-681 («глав сделано» уже считается в SQL) · 00002_readmodel.sql:102-105,127-142, 158-199 (обе фазовые колонки главы · чеки target · notes.id/created_at/unit_id · exports. failed_reason/ready_at) · runs/runs.go:187-231 (paused в allowlist старта, HasLiveRun по
своей книге) · reconcile.go:466-527 (outcome(): полоса отказов → failed) ·
pipeline/refusal.go:29-46 + cmd/tmctl/main.go:50-59 (полоса 10–19) · books/books.go:118-121,141, 355-391 (форма кода, имя файла → титул, расширение → читатель) ·
frontend/src/api/vocabulary.ts:159-163 (шов ПОТРЕБЛЯЕТ TermStatus — эррата 16.08-г верна) ·
frontend/src/api/client.ts:99-106 (ревизия обхода = минимум).
Одно расхождение с якорями отчёта: research/28 Б-7а п.4 указывает runs.go:229 для «paused
входит в допустимые для старта» — сегодня это readyToTranslate на runs/runs.go:227-231. Смысл
подтверждён, номер уплыл; в компаньоне записан диапазон.
5. Адверсариальное селф-ревью (промт §6.5)
Два независимых субагента, author≠reviewer, видели только артефакты, не мой ход мысли:
- (а) опровергатель полноты — получил research/28 (§5 · §5а · §5б · §8 · §8а · §6 + ревью-шапку), D39.138 п.2, канон 0.2.3 и канон 0.3.0. Мандат: «пункты заказа, исполненные неверно или неполно, и правки СВЕРХ заказа». Этого отчёта и прозы компаньона не получал (сам назвал, что грепал только заголовки компаньона — его существование несущее для двух вердиктов). Вернул таблицу покрытия по всем ID заказа + 4 находки класса (A) «не исполнено», 6 класса (B) «сверх заказа», 10 класса (C) «внутренние противоречия».
- (б) холодный потребитель — получил ТОЛЬКО новую спеку, с запретом открывать любой другой файл репозитория (запрет соблюдён: «хочется контекста, которого в файле нет» он записывал находкой, а не шёл искать). Мандат: «ты пишешь клиента с нуля». Вернул 47 находок, ранжированных по цене реализатору.
Ноль находок не случился ни у одного, и это правильный исход. Пере-считано по таблицам ниже (ФБ-10): двумя линзами найдено 57 позиций — 46 исправлено (§5.1) и 11 отклонено с причиной (§5.2); третьей, кросс-модельной, ещё 12 (§5.3). Итого 69 находок, из них 57 исправлено;
четыре из них — дефекты, которые прошли бы в код P7 и стоили бы там на порядок дороже (F1, F8/F9,
F32, C2). Обе линзы независимо нашли одно и то же в трёх местах (структурная версия · X-TM-Client
против bearer · Book без ревизии в ответе на запись) — это и есть подтверждение, что находки не
шум.
5.1. Исправлено (46 позиций)
| # | Линза | Находка | Что сделано |
|---|---|---|---|
| F1 | б | BookIntake.required ставит file перед необязательным title. Генератор «сначала required, потом optional» — обычный дефолт там, где язык не даёт положить параметр с дефолтом первым, — кладёт title ПОСЛЕ файла, и форма получает 400. Тот же класс PD-172 в новой маскировке: моя перестановка properties защищала одно семейство генераторов и была опрокинута массивом required тремя строками ниже |
Класс убит структурно, а не прозой: title сделан ОБЯЗАТЕЛЬНЫМ, пустая строка = «назови по файлу». В схеме не осталось НИ ОДНОГО необязательного члена — значит нет порядка, который генератор мог бы выбрать и нарушить правило. Перепроверено генератором: title · source_lang · target_lang · file |
| F2 | б | У UnitState пропал enum — регрессия, внесённая моей правкой описания |
Восстановлен |
| F3 | б | У RejectReason пропал enum, при том что проза называет словарь закрытым |
Восстановлен, 4 значения |
| F4 | б | EventEnvelope.event — восемь имён кадров живут только в markdown-таблице |
enum из 8 имён |
| F5 | б | limit: схема «выше 1000 невалидно», проза «подрезается», словарь ошибок несёт out_of_range |
Записано: подрезается, НЕ отвергается; «максимум, объявленный здесь» |
| F6 / C9 | б, а | X-TM-Client required: true против прозы «предъявитель bearer освобождён» |
Записано: required — потому что браузерный клиент обязан всегда; bearer освобождён схемой безопасности |
| F8 / F9 | б | Дельта-чтение строго >, а Revision требует catch-up >=; кадр bank предписывает читать строки «ревизией этого кадра» — что при строгом сравнении ВСЕГДА возвращает пустоту. Основной поток всей банковской фичи |
?after_version= сделан ВКЛЮЧИТЕЛЬНЫМ (та же причина, что у catch-up: одна транзакция — одна ревизия, но НЕСКОЛЬКО строк); кадр bank переписан на «ревизию, которую клиент применил последней»; записано правило водяного знака |
| F10 | б | Рукопожатие проверяет МАЖОР, а 0.x ломает миноры — два правила документа исключают друг друга | Проверка версии сделана минор-чувствительной, пока мажор 0; то же на Capabilities.contract_version |
| F11 | б | Переименование объявлено бесплатным («не доезжает до движка») и тут же отвечает 409 при живом прогоне | 409 снят с PATCH — конфликтовать не с чем (§3.9). У DELETE остаётся: там конфликт настоящий |
| F12 | б | «Клиент никогда не опрашивает read-модель» — неправда: ни один кадр не несёт книжных величин библиотеки, а поток книжный | Формулировка приведена к правде: read-модель не опрашивается, чтобы ОБНАРУЖИТЬ изменение; читается по кадру/навигации/фокусу, и условные чтения делают это дёшево. Отсутствие ленты на библиотеку записано направлением в компаньон |
| F13 | б | BankTerm.id объявлен переживающим пересборку и выводится из окна, которое ПЕРЕ-РАЗБОР двигает |
Записано прямо: пере-разбор он НЕ переживает; при смене structure_version клиент перечитывает банк и не считает решения перенесёнными |
| F14 / C8 | б, а | «CORS-слоя нет вовсе» против 403 по origin; плюс 403 по origin срабатывает БЕЗ куки, а описание требует сессии | Обе формулировки уточнены по коду (csrf.go:52,62,65-66) |
| F15 | б | stopped одновременно «выводится контрактом» и значение RunStatus |
Записано: выводит ПЛАТФОРМА, клиент читает поле и никогда не вычисляет |
| F16 | б | Правило «дропать устаревшее чтение», прочитанное буквально, ломает опрос экспорта | Правило сужено: сравнение ПО СКОУПУ и ПО КОЛЛЕКЦИИ; опрос экспорта не дропается |
| F17 / C10 | б, а | Ответы POST/PATCH возвращают Book, у которого нет revision — упорядочить их против кадра нечем |
Добавлен Book.revision (ревизия КНИГИ, не библиотеки) |
| F18 | б | Меняется ли ETag от limit/cursor/after_version |
Записано: валидатор привязан ко всему запросу, строку запроса включая |
| F19 | б | Нет карты «код → статус» | Каждое значение ErrorCode получило свой HTTP-статус |
| F20 | б | Нельзя узнать ДО загрузки, принимает ли деплой книги (404 неотличим от прочих 404) | Capabilities.intake_enabled |
| F21 | б | Можно ли экспортировать недопереведённую книгу | Записано: можно; что окажется внутри — работа S7 |
| F22 | б | {"title": null} в merge-patch — удаление или 400 |
Записано: 400 |
| F23 | б | Едут ли агрегаты банка на ДЕЛЬТА-чтении | Записано: да — «любой ответ на запрос без cursor, дельта включая»; и всегда описывают весь банк |
| F24 | б | Export.url — свой origin? под кукой? fetch или навигация? |
Записано: свой origin, браузер НАВИГИРУЕТ; это загрузка, а не вызов |
| F25 / F26 | б | Что с потоком при удалении книги; сколько потоков держать | Оба записаны нормой: удаление закрывает поток и реконнект → 404; один поток на книгу, ленты на библиотеку нет и открывать поток на строку списка клиент не должен |
| F27 / F39 | б | Retry-After обещан на key_in_flight и объявлен только на опросе экспорта |
Объявлен на ответе Conflict |
| F28 | б | Показывать ли request_id пользователю |
Записано: MAY; идентифицирует запрос, не человека |
| F29 | б | number и heading оба null |
Записано: метка — из позиции в порядке чтения |
| F30 | б | Unit.note единственное, а у пары легально несколько замечаний |
Unit.note → Unit.notes массивом; это же согласуется с моим собственным правилом «пустая коллекция — пустой массив» |
| F31 | б | Кадр status несёт только paused_reason: о rejected и failed объявляет, а причину не даёт — при том что обоснование самого кадра «это ровно тот момент, когда экран обязан сказать почему» |
EventStatus получил reject_reason и failure_reason |
| F32 / F33 / C7 | б, а | Ничто не сообщает, что structure_version сдвинулась — версия была только в hello и двух конвертах; BankPage/NotePage не говорят, к какой структуре относится их содержимое. Две обязанности документа («перечитать банк», «уронить якоря на пары») остались без триггера |
structure_version — на КАЖДОМ кадре (EventBase) и в BankPage/NotePage |
| F34 / F35 | б | Дельта фильтрует по «версии строки», которой у строк нет; и нечем ответить на устаревший водяной знак | Водяной знак = revision конверта (записано); устаревший → 400 cause.code: version_too_old — тот же ответ и тот же смысл, что у мёртвого курсора |
| F36 / C3 | б, а | 500 не объявлен нигде, а internal_error — в закрытом словаре кодов |
Записано: 500 сознательно не перечисляется по операциям (§4 №1, Zalando «do not document»), но тело у него то же и код тот же |
| F37 | б | Нет восстановления экспорта после перезагрузки; нет Idempotency-Key на создании |
Idempotency-Key добавлен на POST /exports; обоснование format переписано (списка экспортов нет) |
| F38 | б | Last-Event-ID и X-Request-Id живут только в прозе — генерённый клиент их не увидит |
Last-Event-ID объявлен параметром streamBookEvents; X-Request-Id — компонент-заголовок на всех девяти ответах-ошибках |
| F41 | б | TermStatus.in_progress недостижим |
Записано: это состояние СТРОКИ; POST /bank/decisions его не ставит |
| F42 | б | Замечания объявлены «в порядке чтения», а ключа порядка на проводе нет: кадр note — единственный, который нельзя потерять, — вставить некуда, и дельта-чтение слить не с чем |
Порядок переобъявлен: created_at, затем id — единственный тотальный порядок, который считают обе стороны; порядок чтения строит клиент по дереву, которое у него уже есть |
| F43 | б | Нет чтения одной главы; кадр chapter про неподгруженную главу некуда деть |
Записано: такой кадр ИГНОРИРУЕТСЯ — кадр не повод пейджить коллекцию. Операция — направлением к паку 161 |
| F45 | б | Нет состояния «останавливается»: 202 возвращает живой статус, кнопка включается обратно |
Записано: 202 не значит «остановлен», значения stopping нет, клиент держит своё pending до кадра status |
| F46 | б | Blocked называет книгу id, а не именем |
Записано: клиент читает карточку той книги |
| F0 | б | Из спеки нельзя сделать НИ ОДНОГО успешного вызова: оба способа аутентификации недостижимы из неё | Названо место входа (GET /auth/login, вне версионного префикса) прямо в разделе Transport |
| A1 | а | Сжатие ратифицировано «В КОНТРАКТ» (D39.138 п.2д), а спека вынесла его ЦЕЛИКОМ в примечание — я перестарался с разводкой по Б-16 | Обязанность честить Accept-Encoding на JSON и НЕ сжимать text/event-stream — норма спеки; в примечании остался только слой исполнения, которого клиент не наблюдает |
| A4 | а | §8а требует класс 2 «с лимитом попыток на аккаунт» — носителя не было нигде | Записано на content_refused: сервер ограничивает число попыток одного аккаунта, граница его и на провод не идёт |
| C1 | а | Правило отсутствия объявляет ОДНО исключение, а их два (Problem и агрегаты BankPage) |
Названы оба |
| C2 | а | 410 Gone возвращает Problem, у которого code — закрытый словарь без значения gone: конформный сервер не может ответить конформным 410 |
gone добавлен в ErrorCode |
| C5 | а | page_size_default без максимума может превысить максимум, который клиент вправе ЗАПРОСИТЬ |
maximum: 1000 |
| C6 | а | Правила докачки опираются на тождество, которого id кадра не несёт: «позиция в потоке» не уникальна между потоками одной книги, а обе нормы (буфер докачки и 204) решают именно «тот ли это поток» |
id переопределён как позиция в истории событий КНИГИ; 204 переформулирован без «поток, названный Last-Event-ID» |
| C4 / B1 | а | Chapter.units_done как сегментный счётчик противоречит «Book.chapters_done никогда не идёт назад» и не определён для главы ВНЕ текущего сегмента |
Формулировка уточнена: счётчик ПРОХОДА (0 — не дошли, units_total — прошли, глава вне прохода держит прежнее) + явная разводка с Book.chapters_done. Сама сегментная логика — прямой заказ промта батча §3, а не самодеятельность: опровергатель промта не видел |
5.2. Отклонено — с причиной (11 позиций)
| # | Линза | Находка | Почему отклонено |
|---|---|---|---|
| A2 | а | Три строки таблицы §5а не вырезаны: Note.unit_id, Bank.signed, нагрузка кадров |
Диспозиция была записана ДО ревью — §3.3, §3.5, §3.6 этого отчёта и §6а компаньона, с контраргументом на каждое. Опровергатель отчёта и прозы компаньона не видел по условию мандата; его вердикт «без прикрытия» основан на неполном входе. Аргументы стоят |
| A3 | а | Файл вырос на 63 %, доля прозы выросла | ПРИНЯТО и ИСПРАВЛЕНО отдельным проходом (§4.4), после того как владелец назвал ту же вещь прямо. Итог прохода: 2586 → 2315 строк, проза −20 %, доля 50 % → 44 %. ⚠ Прежняя приписка «вернул ровно к уровню 0.2.3 (36 %)» СНЯТА дофиксом: она держалась на мерке, где ключевая строка `description: |
| B2 | а | Book.reject_reason сделан обязательным вопреки §4 №22 |
Сам опровергатель называет правку justified: единое правило отсутствия (§5 п.16 того же заказа) не держится с неперепроверенным исключением, а прежний довод («деплой старше этого минора») внутри ЛОМАЮЩЕГО минора пуст |
| B3 | а | Note.message снят, введён Note.code |
Признано justified самим опровергателем; аргумент — §2.3. Вынесено вопросом в §8 |
| B4 / B6 | а | Контентность BankTerm.id записана; дисциплина Б-21 применена в спеке |
Обе — прямые «стоит записать» из research/28 §3 п.6 и Б-21 |
| B5 | а | Новый MUST NOT на heading при открытом К-2 |
Кодифицирует собственное предупреждение движка (manifest.go:80-86). Без него P7 положил бы в поле рендеренный порядковый, и смысл поля сменился бы после строки 160 — то есть мажор |
| F7 | б | Четыре code-поля — свободные строки без словаря |
По замыслу: cause.code/ErrorItem.code открыты специально (это и есть механизм расширяемости варианта B), Note.code/Export.failure_code не enum, пока нет источника — §6 obstacle |
| F40 | б | TermStatus не имеет значения для ОТКЛОНЁННОГО термина — экран подписи не покажет, что уже отклонено |
НЕ ИСПРАВЛЕНО СОЗНАТЕЛЬНО, вынесено вопросом владельцу (§8 п.4): лечение — одно поле BankTerm.decision, а «добавочные поля BankTerm НЕ заводить» — слово владельца 15.08 (D39.136 п.4б), повторённое промтом батча. Право сказать «этого делать не надо» есть, права молча сделать иначе — нет |
| F44 | б | localized — механизм, который ни один код не использует |
Ратифицированный слот (§8 п.4, форма google.rpc.LocalizedMessage); помечен в спеке словами и носителем, чтобы предупреждение не пережило своё основание |
| — | б | «Intl.DisplayNames — браузерный API в контракте, а bearer заявлен для не-браузера» |
Верно как замечание. LangCode отдаёт КОД; чем клиент рисует имя — его дело, и упоминание одного API в описании ни к чему не обязывает. Оставлено как есть |
| — | б | Три дизайн-возражения: двухшаговая загрузка · X-TM-Client параметром, а не обязанностью схемы · Book против BookDetail в ответах на запись |
Первое — направление вне батча (§5 «не в батч»); второе — прямой заказ Б-15 («объявить машинно», иначе правило видно только человеку); третье — форма ответа на запись, менять её не заказано |
5.3. Третья линза — КРОСС-МОДЕЛЬНАЯ (fable), по слову владельца
Обе линзы §5.1–5.2 — одного семейства со мной, и их согласие независимым подтверждением не считается
(тот же долг записан у research/28 §10 и строкой 190). По слову владельца добавлен проход агентом
ДРУГОЙ модели по диффу 0.2.3 → 0.3.0 и по заказу; входы те же артефакты, отчёт и промт не давались.
Мандат отличался прицелом: (1) что будет отлито в код P7 неверно · (2) смысловые дефекты, проходящие
линтер · (3) сверка с заказом · (4) не потеряна ли норма при резке прозы — отдельный риск, которого
у первых двух линз не было, потому что резка случилась после них.
Результат: 1 HIGH и 4 MEDIUM, все — дефекты моей новой формы; ни одного неисполненного пункта заказа. В таблице ниже 12 строк: 11 приняты и исправлены, одна отклонена осознанно (числа интейка — аргумент в самой строке). Прежняя формулировка «все девять» не сходилась с таблицей и исправлена дофиксом (ФБ-10).
| # | Находка | Что сделано |
|---|---|---|
| HIGH-1 | id кадра объявлен «позицией в истории КНИГИ, строго возрастающей, по одному на кадр» — а hello приходит первым на КАЖДОМ подключении, end и resync_required тоже принадлежат соединению. У реализатора два пути, и оба против текста: минтить служебным кадрам книжные номера (два одновременных зрителя тратят номера друг друга, Last-Event-ID одного указывает на кадры, которых второй не видел) либо повторять последний номер (тогда «one per frame» — ложь). На этом же id стоят правило 204 и клиентский гард, то есть ошибка отливается в миграцию, воркер и шов клиента разом |
Разведено: служебные кадры несут id последнего кадра ИСТОРИИ и своего номера НЕ тратят, повтор id на них легален; клиент хранит последний увиденный id и отправляет обратно, но им не считает |
| MED-2 | limit: схема (maximum: 1000) приказывает отвергнуть то, что проза приказывает подрезать. Сервер, собранный со схемной валидацией запросов (oapi-codegen — кандидат P7), ответит 400 на limit=5000 и будет конформен схеме и неконформен собственной прозе |
Записано явно: maximum бьёт по тому, что вправе ЗАПРОСИТЬ КЛИЕНТ; сервер, получивший больше, MUST подрезать и MUST NOT отвергнуть, а деплою со схемной валидацией предписано вывести параметр из-под отказа |
| MED-3 | createExport несёт Idempotency-Key, чья семантика без 409 неисполнима, а 409 в ответах нет (у createBook и startRun есть) |
409 добавлен |
| MED-4 | Машина состояний экспорта недоопределена: ready объявлен терминальным ⇒ expired недостижим и значение мертво; expires_at описан как null вне ready ⇒ в состоянии expired поле «когда истекло» обязано быть пустым |
Переписано: pending → ready → expired либо pending → failed; опрос останавливается на трёх, но терминальность опроса и терминальность состояния разведены; expires_at заполнен там, где артефакт существует — в ready И в expired |
| MED-5 | Порядок замечаний, который я ввёл по находке F42, опирается на сравнение id как тай-брейка — а Id тем же документом ЗАПРЕЩАЕТ сортировать по id; вдобавок лексикографический порядок строк и серверный совпадать не обязаны, так что «единственный порядок, который считают обе стороны» фактически неверно |
Тай-брейк снят с клиента: сервер разрешает ничьи по-своему, клиент этого НЕ воспроизводит и ставит пришедшее кадром замечание после всех, что держит с тем же created_at. Запрет сортировки по Id остаётся в силе |
| LOW | page_size_default несёт maximum: 1000 внутри абзаца, отрицающего вторую копию числа |
Формулировка переписана: бound — следствие единственного определения на limit, а не вторая копия |
| LOW | «MUST tolerate an unknown value under a minor bump» недостижимо весь 0.x: клиенту велено отказываться при любом расхождении минора, значит ветка толерантности физически не получит значения | Названа полоса, для которой правило написано: аддитивные миноры (с 1.0) и деградация внутри версии; в 0.x это пол, а не разрешение работать против чужого минора |
| LOW | Book.chapters_done «никогда не идёт назад» — необеспечимый абсолют: пере-разбор пересчитывает и его, и chapter_count |
Сужено до «внутри одного structure_version» |
| MED (резка) | Потеряно правило «клиент не читает пропуск номера как потерянный кадр» (было в 0.2.3) — и потеряно ровно тогда, когда стало НУЖНЕЕ: в 0.2.3 id был ревизией и дыры были легальны по построению, в 0.3.0 id — «по одному на кадр», а склейка создаёт настоящие дыры. Клиент, дорожащий несклеиваемыми note, прочитал бы дыру как потерю и ушёл в лишний resync |
Возвращено на EventEnvelope.id вместе с явным «дыра легальна» |
| LOW (резка) | Потеряно «книга легально не имеет глав вовсе» — единственное место, где пустое дерево объявлялось законным | Возвращено в listChapters |
| LOW (резка) | Потеряны числа интейка «16 частей / поле ≤ 1 КБ» без переезда в Capabilities |
Отклонено осознанно: Б-3 показал, что эти два числа в спеке и в коде описывали РАЗНЫЕ правила (код считает части ДО файла, спека — все), а комментарий кода называет их «не делом контракта». Клиент узнаёт факт кодами too_long/too_many_parts в errors[]; превалидировать ему нечего — он шлёт четыре части |
| LOW | Компаньон (приложение А-2) давал content_refused как «400/409», спека — только 400 |
Компаньон исправлен |
Что третья линза подтвердила независимо (ценно именно потому, что модель другая): исполнение Б-0
по ЭРРАТЕ, а не по опрокинутой рекомендации (TermStatus — ось, TermOrigin — переименование) ·
чистоту греп-листа анти-утечки, включая проверку СГЕНЕРЁННЫХ типов (wave|draft = 0) · что основание
переписанного resume-абзаца верно по коду (pgstore/runs.go:672-676, runs/runs.go:227-231,
reconcile.go ветка stopped) · что Book.chapters_done действительно Ц1 (величина уже в SQL) ·
что baseline диффа аутентичен (old.yaml = байт-зеркало фронта, ещё 0.2.3).
Что она подняла на ратификацию, а не на исправление: частичный отказ от резки §5а (четыре строки,
каждая с контраргументом) — «должен окнуть оркестратор, а не констатировать батч»; и Note.message →
Note.code как решение класса «политика фраз замечаний», принятое батчем без слова владельца. Оба
уже стояли в §8 ниже; после этой линзы они там усилены, а не добавлены.
6. Obstacle reporting — что НЕ удалось и не сделано
- Гейт на утечку конвейера не построен — и не должен был: тест по образцу
generality.test.tsживёт в зоне фронта, которая заморожена. Я записал ПРАВИЛО и список грепа в компаньон и прогнал греп руками; автоматической защиты до разморозки нет. Носитель — пинг фронту 16.08. - Число страницы замечаний не изменено и не могло быть. Замера масштаба нет ни одного (research/28
§7, §10: «сколько замечаний даёт настоящая книга, не знает никто»). Я снял ЧИСЛО из спеки в
Capabilities.page_size_default(это правильное место для значения деплоя) и оставил оговорку в спеке и в компаньоне. Новое число не выдумано. - Дельта-чтение и структурная версия были дефектны в первой редакции батча и починены только селф-ревью (F8/F9, F32). Это не «удалось», а «поймано вторым рубежом»: механизм, который Б-11а заводит ради экономии 372 МБ, в первой моей редакции возвращал пустое множество в своём главном сценарии, а триггера «структура сдвинулась» не существовало. Обе ошибки — мои, обе были бы отлиты в код P7.
Note.codeне стал enum. Карта «код → фраза» пуста, фразы пишет владелец (строка 148). Enum-словарь без фраз был бы формой без источника — тот самый класс, от которого предостерегает §5а-оговорка. Коды предложены в компаньоне (◆), enum появится вместе с фразами.Export.failure_codeне enum — по той же причине: способы упасть зависят от форматов, которых не существует (S7). Носитель записан в самой схеме.- Рантайм не поднимался. Ни платформа, ни фронт не запускались: платформа 12 из 20 операций не
отвечает вовсе, а фронт заморожен и живёт на моках. Всё, что я утверждаю о поведении сервера,
выведено чтением кода (§4.3), а не наблюдением. Соответственно НЕ проверено исполнением:
реальная форма
problem+jsonпри 413/408 посреди тела; поведениеIdempotency-Key(не построен); условные чтения (не построены). - Зеркало фронта расходится с каноном на момент сдачи — намеренно (зона заморожена, D39.136 п.2).
cmpпри лендинге ОБЯЗАТЕЛЕН, порядок — канон первым, зеркало отдельным зонным коммитом (D39.138 п.3). - Кросс-СЕМЕЙНОЙ проверки по-прежнему нет, кросс-МОДЕЛЬНАЯ есть. Третья линза (§5.3) — другая модель того же семейства, по слову владельца; она нашла HIGH, который две предыдущие пропустили, то есть разнос по модели окупился. Настоящий не-Claude проход при границе «$0 по внешним провайдерам» остаётся невозможным — долг записан строкой 190.
- Платформе и фронту прибавилось работы, и это не оценено мной в часах. Батч ломает
wireProgress,projectBook,projectRun, словари банка, формуProblemи путь потока; фронту —format.ts,About.tsx,Status.tsx,vocabulary.ts,contract.ts, все моки. Оценка — за зонами.
7. Дифф-сводка «было → стало» по операциям
| Операция | Было | Стало |
|---|---|---|
getCapabilities |
— | НОВАЯ: версия · пары · порог · форматы · размер страницы |
listBooks |
Library |
BookPage (на Page), ETag/304, объявлен порядок |
createBook |
file вторым в схеме; часть после файла молча терялась; идемпотентности нет; Location нет |
file последним; часть после файла → 400 + errors[]; Idempotency-Key; Location; 409; отказ по паре кодом |
getBook |
BookDetail |
+ ETag/304; run стал required+nullable |
updateBook |
— | НОВАЯ: merge-patch, только title; принимается и при живом прогоне |
deleteBook |
— | НОВАЯ: 204, 409 при живом прогоне |
listChapters |
«Default page size 5000» в прозе | ChapterPage + structure_version + ETag/304; число ушло в capabilities |
listUnits |
— | + structure_version, ETag/304, 410 Gone |
listNotes |
NoteList; «в порядке чтения» без ключа порядка |
NotePage + structure_version + ?after_version= + ETag/304; порядок created_at,id; Note получил id/created_at/code, chapter_id стал обязательным, message снят |
listBankTerms |
Bank; предупреждение «канала нет» |
BankPage + ?after_version= + ETag/304 + pending_decisions/complete; агрегаты на первой странице; предупреждение снято |
submitBankDecisions |
promote/decline, decisions без maxItems |
approve/decline, maxItems: 1000 |
getRunOptions |
{ceiling} |
+ blocked: {code, book_id} |
startRun |
verify_bank; 409 без причины |
stop_for_signing; Idempotency-Key; 409 несёт blocked; названо лечение «новый прогон с бОльшим потолком» |
streamRunEvents → streamBookEvents |
поток на ПРОГОНЕ; конца нет; склейка без ограничений; id = ревизия; 8 кадров |
поток на КНИГЕ; кадр end + 204; склейка только состояний; id = позиция, ревизия в data; EventCeiling снят, EventEnd добавлен |
getRun |
— | НОВАЯ |
stopRun / resumeRun |
Run без book_id; «MUST NOT offer resume» и никакого лечения |
Run с book_id/failure_reason/progress; 409 c cause.code; лечение названо |
getUsage |
paused_reason необязателен, типизирован прогонным enum |
halt_reason required+nullable, свой AccountHaltReason |
createExport / getExport |
{id, ready, url?}; опрос не завершался |
{id, revision, state, format, expires_at, failure_code, url}; формат из capabilities; правило доступа к ссылке |
Проход резки прозы (§4.4): 2586 → 2315 строк, проза −20 %; типы не изменились ни на поле.
Прочее, изменённое селф-ревью: BookIntake.title стал ОБЯЗАТЕЛЬНЫМ (пустая строка = «назови по
файлу») — в схеме не осталось необязательных членов, и порядок частей больше не зависит от повадки
генератора · Unit.note → Unit.notes массивом · Book получил revision · EventBase получил
structure_version · EventStatus получил reject_reason и failure_reason · ErrorCode получил
gone · Capabilities получил intake_enabled · ?after_version= стал включительным.
Снято с провода: Book.genre · BookIntake.genre · Book.progress · схема Counter ·
BookStatus.finalizing · Problem.instance · EventCeiling · EventHello.run_id ·
EventResyncRequired.reason · Note.message · движковые значения TermStatus/TermOrigin ·
операция streamRunEvents.
8. Вопросы оркестратору (через владельца) — не интерпретировал
- §3.1 — прогонный поток снят вместо «оставить узким видом». Если это чтение §5 неверно, возврат стоит одной операции.
- §2.3 / 3.3 —
Note.messageснят в пользуNote.code,Note.unit_idоставлен вопреки §5а. Оба — мои чтения ратифицированного принципа, а не буквы носителя. content_refusedвRejectReasonзаведён под непостроенный прескрин (ПТ-16, строка 94). Это ратифицировано К-9, но остаётся значением словаря без сегодняшнего писателя — если правило «форма без источника» весит больше, значение снимается одной строкой.- Экран подписи не может показать, ЧТО уже отклонено (находка F40 холодного потребителя; не
исправлена сознательно). Чтение банка отвечает
pending_decisions/complete— сколько осталось, но не КАКИЕ строки решены:TermStatusэто состояние строки банка, а решение живёт отдельной таблицейbank_decisions(00002_readmodel.sql) и на провод не проецируется ни одним полем. Экран, перезагруженный посреди стопа, знает «осталось 17 из 300» и не знает, какие семнадцать; на сотнях строк это разница между «доделать» и «пройти заново». Лечение — ОДНО полеBankTerm.decision(approve/decline/null), Ц0: чтение банка не построено. Не сделано, потому что «добавочные поляBankTermНЕ заводить» — слово владельца 15.08 (D39.136 п.4б), повторённое промтом батча §3. Моё чтение: тот запрет закрывал вопрос ПТ-33 о полях СМЫСЛА («смысла хватает, но не слишком подробно»), а это поле — состояние подписи, то есть предмет Б-14а, который в батч входит. Но чтение — не право сделать иначе. Вопрос владельцу; если ответ «заводить», правка стоит четырёх строк схемы и одной строки P7.
9. Дофикс-раунд 16.08 (заказ приёмки D39.142): диспозиция по каждому ФБ
⚠ Провенанс дерева. Часть правок дофикса приехала в рабочее дерево от ПАРАЛЛЕЛЬНОЙ сессии, которую владелец остановил. Я не принимал их на веру: каждую сверил с заказом и с кодом, три дефекта в них нашёл и починил, один невыполненный пункт доделал. Ниже — состояние ПОСЛЕ этой сверки; там, где я правил чужую правку, это названо.
| ФБ | Что требовалось | Диспозиция |
|---|---|---|
| 1 (HIGH) | Wire-форма кадра неоднозначна: EventEnvelope{event,id,data} как объект против SSE-фрейминга |
ИСПОЛНЕНО. На схеме стоит дословный пример кадра и сказано прямо: EventEnvelope — ОПИСАНИЕ, а не форма JSON на проводе; event и id — поля SSE (event.type/event.lastEventId у EventSource), телом кадра является data и только оно; heartbeat — комментарий, не кадр |
| 2 (HIGH) | Книга в покое без истории: hello без id → клиент без Last-Event-ID → вечный цикл |
ИСПОЛНЕНО. История нумеруется с 1, служебные кадры пустой книги несут 0; Last-Event-ID: 0 попадает под уже существующее правило 204. Последовательность конечна: hello → end → закрытие → одно переподключение → 204 → браузер останавливается |
| 3 (HIGH) | «note не теряется» не переживает reconnect |
ИСПОЛНЕНО. Обещание не расширено (буфер обещать нечем) — названа ОБЯЗАННОСТЬ клиента: после КАЖДОГО переподключения дельта-чтение /notes?after_version=<последняя применённая ревизия> до того, как доверять списку |
| 4 (HIGH) | Revision противоречит себе; BookDetail.revision vs book.revision |
ИСПОЛНЕНО. Одна модель: одно число на КОНВЕРТ; у многостраничного обхода за всё чтение отвечает НАИМЕНЬШАЯ увиденная ревизия — и как гард свежести, и как водяной знак следующей дельты (лишнее перечитывание бесплатно, пропуск строки — нет); равенство BookDetail.revision и BookDetail.book.revision заявлено явно |
| 5 (MED) | Пустые allOf-члены → необитаемые типы |
ИСПОЛНЕНО и ПРОВЕРЕНО ГЕНЕРАТОРОМ. EventEnd и EventResyncRequired теперь allOf из одного члена с описанием на самой схеме; в генерённых типах оба — components["schemas"]["EventBase"], Record<string, never> в наших схемах не осталось (два вхождения в файле — служебные webhooks/$defs генератора). Записано и то, что раньше подразумевалось: эти два кадра неразличимы по форме, диспетчеризация только по имени |
| 6 (MED) | Что делает resume по каждому статусу; «новый прогон» легален при ЛЮБОМ paused |
ИСПОЛНЕНО, таблица ИСПРАВЛЕНА мной. В пришедшей редакции таблица клала КОРНЕВОЙ код в слот причины (cause.code: run_not_resumable) и оставляла три строки вовсе без кода. Исправлено: корневой код run_not_resumable во всех 409, cause.code только там, где причина есть (bank_decisions_incomplete, ceiling_reached), а «это вообще не остановленный прогон» идёт БЕЗ cause. Расширен и сам ceiling_reached: он покрывает и купленные главы, и кредит за ними — иначе строка credit_exhausted таблицы указывала на код, который её не описывает. Значение enum не заводилось (D39.132 п.2а не двигается) |
| 7 (MED) | Ответ НЕ problem+json — потерянный пункт Б-1 рек. 4 |
ИСПОЛНЕНО. Записан пол под моделью ошибок: из такого ответа клиент не показывает ни байта и рисует свою нейтральную фразу — по классу статуса, если он есть, иначе «сервис недоступен» |
| 8 (MED, 10 позиций) | Пачка недоопределённостей | ИСПОЛНЕНЫ все десять: title: null → 400 с объяснением сужения RFC 7386 · разводка X-TM-Client required против освобождения bearer записана в самом параметре · список отказов интейка помечен неисчерпывающим (авторитет — code) · правило резолюции Location отдельным разделом шапки · тождество Idempotency-Key на multipart по объявленным частям + 408 не считается состоявшейся попыткой · min_chapters при max_chapters: 0 — не диапазон, максимум проверяется первым · порядок пяти носителей «нет кредита» · «carried forward marked as unverified» СНЯТО как обещание без носителя, заменено на наблюдаемое (BankPage.signed против total) · 409 у createExport объяснён как ключевой, а не книжный · неизвестный term_id отклоняет весь батч решений с указателем на позицию |
| 9 (LOW) | parser_unavailable по эффекту; вычистить внутренние ссылки из описаний |
ИСПОЛНЕНО, ДОДЕЛАНО мной. Переименование в processing_failed на месте; ⚠ проводное имя теперь расходится с внутренним словарём платформы (parse.go:66) — проекция это вход P7, записано в компаньон. Ссылки: пришедшая редакция сняла три (research/28 §2 Б-15, §8 п.4, §2 Б-4, «companion K-6»), но пропустила две — цитату D-нот в info.summary и companion §2.8 у BankTerm; обе сняты мной. Греп по спеке на `research/28 |
| 10 (отчёт) | Пере-ран чисел ПОСЛЕ последней правки + хвосты | ИСПОЛНЕНО, ДОПРАВЛЕНО мной. Пришедшая редакция закрыла: ErrorCode 16 (сверено awk-счётом), якорь mining.go:81,243 (сверено — цитата «for EACH term either promote … OR decline» стоит на :243), «строка 21» → 18, «12 из 20 не отвечает» (сверено: contractRoutes регистрирует 8 путей из 20 операций), строки записки-плана Б-23/К-10/В-6, README §6а «ужаты на месте», скрипт мерки прозы приложен. Осталось и починено мной: §0 противоречил сам себе (заголовок «проза выросла быстрее», а следующим предложением «выросла ровно пропорционально») · §0 держал старую долю 36 % против новой мерки в §4.4 · заголовки §5.1/§5.2 говорили «26 позиций»/«12 позиций» против фактических 46/11 в самих таблицах · ярлык §5.5 «ИСПОЛНЕНО ИНАЧЕ» не был заменён на «ОТСТУПЛЕНИЕ, аргумент» · все числа пере-раны после ПОСЛЕДНЕЙ правки, а не после предпоследней |
9а. Проверка дофикса (кросс-модельная, по слову владельца)
Одна линза другой модели, узкий мандат: закрыт ли каждый ФБ · нет ли регрессий · правда ли то, что
дофикс УТВЕРЖДАЕТ о коде. Вердикт: ФБ-1..9 закрыты (ФБ-8 — 10/10 подпунктов), регрессий в каноне
не найдено, все кодовые утверждения подтверждены, кроме одного якоря. Неприкосновенное цело —
проверено грепом на месте: прогонного потока нет, Note несёт code, content_refused в обоих
enum, page_size_default единственный носитель размера страницы. Нормативных правил заленденный
канон → нынешний: 44 → 45, потеряно ноль.
| Находка | Диспозиция |
|---|---|
Битый якорь в компаньоне: Resume приписана runs/runs.go, а живёт в runs/reconcile.go:906 (ветка case "paused" — :919, дневной потолок — :931). Пакет верный, файл нет |
ИСПРАВЛЕНО. Моя ошибка, не чужая: строку писал я. Прочие ссылки на runs/runs.go:227-231 (readyToTranslate) проверены и ВЕРНЫ — там функция действительно живёт |
LOW: пример кадра давал id: 1841 и "revision":1841 — одно число на два счётчика, которые батч специально разводил; холодный потребитель мог склеить их обратно |
ИСПРАВЛЕНО: в примере теперь id: 57 при revision: 1841, плюс строка «эти два числа намеренно непохожи и одно не подменяет другое» |
LOW: код позиции unknown введён в submitBankDecisions, но отсутствует в иллюстративном списке ErrorItem.code — потребитель его там не найдёт |
ИСПРАВЛЕНО: добавлен в список |
Наблюдение: версия осталась 0.3.0, хотя после лендинга переименовано значение enum (parser_unavailable → processing_failed) — «0.3.0» теперь обозначает два разных содержания |
ПРИНЯТО КАК ЕСТЬ, называю оркестратору. Дофикс заказан САМОЙ нотой приёмки как часть того же раунда (D39.142 п.3), потребителей у 0.3.0 нет ни одного (зеркало фронта на 0.2.3, платформа на 0.2.3), и бампать минор внутри приёмочного раунда значило бы плодить версию, которую никто не видел. Если оркестратор читает иначе — бамп стоит одной строки |
| Требование «пере-ран чисел ПОСЛЕ последней правки» на момент проверки снова не сходилось (дерево правилось при ней) | ЗАКРЫТО финальным прогоном — числа §0 сняты после САМОЙ последней правки и сверены; проверка это и предсказывала |
Ратификации приёмки, которые дофикс НЕ двигал (проверено грепом по канону): прогонный поток снят,
канал один — книжный · Note.code вместо message · content_refused остаётся значением
RejectReason · единый page_size_default вместо пер-коллекционных.
Что дофикс добавил в долг P7, сверх читающей поверхности: POST /runs/{runId}/resume — построенный
путь, и 0.3.0 меняет его ответ на паузе по потолку с 202-без-изменений на 409
cause.code: ceiling_reached. Записано строкой в таблицу компаньона §3, потому что это единственное
место дофикса, где правится УЖЕ РАБОТАЮЩИЙ код, а не проекция, которой ещё нет.