40 KiB
Архив журнала зоны «Платформа» — эра пака P7 (16–20.08.2026)
⚠ СРЕЗ, выселен оркестратором №18 при лендинге P7 (20.08, D39.153) — по образцу срезов
platform-PROGRESS-P0-P3.mdи-P4-P6.md. Эра ЗАКРЫТА: пак исполнен пятью актами, принят с фикс-листом и заленден (9b23e8c), сессия закрыта владельцем.Не читать при онбординге. Живое состояние зоны — шапка
platform-PROGRESS.md; статус каждого дефекта —DEFECT_REGISTER.md(источник истины); правила, пережившие пак, —STACK_DECISIONS.md§33–36; фикс-лист приёмки и то, что ушло в единый бэклог, — пинг оркестратора №18 там же в живом журнале.
Сессия P7 (16–17.08): читающая поверхность контракта 0.3.0
Промт — PLATFORM_P7_SESSION_PROMPT.md. Ниже: записка-план по §4 (каждый пункт с диспозицией),
команды, живые пробы, находки, obstacle.
Записка-план: пункт → исполнено / отказ → улика
| § | Пункт | Статус | Улика |
|---|---|---|---|
| 0 | Рантбук деплоя живым migrate |
исполнен | БД проекта отведена на v14 при бинаре v15 → tmctl status --json = exit 13, токен schema_mismatch found=14 expected=15; tmctl migrate → бэкап + v14→v15; тот же status = exit 0. Мёртвая цитата вычищена (deploy/README.md, П-1 бэклога), пример в tmplatformctl/runs.go переписан на ВЕРСИОНИРОВАННЫЙ путь |
| 0 | tmplatformctl books --migratable → tmctl migrate end-to-end |
исполнен | 5 книг стенда, все MIGRATE=yes, цикл из деплой-ноты отработал |
| 1а | Читать BookIntake.title |
исполнен | books.Intake.Title; пустая строка = «назови по файлу» (books.title()), непустая не перезаписывается разбором |
| 1б | Проецировать reject_reason |
исполнен | httpapi.contractRejectReason; parser_unavailable → processing_failed (ФБ-9: имя значения — то, на что клиент вешает фразу) |
| 2 | PD-104: грант → 0 + тест | исполнен | config.go дефолт 0; пин config.TestSignupGrantIsParsedNotGuessed пинит ИМЕННО ноль; протухшие «$5» вычищены из PLATFORM_DIRECTION.md §2, BACKLOG.md П-7, регистра |
| 3 | Модель ошибок по карте А-2 | исполнен | httpapi/problem.go: 16 корневых кодов, статус ВЫВОДИТСЯ из кода (пара «код+статус» непредставима расходящейся), cause открытый, errors[], request_id из reqid; WWW-Authenticate на 401; две причины 403 разведены в cause при одном корневом forbidden; класс 2 — один код без вариации |
| 4 | listChapters · listUnits (пер-главно, 410) · listNotes · listBankTerms · submitBankDecisions · дельты · SSE · structure_version · gzip · ETag/304 |
исполнен | httpapi/{reading,stream,conditional}.go, pgstore/{readmodel,events}.go; ручки «вся книга юнитами» НЕТ; Unit.notes массивом; агрегаты банка на ПЕРВОЙ странице; дельты ВКЛЮЧИТЕЛЬНЫЕ; version_too_old и cursor_invalid — причинами |
| 4 | Ловушка heading |
исполнен | поле не декодируется ВОВСЕ (ingest.ManifestChapter), колонка chapters.heading удалена миграцией, проекция отдаёт null; пин readmodel.TestTheManifestsRenderedHeadingHasNowhereToLand |
| 4 | Демонтаж построенного гейта полноты банка | исполнен | runs/reconcile.go: awaiting_bank продолжается при ЛЮБОМ состоянии решений; пин TestResumeLiftsABankStopWithTheDecisionsAsTheyStand. Граница паузы движка сверена чтением (pipeline/mining.go: авто-продолжение с неподписанным банком — дефолт) |
| 4 | Пофазность у главы НЕ строить | соблюдён | у главы один счётчик units_done; пин «фазовый счётчик не доехал до строки главы» |
| 4 | Note.code из ◆-карты |
исполнен с ОДНИМ отступлением | httpapi/notes.go; отступление — незнакомая причина проецируется как unspecified (в карте колонка кода пуста, а поле обязательно). Заведена строка PD-246, ждёт ратификации вместе с фразами |
| 5 | Progress с книги на Run, одна полоса в ГЛАВАХ |
исполнен | pgstore.Progress{Done,Total,ETASeconds}, знаменатель — ceiling_chapters; сегмент выбирается verify_bank && !bank_released |
| 5 | Book: +revision, +chapters_done, −progress, −genre |
исполнен | genre снят и с ПРОВОДА, и из формы интейка, и из колонки: платформа его больше не знает, значение шаблона оператора остаётся его |
| 5 | Run: +book_id, +failure_reason, verify_bank→stop_for_signing |
исполнен | словарь из трёх значений мапится с полосы 10–19 и веток outcome() (runs.failureReason) |
| 5 | Usage: halt_reason со своим словарём |
исполнен | httpapi.contractHaltReason; прогонная причина больше не может доехать до аккаунтного поля |
| 5 | GET /capabilities |
исполнен | все шесть полей; пары — из конфигурации (TM_PLATFORM_LANGUAGE_PAIRS), не список в Go |
| 5 | limit подрезать, а не отвергать |
исполнен | pgstore.clampPage; отвергается только значение, которое не является размером страницы |
| 5 | Трейлинг-части интейка → 400 | исполнен, с находкой | сначала книга ОСТАВАЛАСЬ при 400 (живая проба); теперь часть после файла ПРОВАЛИВАЕТ чтение, и интейк откатывает загрузку своим же путём |
| 5 | Location на 201 |
исполнен | /v0/books/<id> |
| 5 | Idempotency-Key на createBook и startRun |
исполнен | таблица 00017, повтор отдаёт ИСХОДНЫЙ ответ, другой запрос — 409 key_reused, гонка — key_in_flight + Retry-After, >255 → 400 |
| 5 | blocked {code, book_id} на run-options и 409 |
исполнен | pgstore.CreditHeldBy + runs.CreditHeldError |
| 5 | Отказ по неподдерживаемой паре на интейке | исполнен | books.ErrUnsupportedPair → errors[].code: unsupported_pair; пустой список пар = деплой не объявляет ничего и пару не проверяет |
| 5 | finalizing вон из словарей |
исполнен | Go-свитчи и CHECK-констрейнты обеих таблиц |
| 6 | PD-172/173/174/180/199 привести · PD-201 · PD-241 | исполнен | все закрыты либо сужены с уликой; PD-241 — перевёрнутый порядок в outcome() с пином |
| 6 | Кандидаты PD-219/217/162 | отсуждены | PD-217 — половина ЗАКРЫТА (paused больше не блокирует апгрейд движка); PD-219 и PD-162 — не брались, с названной причиной |
| 6 | Находки §9 research/28 | отсуждены | три взяты строками (PD-249/250/251/252), две сведены к существующим (PD-203, PD-162/244) |
| 6 | Инструменты: sqlc / oapi-codegen | отказ с доводом | оба НЕ взяты; довод — PLATFORM_DIRECTION.md §3, и он опирается на замер этого пака, а не на вкус |
Команды (каждое число отчёта — этой командой)
# батарея, оба гейта, -race
export TM_PLATFORM_TEST_DSN='postgres://postgres@/postgres?host=/tmp&port=55433&sslmode=disable'
export TM_PLATFORM_TEST_ENGINE_BIN=<путь к tmctl> TM_PLATFORM_TEST_BOOK_TEMPLATE=<шаблон>
make check # EXIT=0, 18 пакетов, скипов 0, линтер 0 issues
grep -rh '^func Test' --include='*_test.go' . | wc -l # 458 (HEAD: 416)
git diff --stat platform | tail -1 # 46 файлов, +2329 −646
python3 docs/scripts/counts.py # регистр: 256 строк, 63 открытых
⚠ Стенд Postgres на этой машине пришлось поднять с нуля (его не было): рецепт STACK_DECISIONS.md
§«Postgres без root», micromamba + postgresql=18.4 в ~/.local/pgsql, порт 55433 — отработал
дословно.
Живые пробы на дев-стенде (демон + сид + настоящий tmctl)
Сид P6 отработал целиком: дев-вход → грант → загрузка через ЖИВОЙ POST /v0/books → разбор →
not_started, 3 главы. Дальше — курлом по новым ручкам:
GET /capabilities→ все шесть полей,contract_version: 0.3.0, парыzh>ru available+ja>ru unavailable;GET /books→revision · reject_reason: null · structure_version: 1 · chapters_done: 0 · character_count: 987, никакогоprogressи никакогоgenre;GET /books/{id}/chapters→ три главы,heading: nullу каждой;GET /books/{id}/chapters/{ch}/units→ пара с ИСХОДНЫМ ТЕКСТОМ (каналtmctl export --pairsработает),target: "",state: pending,notes: [];GET /books/{id}/notes,/bank→ пустые с агрегатами иstructure_version;- SSE на книге в покое →
hello id=4→end id=4→ закрытие; реконнект сLast-Event-ID: 6→ 204; ETag→304наIf-None-Match;Accept-Encoding: gzip→Content-Encoding: gzip+Vary; тот же заголовок на/events→ не сжато;- ошибки: 404 →
code: not_found+request_id; 401 →WWW-Authenticate: Bearer realm="textmachine"; POST безX-TM-Client→ 403forbidden·cause.code: client_header_missing; - интейк: пара
ja→ru→ 400unsupported_pairна обоих полях; часть после файла → 400missing_or_lateи книга не остаётся (проверено счётом библиотеки до/после); Idempotency-Key: тот же ключ дважды → один и тот жеLocation; тот же ключ с другимtitle→ 409idempotency_conflict·cause.code: key_reused.
Что нашли ПРОБЫ, чего не нашли тесты
Часть формы после файла оставляла книгу. Отказ 400 приезжал (правило соблюдалось), но
intake.Accept к тому моменту уже создал строку и каталог — пользователь получал ошибку И книгу в
библиотеке. Починено не новым кодом отката, а ПРОВАЛОМ чтения: часть после файла возвращает ошибку
из ридера, и интейк отменяет загрузку тем же путём, которым отменяет оборванную. Пин
httpapi.TestAPartAfterTheFileIsRefusedAndTheBookIsNotKept, пере-проверено живьём.
Адверсариальное селф-ревью финалом: три ревьюера, таблица находок
Механизм по §5 промта: три независимых субагента по ФИНАЛЬНОМУ диффу, author≠reviewer, два из трёх — другой модели (кросс-семейная линза, а не вторая своя). Ноль находок не случился — и это хорошо: две HIGH нашли ОБА контрактных ревьюера независимо, что и есть подтверждение.
| # | Линза | Находка | Вердикт | Что сделано |
|---|---|---|---|---|
| Р-1 | контракт (кросс-модель) · деньги/шов | Кадр note уносил движковую причину (glossary_miss, hard_refusal) и не нёс обязательных severity/code; unit_id: null вместо отсутствия |
ПОДТВЕРЖДЕНА, HIGH | Карта переехала в internal/ingest/notes.go — один дом на оба потребителя; кадр строится через неё; unit_id опускается. Пин TestTheNoteFrameCarriesTheContractsNoteAndNotTheEnginesReason |
| Р-2 | контракт (кросс-модель) | Кадр status уносил внутренние словари (daily_ceiling, parser_unavailable): та же величина читалась null на карточке и внутренним словом в потоке |
ПОДТВЕРЖДЕНА, HIGH | Переводы переехали в internal/ingest/vocabulary.go, кадр строит emitStatus; при ошибке чтения кадр НЕ шлётся (вместо status: null вне схемы). Пин TestTheStatusFrameCarriesTheContractsVocabularyOrNothing |
| Р-3 | деньги/шов | DOWN-путь миграции неисполним при первой же строке банка (воспроизведено живым PG): три словаря переписывались без перевода данных, а goose гоняет миграцию одной транзакцией | ПОДТВЕРЖДЕНА, HIGH | Порядок drop → перевод → add в ОБЕ стороны (UP тоже: он должен переживать пере-накат после отката). Пин TestARollbackSurvivesTheDataTheNewVocabulariesWrote — откат до документированного пола (версия 5) на базе С данными |
| Р-4 | деньги/шов | Четыре писателя меняли статус без кадра (RestartRun, PauseRun, FinishUnspawnedStop, ReleaseBankStop); двое из них ставят finished_at, то есть книга «в покое» — и следующий реконнект получает 204 «не переподключайся» при устаревшем статусе у клиента |
ПОДТВЕРЖДЕНА, HIGH | Кадр у всех трёх писателей статуса; четвёртый (ReleaseBankStop) перестал быть отдельным писателем — см. Р-5 |
| Р-5 | деньги/шов | ReleaseBankStop коммитился ДО reopen: резюм, который ничего не сделал (расчёт в полёте, бюджет исчерпан), всё равно переключал полосу с N/N на 0/N |
ПОДТВЕРЖДЕНА, MED | Снятие стопа перенесено ПОСЛЕ успешного reopen |
| Р-6 | деньги/шов | Refresh тратил бюджет прохода ПЕРЕД settle: на большой книге два полных ре-чанка съедали проход, расчёт уходил в «отложено», холд висел свип |
ПОДТВЕРЖДЕНА, MED | Деньги первыми: settle → затем refreshReadModel на СВОЁМ ограниченном контексте, ошибка громкая |
| Р-7 | деньги/шов | failureReason игнорировал маркер, который ему передавали: OOM и kill по стоп-таймауту приезжали interrupted («повтори») |
ПОДТВЕРЖДЕНА, MED | Читает m.Result; пин TestWhyARunFailedDecidesWhetherARetryIsWorthOffering (7 веток) |
| Р-8 | контракт (кросс-модель) | Квитанции Run несли нулевую полосу (202 на старт/стоп/резюм) со свежей ревизией — клиентский гард пропускал её и полоса откатывалась к 0/0 |
ПОДТВЕРЖДЕНА, MED | StartRun/RequestStop/ReadRun считают ту же полосу, что карточка |
| Р-9 | контракт (кросс-модель) | Полоса ВТОРОГО прогона считала главы всей книги: дробь стартовала выше нуля и могла превысить единицу | ПОДТВЕРЖДЕНА, MED | Колонка runs.chapters_before — база сегмента, снимается в том же стейтменте, что создаёт прогон; least(…, ceiling_chapters) закрывает второй край. Пин TestASecondRunsBarStartsAtZeroOverAHalfFinishedBook |
| Р-10 | контракт (кросс-модель) | Last-Event-ID: 0 после подрезки буфера молча пропускал дыру — а 0 это легальный id, который сервер сам выдаёт |
ПОДТВЕРЖДЕНА, MED | Условие last+1 < Oldest вместо last > 0 && last < Oldest |
| Р-11 | контракт (кросс-модель) · скоуп | Склейка кадров chapter шла по имени события, теряя счётчик соседней главы внутри одного соединения |
ПОДТВЕРЖДЕНА, MED | Склейка по (событие, id сущности) |
| Р-12 | контракт (кросс-модель) | Порядок банка был (src, id) — то есть по ХЕШУ, а канон объявляет «по поверхности, затем по окну» |
ПОДТВЕРЖДЕНА, MED | Порядок и кейсет приведены к канону (src, since, until, id) |
| Р-13 | контракт (кросс-модель) | limit всё ещё отвергался снизу (0, отрицательный, нечисло), тогда как канон освобождает параметр от отказа |
ПОДТВЕРЖДЕНА, MED | Никогда не отвергается: сверху подрезка, всё прочее — дефолт деплоя. Пин переписан |
| Р-14 | контракт (кросс-модель) | Неверная пара языков и слишком длинное поле отказывали без errors[] |
ПОДТВЕРЖДЕНА, MED | ErrMalformedLanguage и too_long с указателем на поле |
| Р-15 | контракт (кросс-модель) | SweepIdempotency не имел вызывающего: ключи не забывались никогда |
ПОДТВЕРЖДЕНА, LOW | Подключён в свип демона, окно 25 часов |
| Р-16 | контракт (кросс-модель) | Курсор замечаний нёс движковое слово волны в открытом виде | ПОДТВЕРЖДЕНА, LOW | Пакуется порядковым номером |
| Р-17 | скоуп (кросс-модель) | acceptsGzip игнорировал *; pump спал такт даже после полной пачки; unknownTermPointer парсил текст ошибки; алиас itoa |
ПОДТВЕРЖДЕНЫ, LOW | Все четыре починены (типизированная UnknownTermError вместо парсинга строки) |
| Р-18 | скоуп (кросс-модель) | sqlc был привязан к этому паку ратификацией, а пак от него отказался — это отступление, а не решение зоны |
СПРАВЕДЛИВО | Отказ с доводом записан в PLATFORM_DIRECTION.md §3 и вынесен ВОПРОСОМ оркестратору (ниже) |
| Р-19 | скоуп (кросс-модель) | unspecified — изобретённый код там, где промт требовал пинг |
ЧАСТИЧНО: разбор с владельцем 17.08 показал, что находка верна по форме и неверна по существу | Значение отдано ветке, которую канон УЖЕ ратифицировал («нейтральная фраза для незнакомого кода»), а не заводит новое правило; настоящий предмет — рукописная копия чужого словаря без гейта, PD-246 пере-сформулирован, движку — пинг про публикацию причин данными |
| Р-20 | контракт (кросс-модель) | 410 отвечает и на главу, которой никогда не было: различение 410/404 без надгробий неисполнимо |
ПРИНЯТО КАК ЕСТЬ | Лечение у обоих одно (перечитать дерево); строка PD-253 |
| Р-21 | скоуп (кросс-модель) | Масса комментариев выросла против нормы зоны (миграция на 250 строк — половина проза) | ЧАСТИЧНО ПРИНЯТО | Подрезано самое тяжёлое; остальное — предмет пинга, а не тихой правки |
| Р-22 | скоуп (кросс-модель) | Два писателя ошибок (WriteProblem + WriteStatusProblem/codeForStatus), три одинаковых структуры пары |
НЕ БРАЛОСЬ | Второй писатель обслуживает НЕконтрактную поверхность (/auth, /readyz), где кода версии нет; сведение в один требует ратификации формы для 405/429 — вопрос оркестратору |
Что ревьюеры подтвердили как ВЕРНОЕ (важно не меньше находок): ни одной денежной величины на
проводе ни в каком виде · порядок блокировок не тронут, emitFrame не берёт НОВЫХ локов ни в одной
из 14 точек · кадр не может быть выпущен для откаченного состояния · демонтаж гейта банка
соответствует таблице канона дословно · словари шва (шесть значений банка, полоса отказов 10–19,
все семь типов событий) сверены по коду движка и полны · finalizing снят безопасно · оба гейта
миграций пере-считаны и совпадают.
Живые пробы ПОСЛЕ фикс-раунда (стенд пере-создан)
Стенд пришлось пере-создать, и это отдельная улика: миграция 00016 правилась ПОСЛЕ того, как была
применена, а goose ключуется номером — отредактированный файл не перезапускается никогда. На стенде
лечится сносом базы; в проде именно от этого стоит гейт migrations.sha256 (обе строки пере-считаны).
limit=0 | -3 | abc | 100000→ 200 во всех четырёх (канон освобождает параметр от отказа);- квитанция старта:
status: translating,progress {done:0, total:2},book_id,failure_reason: null— полоса едет с квитанцией, а не только с карточкой; - поток при ЖИВОМ прогоне:
hello id=5→status id=7сfailure_reason: "service_error"иpaused_reason: null— кадр несёт КОНТРАКТНЫЙ словарь (внутреннего слова в кадре нет); - карточка после отказа: книга
failed, прогонfailed+service_error(на стенде нет ключей провайдера — движок отказывает конфигурацией, и это ровно тот класс, который «повтор не лечит»); resumeупавшего прогона →run_not_resumableбезcause(канон: это не остановленный прогон);- неверный код языка →
invalid_request+errors[]на оба поля; /usage→halt_reason: null(свой словарь, не прогонный).
Obstacle: что НЕ удалось и что не проверено
- Отложено в P8 явно (заказ промта):
updateBook·deleteBook·getRun·createExport/getExport· эскроу (П-18). Note.codeдля незнакомой причины — отступление, не решение:unspecified(PD-246).- Ни одного живого ЗАМЕЧАНИЯ и ни одного термина банка через настоящий прогон не прошло —
прогон стоит денег, а сессия $0. Проекции замечаний и банка проверены тестами на живом PG и
ручными строками
unit_resolutions, но не сквозным прогоном движка. - SSE с ЖИВЫМ прогоном не гонялся по той же причине: проверены книга в покое, реконнект, 204,
склейка и
resync_required— тестами и на стенде, но не под настоящим потоком кадров. - Стоимость материализации не замерена на большой книге (PD-248): два полных ре-чанка на границу работы; на демо-книге в 3 главы это миллисекунды, на 2283 главах — секунды CPU.
structure_versionна РЕАЛЬНОЙ пере-нарезке проверен тестом (смена ключа манифеста), но не настоящей сменой чанкера движка.- Опрос потока — 2 запроса/с на соединение (PD-247):
LISTEN/NOTIFYне строился. - Кадр
noteживьём не наблюдался: он рождается только на помеченной единице настоящего прогона. Проверен тестом на живом PG через настоящийRunSink.Apply(полный путь материализатора), но не в потоке под браузером. - Полоса под настоящей работой не наблюдалась:
progress {0,2}— это старт; движения счётчика на стенде без ключей провайдера не бывает.
Вопросы оркестратору (канал вопросов промта, НЕ тихая интерпретация)
-
Словарь флаг-причин — рукописная копия чужой зоны без гейта (PD-246; формулировка исправлена после разбора с владельцем 17.08 — прежняя, «в карте контракта дыра», была неверной). Проверено чтением движка: локализованных фраз там НЕТ вовсе —
refusal.goиstatus.goнесут машинные классы, русских пользовательских строк в продовом коде движка ноль. То есть движок и контракт построены на одном принципе, и переделывать под «гугловский словарик в движке» нечего: фраза всегда на языке ИНТЕРФЕЙСА, которого не знает ни движок, ни платформа, а движок ОДИН на все пары — русская строка в нём была бы таргет-спецификой в общем слое (тот же инвариант, что снялrubyс провода). Проводу окно закрыто: причина вне карты едет кодомunspecified, а канон уже предписывает клиенту нейтральную фразу для незнакомого кода — значение отдано РАТИФИЦИРОВАННОЙ ветке, а не изобретено правило. На подпись остаются две строки продуктового текста: фраза дляunspecifiedи граница ступенейattention/glance(сегодня — догадка платформы, колонка «Ступень» в приложении А пуста у всех 15 строк). Это не блокер. Правка контракта — предлагаю, зона не моя (готовые формулировки, вставить как есть). Обязательный минимум — КОМПАНЬОН, приложение А, последняя строка (README.md:899). Пустая клетка кода в ней и есть то, что заставило платформу выбирать значение самой:| незнакомая причина | 8 (по умолчанию) |unspecified— стабильный плейсхолдер деплоя: не значение словаря, а ОБЯЗАННОСТЬ сервера ответить одним и тем же кодом на причину, которой его сборка карты не знает (носитель — PD-246 платформы) | ⬜ нейтральная, НЕ «ошибка» | ⬜ |Желательное — ОДНА фраза в канон, к описанию
Note.code(openapi.yaml:1693-1700), после абзаца про нейтральную фразу:A deployment that meets a reason its build of the map cannot name answers the stable placeholder
unspecifiedrather than the producer's own word, so the branch above is exercised by a real value in ordinary operation and not only in theory.Форма выбрана намеренно: это ОБЯЗАННОСТЬ СЕРВЕРА, а не запись словаря — канон отказывается перечислять
Note.code(«freezing a list in the schema before the words exist would make it a second copy»), и перечисление начинать нельзя. Ценность ровно одна и она процессная: без строки в нормативном документе значение живёт только в Go платформы, и следующая сессия изобретёт своё. Бампа версии не требует, и вот почему точно:Note.codeобъявленtype: string, не enum, — ни один генерённый тип не сужается и не расширяется; а сама фраза описывает поведение, которое ЕДИНСТВЕННЫЙ потребитель 0.3.0 (этот пак) уже реализует, то есть сломать клиента, сгенерированного по 0.3.0, она не может. ⚠ Прецедент D39.144 (микро-правка канона без бампа) здесь опорой НЕ служит: там довод был «спека ещё никем не потреблена», и он больше не верен. Цена, которую надо назвать: описания компилируются в исходники клиента, значит фраза доедет доschema.ts— то есть зеркало фронта и регенерация типов при разморозке. И пара «код нормативен, фраза ⬜» должна остаться видимой, иначе следующий читатель решит, что карта опять сломана.⚠ ПИНГ ОРКЕСТРАТОРУ — строка в бэклог ДВИЖКА (сама зона в чужой бэклог не пишет): публиковать список флаг-причин ДАННЫМИ — артефакт рядом с манифестом либо
tmctl flag-reasons --json($0, таблицаflagReasonSeverityуже существует). Тогда тест платформы читает его и ПАДАЕТ, если в карте нет строки, и класс «словарь разъехался тихо» закрывается насовсем. Со стороны платформы гейт невозможен: импортbackend/internalзапрещён ревью-гардом модулей (D39.85). Обратный вариант — чтобы движок эмитил сразу контрактный код — отвергнут: это зеркальная утечка, продуктовое слово поехало бы в движок, который о контракте знать не должен. -
sqlc— ОТЛОЖЕН СЛОВОМ ВЛАДЕЛЬЦА 17.08 («отложим»), строка PD-44 дополнена разбором. Фиксирую честно, включая довод против себя: пак был обязан его взять (D39.132 п.2б), и sqlc поймал бы ровно тот класс, который дважды укусил этот пак в рантайме (stop_for_signing,chapters_before) — батарея его поймала, но на цикл позже компилятора. Что мешает в read-модели: запросы собраны склейкой общих фрагментов (одна проекция книги на четыре пути), а sqlc требует литерального SQL на запрос, и страница с её ревизией — транзакция, которую он не моделирует. Предложение зоны: взять в P8 на отложенных однооператорных ручках, по итогам решить про read-модель. -
Поверхность входа: РЕШЕНО контрактной сессией 17.08 (релей владельца), механизм исполнен. Вердикт — вариант A уточнённый:
/auth/*отвечает тем же конвертом и без машинногоcodeвовсе; клиент показывает одну нейтральную фразу, а единственный пользовательски осмысленный случай —429— уже машинный без всякого словаря, потому что лечение едет вRetry-After(платформа его отдаёт:login.go:173,233,dev.go:138). Отвергнуты: расширениеErrorCode(значения, недостижимые на поверхности, которую этот словарь описывает, ломают инвариант «код называет свой статус») и собственный словарь в компаньоне (сделал бы НЕнормативный документ нормативным с чёрного хода — канон в шапке объявляет нормативным для ФОРМЫ только себя). Исполнено в этом дереве:codeForStatusи обратная функция удалены — писатель входа берёт статус в точке вызова и кода не эмитит, поэтому инвертировать больше нечего. Пинhttpapi.TestTheSignInSurfaceAnswersTheSameEnvelopeWithoutAVersionedCode(шесть статусов: конверт иrequest_idесть,codeнет ни у одного). PD-254 закрыт. Остаётся зоне контракта (релей, не пере-проектирование): две фразы — в канон рядом с существующей про вход (:36-37): поверхность механики сессии отвечает тем же конвертом безcode, клиент диспетчеризует по статусу, особый случай429сRetry-After, серверный текст не показывает никогда; и в компаньон §2.14 — восстановить срезанное правило и таблицу шести статусов. Триггер пересмотра назван явно: второй пользовательски осмысленный случай на входе → своя маленькая нормативная спека на/auth/*, а не рост таблицы в спутнике. -
⚠ НАХОДКА КОНТРАКТНОЙ СЕССИИ, которая дороже самого вопроса — передать оркестратору отдельно. Правило про вход было РАТИФИЦИРОВАНО и потеряно молча: в компаньоне 0.2.3 §2.14 стояло «Отказ входа — problem+json, как везде; различать причины отказа клиент не может по замыслу», а в сегодняшней редакции осталась только первая половина — вторую срезал коммит лендинга батча 0.3.0 (
8d82096, провереноgit log -S). То есть P7 не переоткрывал спор, а восстанавливал решение, которое кто-то выкинул. Класс потери структурно непроверяем существующей приёмкой: сверка батча шла по мультимножеству модальных глаголов (MUST/SHOULD/MAY), а в этой фразе модального глагола нет. Стоит отдельного разбора приёмки — не как дефект контракта, а как дыра в МЕТОДЕ проверки резки прозы. -
Плотность комментариев (PD-255): пак вышел за норму «одна-две строки почему». Часть прозы несущая, часть — эссе; прошу решение владельца о норме, а не тихую подрезку в следующем паке.