textmachine/platform/docs/archive/platform-PROGRESS-P7.md

66 KiB
Raw Permalink Blame History

Архив журнала зоны «Платформа» — эра пака P7 (1620.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 §3336; фикс-лист приёмки и то, что ушло в единый бэклог, — пинг оркестратора №18 там же в живом журнале.

Сессия P7 (1617.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 --migratabletmctl migrate end-to-end исполнен 5 книг стенда, все MIGRATE=yes, цикл из деплой-ноты отработал
1а Читать BookIntake.title исполнен books.Intake.Title; пустая строка = «назови по файлу» (books.title()), непустая не перезаписывается разбором
1б Проецировать reject_reason исполнен httpapi.contractRejectReason; parser_unavailableprocessing_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_bankstop_for_signing исполнен словарь из трёх значений мапится с полосы 1019 и веток 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.ErrUnsupportedPairerrors[].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 /booksrevision · 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=4end id=4 → закрытие; реконнект с Last-Event-ID: 6204;
  • ETag304 на If-None-Match; Accept-Encoding: gzipContent-Encoding: gzip + Vary; тот же заголовок на /eventsне сжато;
  • ошибки: 404 → code: not_found + request_id; 401 → WWW-Authenticate: Bearer realm="textmachine"; POST без X-TM-Client → 403 forbidden · cause.code: client_header_missing;
  • интейк: пара ja→ru → 400 unsupported_pair на обоих полях; часть после файла → 400 missing_or_late и книга не остаётся (проверено счётом библиотеки до/после);
  • Idempotency-Key: тот же ключ дважды → один и тот же Location; тот же ключ с другим title → 409 idempotency_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 точек · кадр не может быть выпущен для откаченного состояния · демонтаж гейта банка соответствует таблице канона дословно · словари шва (шесть значений банка, полоса отказов 1019, все семь типов событий) сверены по коду движка и полны · finalizing снят безопасно · оба гейта миграций пере-считаны и совпадают.

Живые пробы ПОСЛЕ фикс-раунда (стенд пере-создан)

Стенд пришлось пере-создать, и это отдельная улика: миграция 00016 правилась ПОСЛЕ того, как была применена, а goose ключуется номером — отредактированный файл не перезапускается никогда. На стенде лечится сносом базы; в проде именно от этого стоит гейт migrations.sha256 (обе строки пере-считаны).

  • limit=0 | -3 | abc | 100000200 во всех четырёх (канон освобождает параметр от отказа);
  • квитанция старта: status: translating, progress {done:0, total:2}, book_id, failure_reason: null — полоса едет с квитанцией, а не только с карточкой;
  • поток при ЖИВОМ прогоне: hello id=5status id=7 с failure_reason: "service_error" и paused_reason: null — кадр несёт КОНТРАКТНЫЙ словарь (внутреннего слова в кадре нет);
  • карточка после отказа: книга failed, прогон failed + service_error (на стенде нет ключей провайдера — движок отказывает конфигурацией, и это ровно тот класс, который «повтор не лечит»);
  • resume упавшего прогона → run_not_resumable без cause (канон: это не остановленный прогон);
  • неверный код языка → invalid_request + errors[] на оба поля;
  • /usagehalt_reason: null (свой словарь, не прогонный).

Obstacle: что НЕ удалось и что не проверено

  1. Отложено в P8 явно (заказ промта): updateBook · deleteBook · getRun · createExport /getExport · эскроу (П-18).
  2. Note.code для незнакомой причины — отступление, не решение: unspecified (PD-246).
  3. Ни одного живого ЗАМЕЧАНИЯ и ни одного термина банка через настоящий прогон не прошло — прогон стоит денег, а сессия $0. Проекции замечаний и банка проверены тестами на живом PG и ручными строками unit_resolutions, но не сквозным прогоном движка.
  4. SSE с ЖИВЫМ прогоном не гонялся по той же причине: проверены книга в покое, реконнект, 204, склейка и resync_required — тестами и на стенде, но не под настоящим потоком кадров.
  5. Стоимость материализации не замерена на большой книге (PD-248): два полных ре-чанка на границу работы; на демо-книге в 3 главы это миллисекунды, на 2283 главах — секунды CPU.
  6. structure_version на РЕАЛЬНОЙ пере-нарезке проверен тестом (смена ключа манифеста), но не настоящей сменой чанкера движка.
  7. Опрос потока — 2 запроса/с на соединение (PD-247): LISTEN/NOTIFY не строился.
  8. Кадр note живьём не наблюдался: он рождается только на помеченной единице настоящего прогона. Проверен тестом на живом PG через настоящий RunSink.Apply (полный путь материализатора), но не в потоке под браузером.
  9. Полоса под настоящей работой не наблюдалась: progress {0,2} — это старт; движения счётчика на стенде без ключей провайдера не бывает.

Вопросы оркестратору (канал вопросов промта, НЕ тихая интерпретация)

  1. Словарь флаг-причин — рукописная копия чужой зоны без гейта (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 unspecified rather 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). Обратный вариант — чтобы движок эмитил сразу контрактный код — отвергнут: это зеркальная утечка, продуктовое слово поехало бы в движок, который о контракте знать не должен.

  2. sqlc — ОТЛОЖЕН СЛОВОМ ВЛАДЕЛЬЦА 17.08 («отложим»), строка PD-44 дополнена разбором. Фиксирую честно, включая довод против себя: пак был обязан его взять (D39.132 п.2б), и sqlc поймал бы ровно тот класс, который дважды укусил этот пак в рантайме (stop_for_signing, chapters_before) — батарея его поймала, но на цикл позже компилятора. Что мешает в read-модели: запросы собраны склейкой общих фрагментов (одна проекция книги на четыре пути), а sqlc требует литерального SQL на запрос, и страница с её ревизией — транзакция, которую он не моделирует. Предложение зоны: взять в P8 на отложенных однооператорных ручках, по итогам решить про read-модель.

  3. Поверхность входа: РЕШЕНО контрактной сессией 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/*, а не рост таблицы в спутнике.

  4. ⚠ НАХОДКА КОНТРАКТНОЙ СЕССИИ, которая дороже самого вопроса — передать оркестратору отдельно. Правило про вход было РАТИФИЦИРОВАНО и потеряно молча: в компаньоне 0.2.3 §2.14 стояло «Отказ входа — problem+json, как везде; различать причины отказа клиент не может по замыслу», а в сегодняшней редакции осталась только первая половина — вторую срезал коммит лендинга батча 0.3.0 (8d82096, проверено git log -S). То есть P7 не переоткрывал спор, а восстанавливал решение, которое кто-то выкинул. Класс потери структурно непроверяем существующей приёмкой: сверка батча шла по мультимножеству модальных глаголов (MUST/SHOULD/MAY), а в этой фразе модального глагола нет. Стоит отдельного разбора приёмки — не как дефект контракта, а как дыра в МЕТОДЕ проверки резки прозы.

  5. Плотность комментариев (PD-255): пак вышел за норму «одна-две строки почему». Часть прозы несущая, часть — эссе; прошу решение владельца о норме, а не тихую подрезку в следующем паке.

⟶ Дослано 22.08 оркестратором №18: записи акта 5, оставшиеся в живом журнале

Заголовок живого журнала «Эра пака P7 — в архиве» утверждал, что выселено всё, а ~185 строк остались на месте. Досылаю их сюда без правки тел.

  • P7, акт 5 — ЗАКРЫТ. Дерево готово к приёмке и лендингу. Батарея: 18 пакетов, exit 0, скипов 0, линтер 0 issues, тестовых функций 532 (⚠ было «533» — испр. оркестратором №18 20.08: канонической командой D39.121 grep -rh '^func Test' platform --include='*_test.go' | wc -l выходит 532; расхождение на единицу, ни на что не влияет, но число было заявлено без команды). Регистр — 327 строк, открытых 65 (13 minor, 52 info), major и BLOCKER нет. Дерево не закоммичено и не застейджено. Миграции пака: 0001900024.

    Что построено и зачем

    1. Долг на материализацию — колонка с арендой (books.read_model_owed_at, 00019). Ставится в ТОЙ ЖЕ транзакции, что закрывает границу работы (конец разбора и все три конца прогона), гасится сверкой метки, берётся в аренду тем, кто платит. Зачем: очередь жила в памяти процесса — упавший Refresh её терял, рестарт демона терял её всю, и оплаченный текст не доезжал до читателя НИКОГДА. Плюс «в покое» для потока теперь значит «и материализация не должна» — это Ф-56: книга, только что нарезанная, получала end и 204 «не переподключайся» ровно в те секунды, пока дерево ещё не легло. Почему отошёл от прежней реализации: удалены очередь в памяти (runs.deferRefresh/ DrainRefresh/Reader) и бэкстоп «дерева нет вовсе» (books.materializeMissingTrees, pgstore.BooksWithNoTree) — оба покрывались одним механизмом, а бэкстоп к тому же не видел книгу, у которой дерево есть, но на границу старше. Дрейн переехал в readmodel.Drain — в пакет, чья это работа.
    2. «Глава сделана» = последний проход, который книга на ЭТОМ деплое реально получает (books.edit_wave, 00024). Зачем: счёт шёл по волне edit, и на деплое без редактора ни одна глава не была сделана никогда — шкала покупки не подрезалась, сервис бесконечно предлагал купить уже переведённое. Форму объявляет сам движок (пайплайн без редактора даёт волне edit знаменатель ноль). Почему на КНИГЕ, а не на прогоне: прочитанная с последнего прогона, она роняла chapters_done в ноль в момент допуска нового прогона — счётчик ходил назад, что канон запрещает. Флаг монотонный: книга, прошедшая редактирующий пайплайн, остаётся такой.
    3. У клейма идемпотентности появился владелец (claim_token, 00020) и тождество интейка решается дайджестом файла (content_sha256, 00023). Зачем: строка ключа не несла, какая попытка её держит — застрявшая попытка удаляла строку преемника (пользователь получал вторую книгу) или писала свою квитанцию поверх чужой (клиент реплеил Location несуществующей книги); а тождество по Content-Length не различало две РАЗНЫЕ книги с одинаковым объявлением.
    4. Остановка АККАУНТА читается с аккаунта, а не с потолка одного прогона; resume различает «прогон закончен, нужен новый» (ceiling_reached) и «пополните счёт» (credit_unavailable); blocked называет холд, который реально освободит шкалу. Зачем: всё это — «у вас нет денег» пользователю, у которого деньги есть, и отправка его делать не то.
    5. Счётчик замечаний вернулся на главу (chapters.note_count, 00022), chapters.units_done снесён (00021). Почему это разворот решения 00016 того же пака: 00016 снёс колонку с доводом «её никто не пишет» — здесь она возвращается ВМЕСТЕ с писателем, которого ей не хватало. Основание замерено: джойн, которым её заменяли, стоил 83% времени страницы библиотеки (16.8 мс против 3.4 мс на корпусе 40 книг × 500 глав, ВАКУУМИРОВАННОМ); со счётчиком — 6.6 мс. ⚠ Условие замера названо здесь и при каждом другом числе про него (P8-FIX, PD-336): на ХОЛОДНОМ корпусе тот же джойн даёт 96%, и это не противоречие, а другой замер. Нормативный носитель вакуумированных цифр — миграция 00022, остальные на неё указывают. Бенчмарк оставлен в дереве: go test ./internal/pgstore/ -bench LibraryPage -run xxx.

    Что форсировано и почему

    • Работа по канону 0.4.0 лежит в дереве до его ратификации. Основание: ответ контрактной сессии на записку зоны плюс слово владельца 20.08 про «кончились деньги → сообщить и дать продолжить». Ломающая правка ровно одна — Run.stop_requested (0.3.0 такого поля не объявляет); cause.code: credit_unavailable легален и по 0.3.0, второй уровень там открыт. Зависимость записана строкой PD-327 и закрывается лендингом канона, а не работой зоны. Если форма сдвинется словом владельца — правки отделимы, карта в archive/P7_ACT5_FIX_PLAN_2026-08-20.md §5б.
    • Тесты на АТОМАРНОСТЬ сверяют xmin (транзакцию, последней писавшей строку). Это не экзотика ради экзотики: два пина акта проходили под мутацией «вынести метку из транзакции во второй оператор» — конечное состояние она не меняет, меняет окно, и утверждать про окно иначе тест не может.

    Что осталось, названо и не спрятано

    • PD-327 — ждёт лендинга канона 0.4.0 (не работа зоны).
    • PD-297 — round-trip на строку в SaveStructure под эксклюзивной блокировкой книги. Не взят осознанно: путь самый опасный на запись, инструмент штатный (tx.SendBatch), но правку надо мерить на корпусной книге до и после.
    • Четыре оси не смотрел никто и никогда: деньги и леджер целиком · вход/сессии/CSRF · очередь и джобы · метрики. Пак их не менял, а все приёмки смотрели дифф. Это отдельный пак.
    • Живого перевода на стенде не было ни разу — движку нужны ключи провайдера и деньги. Всё после «прогон стартовал и остановился» (подпись банка, halt на потолке, резюм после пополнения) покрыто батареей против живого Postgres, а не живым движком.

    Как это проверялось

    make check с гейтами (скипов 0) · каждая правка проверена ПОСАДКОЙ МУТАЦИИ: код испорчен именно так, как называет комментарий пина, пин обязан упасть, код возвращён (40 проб, все поймались) · живые пробы по ручкам на стенде: интейк и реплей, 409 key_reused на другой файл под тем же ключом, часть после файла на реплее, /chapters /units /notes /bank, ETag→304 и разные теги у страниц, 410 на чужой id главы, after_version из прошлой жизни, unsupported_pair, /usage на пустом счету и после adjust, старт и стоп прогона со stop_requested на проводе, SSE в покое и SSE с непогашенным долгом (Ф-56 на проводе), 400 вместо 413 на длинном теле.

    Самопроверка

    Акт закрыт адверсариальным ревью СОБСТВЕННОГО диффа (9 линз × 2 рефутера + критик полноты, 84 агента, 0 ошибок): 37 находок, 18 пережили рефутеров, и почти все — дефекты, внесённые правками самого акта. Строки PD-315…PD-327, каждая с механизмом и пином. Два из них — собственные пины акта, проходившие под мутацией, которую сами называют (PD-318): это третье появление класса в паке, и он назван в плане §3 п.2 ещё до того, как повторился.

  • P7, акт 5 (начало) — исполнение фиксов по ревью акта 4 + ответ контрактной сессии. План и рабочий список — platform/docs/archive/P7_ACT5_FIX_PLAN_2026-08-20.md (читать первым; §5а — что пришло из контракта). ⚠ Контрактная сессия ответила 20.08 на записку зоны archive/CONTRACT_SYNC_FROM_PLATFORM_2026-08-20.md: канон стал 0.4.0 (лежит в дереве контрактной зоны, НЕ ратифицирован — до лендинга в силе 0.3.0). Все три вопроса, висевшие на владельце, закрыты. Зоне пришла работа: resume исчерпанного прогона — 409 с двумя РАЗНЫМИ причинами (ceiling_reached против нового credit_unavailable); «глава сделана» = последний реально пройденный проход, и это ДЕНЬГИ — ChaptersLeft на деплое без волны редактора не подрезается вовсе, сервис бесконечно предлагает купить уже переведённое; отпечаток интейка — по содержимому, Content-Length из нормы вышел, а невозможность установить тождество обязана давать 409, а не реплей; Run.stop_requested обязательным булевым (единственная ломающая правка). Отклонены: слово под суточный потолок движка (решение владельца 15.08) и предложенный зоной признак «прогон продолжаем» (три лишних чтения на карточке, которую перечитывают на каждый кадр). Акт 4 закрылся адверсариальным ревью СОБСТВЕННОГО диффа (9 линз × 2 рефутера, 75 агентов): 33 находки, 27 пережили рефутеров, и часть из них внесли сами правки акта 4. Тяжелейшая — четыре независимые линзы сошлись на том, что реконсилятор снимал стоп подписи банка, которого никто не подписывал, и доспавнивал движок без --verify-bank: складывая снятие стопа внутрь общего reopen, акт 4 не проверил, что reopen зовёт не только resume, но и свип. Нарушение согласия пользователя; починено ключом на вызывающего, пин проверен посадкой мутации. ⚠ Первый написанный на это пин ПРОШЁЛ под мутацией — фикстура не доходила до кода, потому что часы сервиса захвачены при конструировании и f.now их не двигает. Урок в плане, §3. Закрыто также: загрузочная проверка пар — на ДОСТУПНОЙ половине (деплой с одними :unavailable больше не стартует), интейк не трактует пустой список как «пускать всё», теги сравниваются регистронезависимо. Батарея: 18 пакетов, exit 0, скипов 0, тестов 503.

  • P7, акт 4 — доработка 20.08: сверка ВСЕХ находок против дерева + триаж §8.2. (Пакет релеев archive/P7_ACCEPTANCE_HANDOFF_2026-08-17.md §7; три вопроса владельцу §8.1а; вопросы контракта вынесены в platform/docs/archive/CONTRACT_SYNC_FROM_PLATFORM_2026-08-20.md для отдельной контрактной сессии.) Все 74 позиции обеих приёмок сверены поштучно с ТЕКУЩИМ деревом — каждая с цитатой живого кода, исход «не проверено» отдельно от «опровергнуто». ⚠ Три места числились закрытыми и закрыты не были — правка не закрывала собственный сценарий: (1) подрезка буфера потока сверялась ПОСЛЕ того, как водяной знак перепрыгнул дыру, поэтому условие не срабатывало никогда и кадры note терялись молча (PD-283); (2) CompleteIdempotency писала квитанцию в строку, которая попытке уже не принадлежит — воспроизведено живым PG, клиент получал квитанцию ЧУЖОГО запроса (PD-284); (3) пин на снятие --verify-bank был ПУСТЫМ: фикстура стартовала прогон без stop_for_signing (PD-285). Все три «подтверждались» зелёной батареей. Ещё закрыто: HEAD на потоке больше не держит горутину и ошибка Flush не выдаётся за отправленный кадр (PD-286) · blocked называет чужую книгу только когда её холд укорачивает шкалу (PD-287, пина не было вовсе) · note_count и GET /notes описывают одно множество (PD-288) · устаревшая доставка не рождает кадр (PD-289) · чтения на одном снапшоте (PD-290) · флаг «текст прочитан» переехал на ПАРУ (PD-291) · снятие стопа банка внутри транзакции переоткрытия, отдельного писателя больше нет (PD-292) · проход материализации подрезается своим бюджетом (PD-293) · блокировка книги в решениях банка (PD-294) · дубликат индекса снят (PD-295) · маршруты и коды ошибок — по ОДНОЙ таблице, тесты ходят по ней (PD-296) · бэкстоп-свип для книги без дерева (PD-276 дозакрыт) · интейк не платит за третью ре-нарезку (PD-248 актуализирован). Триаж §8.2: 17 сделано, 5 отклонено с причиной, 1 релей. Батарея: 18 пакетов, exit 0, скипов 0, линтер 0 issues, тестов 458 → 501; каждая правка проверена посадкой мутации, не рассуждением. ⚠ Осознанно НЕ сделано, с причиной: PD-297 (round-trip на строку под блокировкой книги — мерить прежде правки) · PD-202 (draft-only, вопрос владельцу) · PD-282 (канон §resumeRun не покрывает «денег не осталось») · PD-298 (сперва сверка у движка) · PD-299. Осей, которых не смотрел никто: деньги и леджер · вход/сессии/CSRF · очередь и джобы · метрики.

  • P7: приёмка → правки → приёмка правок (17.08). ⚠ Полный хендофф под сессию без контекста — platform/docs/archive/P7_ACCEPTANCE_HANDOFF_2026-08-17.md (читать §0§3, дальше по нужде). Там же: принципы приёмки, сверка с исходным промтом, техдолг, план следующей сессии. Категорийное ревью по 12 осям с адверсариальной верификацией: 80 находок, 54 подтверждено, 16 правдоподобно, 10 опровергнуто; после дедупа 46 уникальных дефектов — 3 блокирующих класса, 15 HIGH, 17 MEDIUM. Батарея была зелёная, потому что все три блокирующих дефекта живут в путях, которые тесты проходили только в безопасной форме. Починено в зоне (регистр PD-257…PD-281): пере-нарезка книги больше не валит читающую модель навсегда (отложенный констрейнт, миграция 00018) · упавший tmctl export больше не затирает текст пустотой · весь блок идемпотентности (гонка первых попыток 500→409, область ключа, отпечаток с размером файла, закрытие ключа вне контекста запроса) · база полосы прогресса считает тот же проход, что числитель · SSE (подрезанные кадры → resync_required, дедлайн записи, id из будущего) · материализация не голодает свип · банк читается на штатном деплое · деплой-нота больше не включает обратно грант, который PD-104 выключил. Батарея: 18 пакетов, exit 0, скипов 0, линтер 0 issues, тестов 458 → 490. ⚠ Приёмка ПРАВОК (9 осей + fable-5) нашла, что восемь дефектов внесли сами правки — батарея не увидела ни одного, три были запинены под ошибку автора. Все исправлены; разбор — хендофф §0. Б-1 ПОЧИНЕН разбором с владельцем 17.08 (PD-277). Рамка приёмки была неверна: это не «нужен новый канал в движок», а невыполненная половина строки единого бэклога 191(в) (слово владельца 16.08, D39.144) — «проводка resume = снятие стопа до движка». Движок при выключенном --verify-bank уже делает модель владельца: неподписанное едет авто-строками с пометкой ⟨проверить⟩ (mining.go:211-231, D39.42 п.3), а платформа передавала флаг обратно и роняла прогон в тот же стоп. Снято: l.VerifyBank && !l.BankReleased; пин TestAResumedRunIsSpawnedWithoutTheSigningStop. ⚠ Остаток: decline пользователя до движка не доезжает (движок читает mined_rejects) — строка бэклога 192, отложена владельцем. PD-273 закрыт решением владельца 17.08: статус остаётся awaiting_bank, лечение — признак «стоп был запрошен» в контракте; готовая строка для единого бэклога — хендофф §7(и), вставляет оркестратор (зона docs/ не наша). Разбор, план, обязательства через оркестратора и команды воспроизведения — platform/docs/archive/P7_ACCEPTANCE_HANDOFF_2026-08-17.md. ⚠ Заявки раздела «Сессия P7» ниже приёмку НЕ прошли — читать их вместе с хендоффом, не вместо.

  • P7 ПОСТРОЕН — заявка САМОЙ сессии написания (17.08).Приёмку эта заявка НЕ прошла — см. верхнюю запись и archive/P7_ACCEPTANCE_HANDOFF_2026-08-17.md: 46 уникальных дефектов, три блокирующих класса, из них ни один батарея не видела. Читать ниже как исходную позицию, а не как состояние дерева. Читающая поверхность контракта 0.3.0: главы · пары с текстом · замечания · банк и подпись · SSE на КНИГЕ · GET /capabilities · машинная модель ошибок (code + request_id) · условные чтения и сжатие · Idempotency-Key · structure_version. Плюс форварды (title, reject_reason), PD-104 (грант → 0), демонтаж построенного гейта полноты банка (D39.144), PD-241, конформность построенного по §5 промта и рантбук деплоя, прогнанный ЖИВЫМ migrate. Батарея на стенде ~/.local/pgsql (порт 55433) под -race с ОБОИМИ гейтами: EXIT=0, 18 пакетов, скипов 0, линтер 0 issues, тестовых функций 416 → 458. Дифф: 46 файлов, +2329/646. Селф-ревью финалом — ТРИ независимых ревьюера, два из них другой модели: 22 находки, 17 подтверждены и починены (две HIGH нашли оба контрактных ревьюера независимо), 5 — диспозиция с аргументом. Разделы ниже — записка-план, живые пробы, таблица селф-ревью, obstacle, вопросы оркестратору.