Land the platform's documentation revision: the deploy runbook stops building an instance that refuses every run, and a dozen claims stop describing a tree that changed under them
This commit is contained in:
parent
40e1606fbb
commit
9e978d6a78
12 changed files with 259 additions and 75 deletions
|
|
@ -29,8 +29,8 @@
|
|||
| П-15 | **ИСПОЛНЕНО P6 (14.08).** Платформенная половина шва эмиттера (движковая залендена D39.131, `9cfe080`) | ИСПОЛНЕНО P6: коды выхода движка 4/5/10–19 через `ingest.OutcomeOf`, потолок = `paused` двумя каналами (PD-113 закрыт), интейк судит по коду (PD-196 закрыт), exit 5 без намерения = прерывание и перезапуск (PD-152 закрыт); фолд `unit_done` ПРИСВАИВАНИЕМ по тройке (chapter, unit, wave) через `unit_resolutions` (миграция 00015; ратифицировано D39.131 п.2г, at-least-once); StreamVersion 1.1, `Ceiling.Scope` (book/day), внутренняя причина `daily_ceiling`, резюм дневного потолка = 409 (PD-157 половина); dev-супервизор читает ту же таблицу; foreign-hello adoption воспроизведён и закрыт (PD-200); порядок деплоя — `deploy/README.md` + `tmplatformctl books --migratable`. ⚠ Граница присваивания (PD-219): оно самолечит ПОВТОРНУЮ доставку, а не пропущенную — недодрейненный хвост новая попытка не перечитывает, её курсор начинается с размера журнала на допуске | приёмка D39.131 |
|
||||
| П-14 | **ИСПОЛНЕНО P6 (14.08).** Стройка интейка формы Б (ратификация D39.130) | ИСПОЛНЕНО P6: рендер в ОДНОМ месте (`books.provision`); правила шаблона и его отказов — `deploy/README.md` §«Шаблон книги», требования к полям — `backend/internal/config/book.go` как справочник. При приходе формы В (строка 170 единого, `tmctl init`) рендер заменяется вызовом движка. Проверено настоящим `tmctl manifest` (3 главы) | ратификация D39.130 (приёмка P5) |
|
||||
| П-16 | **ИСПОЛНЕНО P6 (14.08).** Дев-сид стенда: тестовый пользователь, чтобы фронт разрабатывался на живой платформе, а не на своих моках (слово владельца 14.08). Границы: фикстуры фронта остаются батарее его гейтов (детерминированные состояния — не работа стенда); полный снос `frontend/src/mock/` — триггер Ф-29, после читающей поверхности | ИСПОЛНЕНО P6: `tmplatformctl seed` и дев-вход `TM_PLATFORM_DEV_LOGIN` с четырьмя гардами непроходимости в проде — рецепт и разбор гардов в `deploy/README.md` §«Дев-стенд». Прогнано на стенде целиком | владелец 14.08 |
|
||||
| П-17 | **ИСПОЛНЕНО P7 (16–17.08).** Читающая поверхность — остаток П-1, названный отдельной строкой | ИСПОЛНЕНО P7 по канону 0.3.0; состав поверхности — `platform/README.md` §«Что здесь будет» и §«Карта зоны» (`internal/readmodel`). ⚠ `submitBankDecisions` СНЯТ 22.08 вместе с пер-термной моделью подписи — D39.144, слово владельца; см. PD-370. **НЕ вошло и отложено в P8:** `updateBook`/`deleteBook`/`getRun`, экспорт (`createExport`/`getExport`), эскроу (П-18). ⚠ **ЭКСПОРТ ПОСТРОЕН 04.09** паком «закрыть цикл» — `createExport`/`getExport` плюс третий адрес `.../content`, который канон описывает словами (`Export.url`), а `operationId` не даёт; смонтировано 18 маршрутов. Остаются непостроенными `updateBook` · `deleteBook` · `getRun` (и `deleteBook` теперь стоит дороже: у книги появились АРТЕФАКТЫ на диске, и удаление обязано снимать и строки `exports`, и файлы — каскад по строкам есть, по файлам нет) | П-1, D39.84/85, контракт 14 |
|
||||
| П-17 | **ИСПОЛНЕНО P7 (16–17.08).** Читающая поверхность — остаток П-1, названный отдельной строкой | ИСПОЛНЕНО P7 по канону 0.3.0; состав поверхности — `platform/README.md` §«Что здесь будет» и §«Карта зоны» (`internal/readmodel`). ⚠ `submitBankDecisions` СНЯТ 22.08 вместе с пер-термной моделью подписи — D39.144, слово владельца; см. PD-370. **НЕ вошло и отложено в P8:** `updateBook`/`deleteBook`/`getRun`, экспорт (`createExport`/`getExport`), эскроу (П-18). ⚠ **ЭКСПОРТ ПОСТРОЕН 04.09** паком «закрыть цикл» — `createExport`/`getExport` плюс третий адрес `.../content` (канон дал ему `operationId` `downloadExport` минором `0.10.0` — `58bca19`, `D39.194` п.3); смонтировано 18 маршрутов. Остаются непостроенными `updateBook` · `deleteBook` · `getRun` (и `deleteBook` теперь стоит дороже: у книги появились АРТЕФАКТЫ на диске, и удаление обязано снимать и строки `exports`, и файлы — каскад по строкам есть, по файлам нет) | П-1, D39.84/85, контракт 14 |
|
||||
| П-18 | **Эскроу денег шва: write-ahead intent · `uncertain` · `closing`** (строка 136 единого бэклога) — денежный промт, сознательно НЕ взятый ни P5, ни P6. Здесь же закрывается PD-154 (`settled_at` между `Settle` и `MarkSettled`): половина эскроу рядом с проектируемым целым — второй, более слабый ответ на тот же вопрос | денежный промт | строка 136, PD-154 |
|
||||
| П-19 | **Конверсия свободного от склейки блока под `sqlc`** — ⚠ **ИСПОЛНЕНО отдельной сессией 29.08 и заленджено (D39.172; `PD-44` закрыт).** Отдельной сессией, а не внутри пака, — решение владельца 22.08: изменений много, мешать их с чем-либо нельзя. Что построено (пин v1.31.1, `sqlc.yaml`, генерённый код в дереве, два гейта актуальности, override `*.*_micro_usd` на деньги) — `docs/STACK_DECISIONS.md`, строка «Кодоген SQL». ⚠ Живая ГРАНИЦА набора, которую стоит помнить при следующем касании: **42 места вызова / 41 текст SQL / 40 конвертируемых**; `observe.go` структурно не конвертируется — River мигрирует `river_job` сам, и внесение чужой схемы в конфиг завело бы ВТОРОЙ её носитель (испр. D39.172), поэтому худший позиционный дрейф набора — семь `int64` подряд — остался рукописным. Остальные 25 склеенных мест недостижимы по построению (`lastRun` — девять потребителей, `nextRevisionOfThisBooksLibrary` — восемь), и это ровно та часть, где рантайм-ошибки и случались. ⚠ Пак покупал типизированные скан-структуры и раннюю обратную связь, а НЕ корректность: класс «нет такой колонки» закрыт постоянным гейтом `TestEverySQLStatementParsesAgainstTheMigratedSchema` (пол — 140 операторов; после конверсии видит 172 — числа в `PD-44`) | ИСПОЛНЕНО (D39.172) | PD-44, фикс-лист приёмки P7 п. 4, решения владельца 20 и 22.08 |
|
||||
| П-20 | **Хвост PD-162 после P8-FIX: холд прогона, чей движок не ответит НИКОГДА, возвращается только рукой.** Пак дал оператору и диагноз (`tmplatformctl runs --stalled`, гейдж `tm_platform_runs_stalled`), и терминальный вердикт (`run abandon [--release-hold]`), и это сознательная граница: автоматически решать про деньги прогона, о котором нельзя спросить, — та же ошибка «я не смог спросить = его нет», только со счётчиком впереди. Автоматический ответ на этот вопрос и есть эскроу — write-ahead intent · `uncertain` · `closing` (строка 136 единого бэклога, П-18). Пока эскроу нет, ручка остаётся ручной, и это записано, а не подразумевается | вместе с П-18 | P8-FIX |
|
||||
| П-21 | **Ключ идемпотентности под конкуренцией даёт 500 (PD-369).** ⚠ Не работа пака P8-FIX и файл им не тронут — найдено попутно, прогоном батареи: собственный тест `TestAClaimThatLostARaceToAReleaseIsRetriedAndNotAnError` ФЛЕЙКОВЫЙ, 1–2 падения на ~80 прогонов. Причина не в тесте: `ClaimIdempotency` ретраит проигранную гонку ровно `claimRounds = 3` раза, а восемь одновременных попыток одного ключа, каждая из которых сразу отдаёт ключ назад (как делает любой 4xx), могут отобрать гонку у одного проигравшего трижды подряд — и он получает внутреннюю ошибку вместо одного из четырёх контрактных ответов. Направление, а не решение: граница по ВРЕМЕНИ вместо числа раундов либо `ErrKeyInFlight` при исчерпании — это решение о контрактном поведении | до первого чужого пользователя | попутная находка сессии P8-FIX |
|
||||
| П-21 | **Ключ идемпотентности под конкуренцией даёт 500 (PD-369).** ⚠ Не работа пака P8-FIX и файл им не тронут — найдено попутно, прогоном батареи: собственный тест `TestAClaimThatLostARaceToAReleaseIsRetriedAndNotAnError` ФЛЕЙКОВЫЙ, 1–2 падения на ~80 прогонов. Причина не в тесте: `ClaimIdempotency` ретраит проигранную гонку ровно `claimRounds = 3` раза, а восемь одновременных попыток одного ключа, каждая из которых сразу отдаёт ключ назад (как делает любой 4xx), могут отобрать гонку у одного проигравшего трижды подряд — и он получает внутреннюю ошибку вместо одного из четырёх контрактных ответов. ⚠ **ЗАКРЫТО паком P12** — второй из двух названных форм, ответом, а не границей по времени: `ErrKeyContended` оборачивает `ErrKeyInFlight` (`internal/pgstore/idempotency.go:92`), словарь кодов контракта не расширен, исчерпание раундов стало контрактным ответом вместо `500`. Регистр — `PD-369` = `fixed(пак P12)`. Строка держалась развилкой после того, как развилка была разрешена кодом | ИСПОЛНЕНО (пак P12) | попутная находка сессии P8-FIX |
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# platform — control plane (SaaS-слой)
|
||||
|
||||
Зона записи сессии «Платформа». Что построено и каким паком — секция «Что здесь будет» ниже и
|
||||
шапка «Текущее состояние» зонного журнала. Направление зоны — `docs/PLATFORM_DIRECTION.md`,
|
||||
ВЕРХ зонного журнала: он обратно-хронологический, свежее — выше (раздел «Состояние эры P8» в конце файла — ИСТОРИЯ, не состояние). Направление зоны — `docs/PLATFORM_DIRECTION.md`,
|
||||
критерии приёмки — `docs/ENGINEERING_STANDARDS.md`, дефекты — `docs/DEFECT_REGISTER.md`, зонный
|
||||
журнал — `docs/platform-PROGRESS.md` (весь прогресс зоны здесь, решение владельца 04.08), стек —
|
||||
`docs/STACK_DECISIONS.md`.
|
||||
|
|
@ -45,7 +45,7 @@ Prometheus; на контрактную поверхность они не вы
|
|||
|
||||
| Вопрос | Ответ лежит здесь |
|
||||
|---|---|
|
||||
| что сделано последним паком и зачем | `docs/platform-PROGRESS.md`, шапка «Текущее состояние» |
|
||||
| что сделано последним паком и зачем | `docs/platform-PROGRESS.md`, **ВЕРХ файла** — журнал обратно-хронологический, свежее выше; отчёт последнего пака — в верхней трети, ниже могут стоять более свежие записи смены |
|
||||
| статус конкретного дефекта | `docs/DEFECT_REGISTER.md` (источник истины; счёт — `python3 docs/scripts/counts.py` от корня репозитория) |
|
||||
| правила, которые переживают пак и не выводятся из одной функции | `docs/STACK_DECISIONS.md` §22 (порядок блокировок), §33–36 (долг материализации, форма пайплайна, атомарность) |
|
||||
| что зона обязана уметь и по какой норме | `docs/PLATFORM_DIRECTION.md`, `docs/ENGINEERING_STANDARDS.md` |
|
||||
|
|
@ -95,7 +95,7 @@ Prometheus; на контрактную поверхность они не вы
|
|||
реконсилятор, который читает мир (Postgres · журнал книги · маркер выхода) и не ждёт процесса;
|
||||
- читающая поверхность контракта — **есть (P7)**: дерево глав и пары с текстом, замечания, ЧТЕНИЕ
|
||||
банка, `GET /capabilities`, машинная модель ошибок (`code` + `request_id`), условные чтения
|
||||
(`ETag`/304) и сжатие JSON, `Idempotency-Key` на двух создающих вызовах. ⚠ Снята ПЕР-ТЕРМНАЯ
|
||||
(`ETag`/304) и сжатие JSON, `Idempotency-Key` на трёх создающих вызовах. ⚠ Снята ПЕР-ТЕРМНАЯ
|
||||
МОДЕЛЬ ПОДПИСИ (22.08, `PD-370`, D39.144: подпись — это `resume`), а НЕ правка термина: дверь
|
||||
`POST /books/{bookId}/bank/corrections` построена паком P9 — `httpapi/bank.go`, маршрут в
|
||||
`contractSurface` (`httpapi/v0.go`), канон `14-api-contract/openapi.yaml`, акты D39.161/162/166,
|
||||
|
|
@ -126,7 +126,7 @@ Prometheus; на контрактную поверхность они не вы
|
|||
|---|---|
|
||||
| `cmd/tmplatformd` | демон: сборка зависимостей, очередь, свипы. `runner.go` — единственное место, где зона склеивается |
|
||||
| `cmd/tmplatformctl` | админ-CLI и `exit-marker`, который systemd зовёт на конце прогона |
|
||||
| `internal/httpapi` | ВСЯ контрактная поверхность: `v0.go` (маршруты, библиотека, интейк, прогоны) · `reading.go` (главы, пары, замечания, банк) · `stream.go` (SSE) · `capabilities.go` · `problem.go` (модель ошибок) · `conditional.go` (ETag/304 + gzip; SSE через него НЕ проходит — потому и не сжимается) · `idempotency.go` · `project.go` (переводы словарей: read-модель → провод) · `bank.go` (дверь правок банка) |
|
||||
| `internal/httpapi` | ВСЯ контрактная поверхность: `v0.go` (маршруты, библиотека, интейк, прогоны) · `reading.go` (главы, пары, замечания, банк) · `stream.go` (SSE) · `capabilities.go` · `problem.go` (модель ошибок) · `conditional.go` (ETag/304 + gzip; SSE через него НЕ проходит — потому и не сжимается) · `idempotency.go` · `project.go` (переводы словарей: read-модель → провод) · `bank.go` (дверь правок банка) · `exports.go` (дверь выдачи книги файлом) |
|
||||
| `internal/auth`, `internal/login` | сессии, CSRF, вход. Принципал создаётся ТОЛЬКО в мидлваре |
|
||||
| `internal/books` | интейк: приём файла, каталог книги, рендер стартового `book.yaml`, разбор через `tmctl manifest` |
|
||||
| `internal/runs` | жизнь прогона: допуск, спавн транзиентного юнита, реконсилятор, деньги на границе попытки |
|
||||
|
|
|
|||
|
|
@ -21,12 +21,18 @@
|
|||
(`MemoryMax=80%`, `OOMPolicy=continue`) живьём не проверялись — вывод из
|
||||
`systemd.resource-control(5)`/`systemd.service(5)`.
|
||||
|
||||
## Откат релиза: не ниже версии 5
|
||||
## Откат релиза: не ниже версии 15
|
||||
|
||||
`goose down` до версии 4 и ниже НЕ РАБОТАЕТ на живой базе: down-путь `00005` восстанавливает
|
||||
⚠ **Граница была названа «5» и это уже неверно.** `goose down` до версии 14 и ниже НЕ РАБОТАЕТ на
|
||||
живой базе: down-путь `00015` сужает `runs_paused_reason_check` обратно к одному `credit_exhausted`,
|
||||
а боевой код пишет ещё два значения — `daily_ceiling` и `ceiling_unknown` (оба через
|
||||
`internal/pgstore/books.go` `CeilingPause`). Значит `DownTo(<15)` на базе, где такая пауза
|
||||
случалась, падает РАНЬШЕ, чем дойдёт до `00005`. Носитель — открытый ряд `PD-218`.
|
||||
|
||||
Ниже 5 не работает и подавно: down-путь `00005` восстанавливает
|
||||
`users_email_key` и `email NOT NULL`, а обе формы нарушают строки, которые пишет боевой код
|
||||
(неподтверждённая личность даёт `email = NULL`; один адрес законно принадлежит двум аккаунтам).
|
||||
Откат транзакционный, поэтому падение ничего не портит — но планировать откат ниже 5 нельзя,
|
||||
Откат транзакционный, поэтому падение ничего не портит — но планировать откат ниже 15 нельзя,
|
||||
план отката — накатить вперёд. Разбор: `docs/STACK_DECISIONS.md` §8.
|
||||
|
||||
## Что юнит закрывает содержательно
|
||||
|
|
@ -41,8 +47,13 @@
|
|||
(`TM_PLATFORM_RUN_MEMORY_MAX`/`_TASKS_MAX`), и это ИЗМЕРЕНО — в дефолтном `app.slice`
|
||||
пользовательского менеджера лимиты принимаются и не применяются (`STACK_DECISIONS` §16).
|
||||
Дев-путь супервизора с группой процессов остаётся дев-путём.
|
||||
- **`TimeoutStopSec=90`** больше, чем дренаж платформы (15 с) плюс grace движка (30 с). Меньше —
|
||||
и systemd прибьёт `tmctl` посреди остановки, оставив лок проекта.
|
||||
- **`TimeoutStopSec=90`** ограничивает остановку САМОГО ДЕМОНА: дренаж HTTP плюс останов очереди,
|
||||
с запасом. ⚠ Прежняя редакция объясняла его «grace движка 30 с» — такой грации нет: `stopGrace`
|
||||
равен 10 МИНУТАМ (`internal/runner/runner.go:59`) и штампуется как `TimeoutStopSec` на
|
||||
ТРАНЗИЕНТНОМ юните прогона, а не на этом. ⚠ Но и обратное неверно: четыре движковых вызова —
|
||||
`manifest`/`status`, сборка экспорта, `bank-apply` и сам `systemd-run` — идут ПРЯМЫМИ ДЕТЬМИ
|
||||
демона, лежат в его cgroup, и этот таймаут на них распространяется. Разбор — комментарий в
|
||||
`deploy/tmplatformd.service` над строкой `TimeoutStopSec=`.
|
||||
- **Секреты через `LoadCredential=`,** а не через окружение: переменная окружения видна в
|
||||
`/proc/<pid>/environ` и наследуется каждым ребёнком-`tmctl`. Конфиг читает `*_FILE` первым.
|
||||
|
||||
|
|
@ -56,8 +67,11 @@ useradd --system --home-dir /srv/textmachine tmplatform
|
|||
install -d -m0750 -o tmplatform -g tmplatform /srv/textmachine
|
||||
install -D -m0755 tmplatformd /usr/local/bin/tmplatformd
|
||||
install -D -m0755 tmplatformctl /usr/local/bin/tmplatformctl
|
||||
# ⚠ БЕЗ ЭТОГО НИ ОДИН ПРОГОН НЕ СТАРТУЕТ. Прогоны — транзиентные юниты в пользовательском
|
||||
# менеджере `tmplatform`, а он существует вне сессии входа только при включённом linger.
|
||||
# ⚠ ДЛЯ БОЕВОГО ДЕПЛОЯ ОБЯЗАТЕЛЬНО. Прогоны — транзиентные юниты в пользовательском менеджере
|
||||
# `tmplatform`, а он ПЕРЕЖИВАЕТ выход из сессии только при включённом linger.
|
||||
# ⚠ Формулировка «без этого не стартует НИ ОДИН прогон» снята 04.09 как ложная для стенда: в
|
||||
# ЖИВОЙ сессии менеджер уже есть, и замер зоны (H2) дал `Linger=no` при шести стартовавших
|
||||
# прогонах. Сессия, честно исполнившая прежнюю проверку, решала, что стенд сломан.
|
||||
# Проверка: `systemctl --user -M tmplatform@ is-system-running` отвечает, а не «Failed to connect».
|
||||
loginctl enable-linger tmplatform
|
||||
# Движок кладётся по ВЕРСИОНИРОВАННОМУ пути: попытка прогона пиннится к тому, с которого началась
|
||||
|
|
@ -82,10 +96,12 @@ TM_PLATFORM_BOOKS_DIR=/srv/textmachine/books
|
|||
TM_PLATFORM_BOOK_TEMPLATE=/srv/textmachine/book-template.yaml
|
||||
TM_PLATFORM_METRICS_ADDR=127.0.0.1:9464
|
||||
|
||||
# ⚠⚠ ДОПИСАНО 29.08. БЕЗ ЭТИХ ЧЕТЫРЁХ ИНСТАНС НЕ ЗАПУСТИТ НИ ОДНОГО ПЕРЕВОДА, и это не
|
||||
# «неполный пример», а окружение, при котором каждый ОПЛАЧЕННЫЙ прогон падает.
|
||||
# ⚠⚠ ДОПИСАНО 29.08, ПЕРЕ-СНЯТО 04.09. Прежняя редакция говорила «без этих четырёх инстанс не
|
||||
# запустит ни одного перевода» — неверно для ДВУХ из четырёх, разбор ниже под блоком. Держи их
|
||||
# все, но знай, ЧТО именно ломает каждая: считать обязательными все четыре дешевле не выходит —
|
||||
# оператор, увидев, что без CTL_BIN всё работает, перестаёт верить и остальным трём.
|
||||
TM_PLATFORM_ENGINE_BIN=/opt/textmachine/engine/<версия>/tmctl # пусто = инстанс только читает
|
||||
TM_PLATFORM_CTL_BIN=/opt/textmachine/bin/tmplatformctl # его зовёт юнит как ExecStopPost
|
||||
TM_PLATFORM_CTL_BIN=/usr/local/bin/tmplatformctl # его зовёт юнит как ExecStopPost
|
||||
TM_PLATFORM_STATE_DIR=/var/lib/tmplatform/state # маркеры выхода; ТОЛЬКО абсолютный
|
||||
TM_PLATFORM_ENGINE_KEYS_PATH=/etc/tmplatform/engine-keys # KEY=VALUE, едет --keys-file
|
||||
|
||||
|
|
@ -96,13 +112,33 @@ TM_PLATFORM_EXPORTS_DIR=/var/lib/tmplatform/exports # ТОЛЬКО абсол
|
|||
TM_PLATFORM_EXPORT_TTL=24h # сколько живут артефакт и ссылка
|
||||
```
|
||||
|
||||
⚠ **Почему каждая из четырёх обязательна, а не желательна:**
|
||||
- **`ENGINE_BIN`** пусто — инстанс объявляет себя читающей репликой и не спавнит ничего;
|
||||
- **`CTL_BIN`** — путь, который юнит зовёт в `ExecStopPost`, чтобы записать маркер выхода; без него
|
||||
прогон завершается, а платформа об этом не узнаёт никогда;
|
||||
- **`STATE_DIR`** — каталог маркеров. ⚠ Дефолт лежит ВНЕ `ReadWritePaths=` юнита, а `deploy/`
|
||||
ставит `ProtectSystem=strict`, поэтому без явного значения запись маркера запрещена файловой
|
||||
системой, а не логикой;
|
||||
⚠ **Что из этой четвёрки ДЕЙСТВИТЕЛЬНО гейтит прогон — одна переменная, а не четыре.** Остальные
|
||||
три ломаются иначе, и знать чем дешевле, чем считать их все обязательными:
|
||||
- **`ENGINE_BIN`** — ЕДИНСТВЕННАЯ, чьё отсутствие останавливает прогоны прямо: пусто ⇒ инстанс
|
||||
объявляет себя читающей репликой, не спавнит ничего и не принимает загрузок
|
||||
(`cmd/tmplatformd/runner.go:59-61`);
|
||||
- **`CTL_BIN`** — путь, который юнит зовёт в `ExecStopPost`, чтобы записать маркер выхода.
|
||||
⚠ **ПУСТО — НЕ отказ:** берётся `tmplatformctl` РЯДОМ с демоном (`internal/config/config.go:115`,
|
||||
сиблинг-дефолт `cmd/tmplatformd/runner.go:468-476`), а рантбук ставит его туда же, куда и демона
|
||||
(`install -D -m0755 tmplatformctl /usr/local/bin/tmplatformctl` в блоке установки выше).
|
||||
Отказывает **НЕВЕРНЫЙ** путь: `os.Stat` не находит его на буте (`runner.go:87` —
|
||||
WARN, не паника), `MarkerArgv` остаётся пустым, и каждый старт/резюм отвечает `503`
|
||||
(`internal/runs/runs.go:415-418` `ErrRunnerIncomplete` → `internal/httpapi/v0.go:844-848`).
|
||||
⚠ Прежняя редакция объясняла его ОТСУТСТВИЕ симптомом «прогон завершается, а платформа не узнаёт»
|
||||
— такого исхода в коде нет: ровно чтобы он не наступил, зона и отказывает на старте
|
||||
(`internal/runs/runs.go:151-152`);
|
||||
- **`STATE_DIR`** — каталог маркеров. ⚠ **Прежняя редакция объясняла его ложно** («дефолт вне
|
||||
`ReadWritePaths=`, запись запрещена файловой системой»): дефолт — `/var/lib/tmplatform`
|
||||
(`internal/config/config.go:463`), а это ровно то, что юниту даёт `StateDirectory=tmplatform`
|
||||
(строка `StateDirectory=tmplatform` в `deploy/tmplatformd.service`), — systemd создаёт каталог
|
||||
и делает его записываемым ПОД
|
||||
`ProtectSystem=strict`, а сам маркер пишет `ExecStopPost` ТРАНЗИЕНТНОГО юнита прогона, на
|
||||
котором песочницы нет вовсе. ⚠ Задавать явно всё же стоит, но по ТРЁМ ДРУГИМ причинам, и они
|
||||
настоящие: путь обязан быть абсолютным (демон отказывает на старте, `internal/config/config.go:484`) ·
|
||||
каталог обязан принадлежать пользователю ПРОГОНОВ — по его владельцу админ-CLI отличает свой
|
||||
systemd от чужого и без этого отказывается судить о судьбе прогона
|
||||
(`cmd/tmplatformctl/runs.go:360` `notTheRunsOwnManager`) · и он не должен меняться при живых
|
||||
прогонах: смена осиротляет exit-маркеры идущих (`PD-155`);
|
||||
- **`EXPORT_FORMATS`** — не из четвёрки и не обязательна, но у неё своя ловушка: это
|
||||
ДЕКЛАРАЦИЯ ОПЕРАТОРА о том, что умеет РАЗВЁРНУТЫЙ здесь движок, а не открытие. Список
|
||||
форматов живёт Go-переменной внутри движка, импортировать который платформе запрещено
|
||||
|
|
@ -111,8 +147,19 @@ TM_PLATFORM_EXPORT_TTL=24h # сколько живут арте
|
|||
кодом `deployment_error`: честно, но чинить это вам. ⚠ `EXPORTS_DIR` абсолютен по
|
||||
причине острее, чем у `STATE_DIR`: путь уходит движку аргументом `--out`, а движок
|
||||
работает с каталогом КНИГИ как рабочим, поэтому относительный писал бы артефакты
|
||||
внутрь чужого проекта;
|
||||
- **`ENGINE_KEYS_PATH`** — ЕДИНСТВЕННЫЙ канал провайдерских ключей в движок (едет аргументом
|
||||
внутрь чужого проекта. ⚠ **И он обязан быть ЗАПИСЫВАЕМ ВНУТРИ ПЕСОЧНИЦЫ ЮНИТА** — для
|
||||
`BOOKS_DIR` эта оговорка написана, для экспорта её не было. Дефолт `<STATE_DIR>/exports`
|
||||
лежит под `StateDirectory=tmplatform` и потому пишется; любой путь вне его надо добавить
|
||||
в `ReadWritePaths=`. ⚠ Симптом НЕ тот, которого ждёшь: каталог создаёт САМ ДЕМОН НА БУТЕ
|
||||
(`cmd/tmplatformd/runner.go:208`), и невозможность создать — **ОТКАЗ СТАРТА** демона
|
||||
(`exports directory <путь>: …`), а не отказ отдельного экспорта. Каталог, который есть,
|
||||
но доступен только на чтение, роняет создание подкаталога КНИГИ на первом же экспорте
|
||||
(`internal/exports/exports.go:358`), и код отказа там `build_failed`, а не
|
||||
`deployment_error`;
|
||||
- **`ENGINE_KEYS_PATH`** (четвёртая из «четвёрки»; **старту НЕ мешает** — демон пишет один WARN
|
||||
и поднимается здоровым, `cmd/tmplatformd/runner.go:80`, принимает книгу и БЕРЁТ ХОЛД, а падает
|
||||
первый платный вызов, то есть уже после резервирования денег пользователя — замер H1 зоны) —
|
||||
ЕДИНСТВЕННЫЙ канал провайдерских ключей в движок (едет аргументом
|
||||
`--keys-file`, мимо процесса платформы и мимо окружения юнита). Без него движок ищет `.env` рядом
|
||||
с `book.yaml`, которого SaaS-путь не пишет, и каждый платный прогон падает `exit 10`
|
||||
(«missing API keys»). На буте это WARN, а не отказ, — то есть тихо.
|
||||
|
|
|
|||
|
|
@ -38,8 +38,17 @@ Group=tmplatform
|
|||
# its children the way the engine expects; SIGKILL to everything left when the timeout runs out.
|
||||
KillMode=mixed
|
||||
KillSignal=SIGTERM
|
||||
# Longer than the platform's own drain (15s) plus the engine's stop grace (30s), or systemd would
|
||||
# SIGKILL a tmctl mid-shutdown and leave its project lock behind.
|
||||
# This bounds the shutdown of THIS unit: the HTTP drain plus the queue stop, with slack.
|
||||
# ⚠ Two corrections, 04.09, and the second reverses the first attempt at fixing this comment.
|
||||
# (1) The edition before that justified the number by "the engine's stop grace (30s)". There is no
|
||||
# 30s grace anywhere: `stopGrace` is 10 MINUTES (internal/runner/runner.go:59) and it is stamped
|
||||
# as TimeoutStopSec on the TRANSIENT RUN unit (runner.go:166), not honoured by this one.
|
||||
# (2) The first correction then claimed no tmctl is a child of this unit — ALSO false, and in the
|
||||
# more dangerous direction. Only `translate` is a transient unit. Four engine invocations run as
|
||||
# DIRECT CHILDREN of this daemon: manifest/status (internal/runner/engine.go:201), the export
|
||||
# build (build.go:88), bank-apply (bankapply.go:73) and systemd-run itself (runner.go:107).
|
||||
# Those DO sit in this unit's cgroup and this timeout DOES apply to them. What the header's
|
||||
# D39.106 rules out is a translation being killed here — not every tmctl.
|
||||
TimeoutStopSec=90
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
|
|
@ -90,9 +99,15 @@ RestrictRealtime=yes
|
|||
LockPersonality=yes
|
||||
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
|
||||
|
||||
# These bound the unit's CGROUP, and by the argument at the top of this file every tmctl the
|
||||
# platform spawns lives in it. So they do not bound "the control plane" — they bound the control
|
||||
# plane plus every run in flight, together (PD-55). Two consequences follow, and both are decided
|
||||
# These bound the unit's CGROUP: the control plane PLUS every engine process the daemon spawns
|
||||
# in-process — manifest/status, the export build, bank-apply — but NOT translation runs, which are
|
||||
# transient units in the user manager (the header's D39.106).
|
||||
# ⚠ Both earlier editions of this sentence were wrong, in opposite directions, and the pair is worth
|
||||
# keeping: the first said "every tmctl the platform spawns lives in it… the control plane plus every
|
||||
# run in flight, together (PD-55)" — pre-D39.106, and contradicted the header of this same file; the
|
||||
# 04.09 correction then said the cgroup holds the control plane and nothing else — which erased the
|
||||
# four in-process children that really are in it. PD-136 recorded the first paragraph as DELETED; it
|
||||
# was rewritten, not deleted, and only on 04.09. Two consequences follow, and both are decided
|
||||
# here rather than discovered in production:
|
||||
#
|
||||
# 1. The ceiling is a machine backstop, not a service sizing. systemd.resource-control(5) calls
|
||||
|
|
@ -106,8 +121,10 @@ RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
|
|||
# (TM_PLATFORM_RUN_*). That is the answer to PD-13, and it is measured (STACK_DECISIONS §16).
|
||||
MemoryMax=80%
|
||||
OOMPolicy=continue
|
||||
# Platform plus concurrent runs, each a Go process with a few dozen threads: room for roughly a
|
||||
# dozen runs on one VM, which is more than a single box will carry.
|
||||
# ⚠ The previous justification counted "platform plus concurrent RUNS" — a surviving conclusion of
|
||||
# the premise removed above: runs are not in this cgroup. What to count is the daemon plus its
|
||||
# IN-PROCESS children (manifest/status, the export build, bank-apply, systemd-run), each a Go
|
||||
# process with a few dozen threads. The number is unchanged: it is generous for that set too.
|
||||
TasksMax=512
|
||||
LimitNOFILE=8192
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -106,8 +106,10 @@ hosted IdP (связка PII + доступность, а свою сессию
|
|||
> ⚠ **`oapi-codegen` ПЕРЕ-ПОДПИСАН `D39.132` п.2б: КАНДИДАТ**, решение за паком, который его возьмёт.
|
||||
> Довод отказа P7 (20.08) остаётся действующим аргументом, а не историей:
|
||||
>
|
||||
> - **`oapi-codegen`.** Довод «на P7 ручек станет больше, значит дешевле сейчас» проверен фактом: 20
|
||||
> операций контракта, из них построено 14, и КАЖДАЯ требует рукописной проекции read-модели в
|
||||
> - **`oapi-codegen`.** Довод «на P7 ручек станет больше, значит дешевле сейчас» проверен фактом: на 20.08 — 20
|
||||
> операций контракта, из них построено 14; **на 04.09 — 21 и 18** (пере-счёт: `operationId:` в
|
||||
> `openapi.yaml`, записи `contractSurface` в `internal/httpapi/v0.go`; непостроены `updateBook`,
|
||||
> `deleteBook`, `getRun`), то есть довод не устарел, а усилился. И КАЖДАЯ требует рукописной проекции read-модели в
|
||||
> контрактные словари (`internal/httpapi/project.go`) — генератор даёт имена полей, а переводит
|
||||
> словари всё равно человек. Плюс замеренное 05.08: 3.1-условия (`if action=approve → dst`)
|
||||
> генератор игнорирует, и исполнителем правила остаётся констрейнт БД. Взамен пак поставил ДРУГОЙ
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
|---|---|---|---|
|
||||
| Go (язык, `go.mod`) | **1.26.4** | 02.06.2026 | Тот же floor, что у движка (`backend/go.mod`) — общий стенд собирает оба модуля одним тулчейном |
|
||||
| Go (тулчейн сборки, `make version-check` + `toolchain` в `go.mod`) | **≥1.26.6** | 13.08.2026 | Поднят с 1.26.5 не по вкусу, а по `make vuln`: база адвизори опубликовала пять уязвимостей stdlib против 1.26.5 — `net/http`, `crypto/tls`, `net/url`, `encoding/xml`, `encoding/asn1` (GO-2026-6218/6090/6089/6088/5972), все закрыты в 1.26.6, и две трассируются в пути, которые эта служба зовёт (`pgstore.Open → pgx.ParseConfig → asn1.Unmarshal`). На 1.26.6 батарея зелёная и скан чист. ⚠ Как floor РЕАЛЬНО держится: `make version-check` СРАВНИВАЕТ версии (`sort -V`, префиксы `rc`/`devel` отвергаются), и `go.mod` несёт `toolchain go1.26.6` — его читает всякая сборка, даже мимо make: при `GOTOOLCHAIN=auto` хост скачает нужный тулчейн, при `=local` остановится с ошибкой. Само сравнение запинено (`internal/gates`) |
|
||||
| PostgreSQL | **18.x** (проверено на 18.4), floor **16** | 18.4 — май 2026 | 18 — текущая мажорная (19 в бете, в прод не берём); floor 16, потому что River тестируется на трёх последних мажорных |
|
||||
| PostgreSQL | **18.x** (проверено на 18.4), floor **16** (⚠ **ничем НЕ гейчен, в отличие от Go-floor выше:** `Open` только разбирает DSN, `Ready` сверяет `goose_db_version` И наличие схемы очереди (`select to_regclass('river_job')`), но не версию сервера, `server_version` в зоне не читается нигде — Postgres ниже 16 поднимет службу и упадёт впервые на запросе, чьего синтаксиса не знает) | 18.4 — май 2026 | 18 — текущая мажорная (19 в бете, в прод не берём); floor 16, потому что River тестируется на трёх последних мажорных |
|
||||
| HTTP | stdlib `net/http` + `ServeMux` | — | Роутер-библиотека не нужна: `ServeMux` с 1.22 умеет метод+wildcards, а `Request.Pattern` даёт лог по маршруту, не по пути |
|
||||
| CSRF | stdlib `http.CrossOriginProtection` | Go 1.25 | Ровно тот механизм, что описан в §5 (Sec-Fetch-Site → Origin), теперь в тулчейне — свой велосипед не пишем |
|
||||
| Postgres-драйвер | `github.com/jackc/pgx/v5` **v5.10.0** | 03.06.2026 | Живой pool, `pgconn.PgError` для проверки констрейнтов, `stdlib` для goose |
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
| OIDC-вход | `golang.org/x/oauth2` **v0.36.0** + `github.com/coreos/go-oidc/v3` **v3.20.0** | 11.02.2026 · 08.07.2026 | Ратифицировано `PLATFORM_DIRECTION.md` §1 (там же — что отдано библиотекам и что пишем сами); сверено живьём 05.08. Транзитивно приходит `go-jose/v4` v4.1.4 |
|
||||
| Рейт-лимит в процессе | `golang.org/x/time` **v0.15.0** | 11.02.2026 | `rate.Limiter` на ОБЕИХ неаутентифицированных ручках, которые ПИШУТ: `/auth/login` (строка состояния) и `/auth/callback` (строка журнала на каждом отказе — замерено ~880 строк/с с одного хоста, пока лимита не было) |
|
||||
| Линтер | `golangci-lint` **2.12.2** | 06.05.2026 | Тот же пин, что у движка: находки версионно-зависимы, разъезд пинов = разные гейты в одном репо |
|
||||
| Кодоген SQL | `sqlc` **v1.31.1** | 22.04.2026 | **Инструмент разработчика, НЕ зависимость модуля** — в `go.mod` не входит, рантайм-граф не растёт ни на один пакет; пин держит `make tools-check`, как у линтера, и по более острой причине: генерённый код лежит В ДЕРЕВЕ, поэтому другая версия молча даёт другой диф и `sqlc diff` краснеет на чистом клоне. **Зачем:** генерирует слой запросов свободного от склейки блока `internal/pgstore` (40 запросов из `queries/*.sql`) — SQL и его `Scan` перестают быть двумя списками, которые сверяет человек ПО ПОЗИЦИИ. Взят по решению владельца 20.08 (`D39.153` п.6а, уточнение `D39.154`), носитель — `BACKLOG.md` П-19. ⚠ Довод «гейт `sqlgate_test.go` это уже закрыл» ПРОВЕРЕН и не подтвердился: гейт получает только СТРОКУ SQL и Go-сторону вызова не видит — шесть посаженных перестановок целей `Scan` и сломанных арностей ВЫЖИЛИ на полной батарее (замер 29.08, `platform-PROGRESS.md`). Схему читает ТОТ ЖЕ каталог `migrations/`, что гейтит манифест (второго носителя нет); деньги держит подстановочный override `*.*_micro_usd` → `money.MicroUSD` — без него генератор выдаёт голый `int64`, с ним снятие override становится ОШИБКОЙ СБОРКИ. Актуальность генерации гейчена дважды: `sqlc diff` в `make check` и `pgstore.TestEveryGeneratedQueryMatchesItsSourceFile` в батарее, который работает и без установленного sqlc |
|
||||
| Кодоген SQL | `sqlc` **v1.31.1** | 22.04.2026 | **Инструмент разработчика, НЕ зависимость модуля** — в `go.mod` не входит, рантайм-граф не растёт ни на один пакет; пин держит `make tools-check`, как у линтера, и по более острой причине: генерённый код лежит В ДЕРЕВЕ, поэтому другая версия молча даёт другой диф и `sqlc diff` краснеет на чистом клоне. **Зачем:** генерирует слой запросов свободного от склейки блока `internal/pgstore` (50 запросов из `queries/*.sql`) — SQL и его `Scan` перестают быть двумя списками, которые сверяет человек ПО ПОЗИЦИИ. Взят по решению владельца 20.08 (`D39.153` п.6а, уточнение `D39.154`), носитель — `BACKLOG.md` П-19. ⚠ Довод «гейт `sqlgate_test.go` это уже закрыл» ПРОВЕРЕН и не подтвердился: гейт получает только СТРОКУ SQL и Go-сторону вызова не видит — шесть посаженных перестановок целей `Scan` и сломанных арностей ВЫЖИЛИ на полной батарее (замер 29.08, `platform-PROGRESS.md`). Схему читает ТОТ ЖЕ каталог `migrations/`, что гейтит манифест (второго носителя нет); деньги держит подстановочный override `*.*_micro_usd` → `money.MicroUSD` — без него генератор выдаёт голый `int64`, с ним снятие override становится ОШИБКОЙ СБОРКИ. Актуальность генерации гейчена дважды: `sqlc diff` в `make check` и `pgstore.TestEveryGeneratedQueryMatchesItsSourceFile` в батарее, который работает и без установленного sqlc |
|
||||
| Уязвимости | `govulncheck` **v1.6.0** | 09.07.2026 | Отдельная цель `make vuln`, не часть `check`: ей нужна сеть, а батарея обязана быть зелёной на голом клоне офлайн |
|
||||
|
||||
**Redis нет** — архитектурное «нет» в силе, с доводом: иначе он приползёт по частям. Дом решения —
|
||||
|
|
@ -61,7 +61,7 @@
|
|||
изменить строку в манифесте, где это видно ревьюеру. Апгрейд со старого релиза проверен
|
||||
исполнением (`TestDatabaseAtAnOlderReleaseCatchesUp`), down-путь — тоже.
|
||||
|
||||
> ⚠ **Цена правила, названная честно: откат НИЖЕ версии 5 недоступен.** Down-путь `00005`
|
||||
> ⚠ **Цена правила, названная честно: откат НИЖЕ версии 15 недоступен, а ниже 5 — тем более.** Down-путь `00005`
|
||||
> восстанавливает `users_email_key` и `email NOT NULL` — ровно то, что его же up-путь снял, — а
|
||||
> обе эти формы нарушаются строками, которые пишет боевой код: `email = NULL` у неподтверждённой
|
||||
> личности и один подтверждённый адрес на двух аккаунтах (прямое следствие «почта не ключ»).
|
||||
|
|
@ -69,13 +69,23 @@
|
|||
> а новая миграция чужой down-текст не заменяет. Данные при этом целы: down транзакционный,
|
||||
> `Up()` возвращает схему на текущую версию (проверено прогоном: `DownTo(4)` падает на
|
||||
> `users_email_key`, SQLSTATE 23505). Принято как цена правила.
|
||||
>
|
||||
> ⚠ **ГРАНИЦА УЕХАЛА С 5 НА 15 ТЕМ ЖЕ МЕХАНИЗМОМ, и до 04.09 доки этого не знали.** Down-путь
|
||||
> `00015` сужает `runs_paused_reason_check` обратно к одному `credit_exhausted`, а его же
|
||||
> up-путь легализовал `daily_ceiling` и `ceiling_unknown` — оба пишет боевой код через
|
||||
> `internal/pgstore/books.go` `CeilingPause`. ⚠ **Пол зависит от ДАННЫХ, а не от схемы:**
|
||||
> `DownTo(<15)` падает только там, где такая пауза случалась, — поэтому тестовая база,
|
||||
> в которой её не было, честно катится до 5 (`internal/pgstore/pg_test.go`), и зелёный тест
|
||||
> НЕ опровергает границу. Носитель — открытый ряд `PD-218`.
|
||||
9. **Ключ личности — `(provider, subject)`; почта не ключ.** `users.email` стала NULLABLE и БЕЗ
|
||||
уникального индекса; неизвестная пара всегда создаёт НОВЫЙ аккаунт. Разбор и цена решения —
|
||||
`platform/docs/archive/platform-PROGRESS-P0-P3.md:496`=`### Политика коллизии почты` (раздел закрыт
|
||||
вместе с эрой P0–P3, в живом журнале его нет).
|
||||
10. **Админ-поверхность — CLI (`tmplatformctl`), не HTTP-ручка.** Ручке понадобилась бы вторая
|
||||
модель авторизации (роли, эскалация, отзыв админской куки) ради пяти операций
|
||||
(`grant` · `adjust` · `balance` · `logins` · `revoke`), тогда как
|
||||
модель авторизации (роли, эскалация, отзыв админской куки) ради ПЯТИ операций, какими они были на дату решения
|
||||
(`grant` · `adjust` · `balance` · `logins` · `revoke`); на 04.09 их **двенадцать** — прибавились
|
||||
`book add` · `book refresh` · `books` · `runs` · `run abandon` · `run unquarantine` · `seed`,
|
||||
то есть довод не устарел, а усилился. Тогда как
|
||||
граница доверия «есть шелл на машине и доступ к DSN» уже обеспечена машиной. Браузерная панель,
|
||||
если понадобится, обернёт те же вызовы стора.
|
||||
10а. **Имя провайдера — `TM_PLATFORM_OIDC_PROVIDER`, и оно должно меняться ВМЕСТЕ с издателем.**
|
||||
|
|
@ -220,7 +230,7 @@
|
|||
|
||||
## Что решено дофиксом P4 (09.08)
|
||||
|
||||
21. **`--ceiling-usd` — это КНИЖНЫЙ потолок в силе, а не бюджет прогона, и пересчёт лежит на платформе.** Флаг переопределяет `ceilings.book_usd` и сравнивается с накопленным `committed + reserved` книги на КАЖДОЙ резервации (`backend/internal/store/ledger.go` `Reserve`; сам флаг документирует это словами «not this run's increment»). Пользователь же покупает ПРИРОСТ в главах (D39.110), поэтому платформа обязана перевести одно в другое — эта обязанность и ратифицирована D39.122. Формула зоны: **`аргумент = committed + прирост×ставка`**. ⚠ Слагаемого `reserved` в ней нет, вопреки букве пинга ратификации, и это НАЗВАННОЕ отклонение в консервативную сторону: `store.Open` (путь записи каждого `translate`) обнуляет остаточный `reserved_usd` книги ДО первой судимой резервации (`backend/internal/store/store.go:88`, `:214`), а read-only `status` этот проход не делает — значит прочитанная цифра к моменту сравнения уже стёрта, и её добавление отдало бы прогону запас БОЛЬШЕ его холда. Разбор — PD-158; **ратифицировано 09.08** (оркестратор пере-мерил обе формулы против гейта движка: ратифицированная переплачивала запасом ровно на leftover-reserved). Обе величины читаются ОДНИМ вызовом `status --json` перед стартом — они должны быть согласованы между собой, а второй вызов стоит секунды CPU на пере-нарезку.
|
||||
21. **`--ceiling-usd` — это КНИЖНЫЙ потолок в силе, а не бюджет прогона, и пересчёт лежит на платформе.** Флаг переопределяет `ceilings.book_usd` и сравнивается с накопленным `committed + reserved` книги на КАЖДОЙ резервации (`backend/internal/store/ledger.go` `Reserve`; сам флаг документирует это словами «not this run's increment»). Пользователь же покупает ПРИРОСТ в главах (D39.110), поэтому платформа обязана перевести одно в другое — эта обязанность и ратифицирована D39.122. Формула зоны: **`аргумент = committed + прирост×ставка`**. ⚠ Слагаемого `reserved` в ней нет, вопреки букве пинга ратификации, и это НАЗВАННОЕ отклонение в консервативную сторону: `store.Open` (путь записи каждого `translate`) обнуляет остаточный `reserved_usd` книги ДО первой судимой резервации (`backend/internal/store/store.go:110`, `:278`), а read-only `status` этот проход не делает — значит прочитанная цифра к моменту сравнения уже стёрта, и её добавление отдало бы прогону запас БОЛЬШЕ его холда. Разбор — PD-158; **ратифицировано 09.08** (оркестратор пере-мерил обе формулы против гейта движка: ратифицированная переплачивала запасом ровно на leftover-reserved). Обе величины читаются ОДНИМ вызовом `status --json` перед стартом — они должны быть согласованы между собой, а второй вызов стоит секунды CPU на пере-нарезку.
|
||||
|
||||
Три следствия, каждое построено: `reserved_usd` лежит в аллоулисте УКАЗАТЕЛЕМ и обязателен (отсутствие ≠ ноль) — он не входит в потолок, но именно он доказывает, что отклонение безопасно: на спавне другого писателя нет, значит любой резерв по построению остаток; рестарт считает по СВЕЖЕМУ отсчёту, потому что прерванная попытка счётчик сдвинула; фактически ушедшее значение хранится (`run_attempts.ceiling_arg_micro_usd`) — после сдвига счётчика его нечем восстановить, а «какой лимит был у того процесса» это первый вопрос к прогону, вставшему рано.
|
||||
|
||||
|
|
@ -230,7 +240,7 @@
|
|||
|
||||
Порядок утверждается ПРЯМЫМ пином, а не конкурентным прогоном: тест держит замок книги, дожидается, пока операция реально заблокируется, и проверяет строку попытки через `for update nowait`. Конкурентная проба оставлена, но она пробует — посадка «снять книгу-первой из `RestartRun`» её пережила, потому что рестарт берёт замок один раз за прогон.
|
||||
|
||||
23. **«Ошибка материализации» — два разных факта, и различает их `runs.quarantines`.** Транзиентное — повтор следующим свипом; сюда входят не только дедлок и сериализация (`40P01`/`40001`), но и всё, во что превращается ШТАТНЫЙ рестарт управляемого Postgres: класс 08, `57P01`/`57P02`/`57P03`, `pgconn.SafeToRetry`, любой `net.Error`. ⚠ Снятия карантина в дереве нет, поэтому ошибочный карантин необратим и ослепляет проекцию живого платного прогона навсегда. Граница держится на типах: ошибка чтения ФАЙЛА — `*fs.PathError`, а он `net.Error` не удовлетворяет; пропасть, конфликт payload, битая строка — карантин ПОПЫТКИ (её проекции), прогон при этом продолжается и продолжает платить.
|
||||
23. **«Ошибка материализации» — два разных факта, и различает их `runs.quarantines`.** Транзиентное — повтор следующим свипом; сюда входят не только дедлок и сериализация (`40P01`/`40001`), но и всё, во что превращается ШТАТНЫЙ рестарт управляемого Postgres: класс 08, `57P01`/`57P02`/`57P03`, `pgconn.SafeToRetry`, любой `net.Error`. ⚠ Пока карантин стоит, проекция живого ПЛАТНОГО прогона слепа, а прогон идёт и тратит; **снятие построено паком P13** (`PD-426`, `fixed(6ae3e76)`) — `tmplatformctl run unquarantine --run <id>` (`internal/pgstore/runs.go` `Unquarantine`) чистит `quarantine_reason` ЖИВОЙ попытки и НЕ трогает курсор: те же нечитаемые байты следующий свип карантинит снова с той же причиной, и это честный ответ, а не сбой команды. Три отказа своими словами: нет такого прогона · у прогона нет живой попытки · попытка не в карантине. Пины — `TestLiftingAQuarantineClearsItAndSaysWhatItWas` и `TestALiftedQuarantineMaterializesTheJournalAgainFromTheCursor`; рантбук — `deploy/README.md` §«Застрявшая работа». Граница держится на типах: ошибка чтения ФАЙЛА — `*fs.PathError`, а он `net.Error` не удовлетворяет; пропасть, конфликт payload, битая строка — карантин ПОПЫТКИ (её проекции), прогон при этом продолжается и продолжает платить.
|
||||
|
||||
## Что решено сессией P5 (11.08) — загрузка книги, стоп/резюм, наблюдаемость
|
||||
|
||||
|
|
@ -388,8 +398,8 @@
|
|||
| `<project_db>.bank-stop.txt` | `pipeline/mining.go` `os.WriteFile` (греп `bankStopTablePath`) | **НЕ атомарно** (усечение первым делом) | читателя нет | ✅ **и это важное правило, а не наблюдение: брать его на ЖИВОМ прогоне нельзя** — прочитаешь обрезанный файл без всякой ошибки |
|
||||
| `<project_db>.mined-signature.yaml` | `pipeline/mining.go` `writeFileAtomic` (греп `signatureMapPath`) | **атомарно** | читателя нет | ✅ ⚠ испр. 02.09: писатель сменился на атомарный, прежняя строка «`os.WriteFile`, НЕ атомарно» была протухшей |
|
||||
| `<project_db>.auto-bank.yaml` | `pipeline/mining.go` `writeFileAtomic` (греп `autoBankPath`) | **атомарно** | читателя нет | ✅ то же исправление |
|
||||
| `mined_delta` (путь из `book.yaml`) | **писателя в движке НЕТ** — только читатель `loadMinedDelta`; формат `seed.File` (`terms:`), грузится `membank.LoadGlossarySeed`, `Source` пере-штампуется на `"mined"` | — | **писателя НЕТ и у платформы** | ❌ **разрыв — это строка 199(а) единого бэклога**, развилка ждёт ратификации |
|
||||
| `mined_rejects` | читатель `loadMinedRejects`; формат `rejects: [{src, note}]` — это ФИЛЬТР ПРЕДЛОЖЕНИЙ, в банк не входит | — | писателя нет | ❌ тот же разрыв |
|
||||
| `<book_id>.mined-delta.yaml` — путь **ДЕРИВИРОВАННЫЙ**, в `book.yaml` не объявляется (`backend/internal/config/book.go` `MinedDelta string \`yaml:"-"\``, «DERIVED, never read from the file») | **писатель В ДВИЖКЕ ЕСТЬ:** `pipeline/bankdecisions.go` `writeDecisionFiles` (глагол `tmctl bank-apply`); читатель `loadMinedDelta`; формат `seed.File` (`terms:`), грузится `membank.LoadGlossarySeed`, `Source` пере-штампуется на `"mined"` | **атомарно** (стейдж + переименование, каталог книги синкается) | платформа их НЕ пишет и писать не должна — `pipeline/status.go`: «the engine remains their only writer»; её половина канала — документ решений, строкой ниже | ✅ **разрыв ЗАКРЫТ** (D39.156 п.3 · D39.166). ⚠ Ключи `mined_delta:`/`mined_rejects:` в `book.yaml` **РЕТАЙРНУТЫ**: непустое значение — жёсткая ошибка загрузки конфига (`config/book.go`, `RetiredMinedDelta`), то есть ключ в шаблоне платформы уронил бы КАЖДУЮ новую книгу |
|
||||
| `<book_id>.mined-rejects.yaml` — путь деривируется так же (`MinedRejects string \`yaml:"-"\``) | писатель тот же (`writeDecisionFiles`); читатель `loadMinedRejects`; формат `rejects: [{src, note}]` — ФИЛЬТР ПРЕДЛОЖЕНИЙ, в банк не входит | **атомарно**, переименовывается ПЕРВЫМ | платформа не пишет (тот же запрет) | ✅ то же: закрыт D39.156 п.3 / D39.166, ключ ретайрнут |
|
||||
| `tmctl bank-apply` + документ решений | зона ПИШЕТ (`internal/runs/bank.go` `decisionsFile` → файл во временном каталоге), движок отвечает отчётом `tm-bank-report-v1` на stdout | документ пишется целиком до вызова; отчёт — одноразовая выдача на вызов | `internal/runner/bankapply.go` → `ingest.DecodeBankReport` → `internal/runs/bank.go` `bankVerdict` | ⚠ ЕДИНСТВЕННЫЙ канал, по которому платформа ПИШЕТ в проект движка, — отсюда и своя дверь (`POST /books/{bookId}/bank/corrections`), и свой класс отказов, и класс `write_incomplete` = exit 15. ⚠ Пост-verb факт этого канала (`bank_moved_at`) пишется на ОТДЕЛЬНОМ, отцепленном контексте — иначе обрыв клиента теряет его навсегда (`PD-425`) |
|
||||
| `tmctl build` + сайдкар `<project_db>.book.<fmt>` | движок (`pipeline`, пак «писатель книги», D39.175) | файл пишется целиком до публикации пути; ПУТИ публикуются в `StatusArtifacts.book_files` (`status --json` / `manifest --json`), stdout — конверт `tm-build-v1` | `internal/runner/build.go` (`BuildArgs`/`Build`) → `ingest.DecodeBuild` → `internal/exports` (04.09, пак «закрыть цикл») | ✅ **Читатель есть с 04.09, и он СТРОИТ, а не подбирает** (D39.175 п.2). Три правила канала, каждое куплено кодом движка: **(1) `--out` обязателен.** Без него `build` пишет рядом с БД и УДАЛЯЕТ форматы, о которых не просили (`pipeline/bookbuild.go`, цикл `RemovedFiles`) — то есть экспорт `txt` снёс бы операторский `epub`, а два экспорта одной книги затирали бы артефакт друг друга. С `--out` уборки нет вовсе, каждый экспорт — свой неизменяемый файл, и TTL с GC становятся платформенными. **(2) `--partial` обязателен** — дверь ВСЕГДА строит (D39.178 п.1), и exit **16** через неё недостижим по построению: увидели 16 — значит флаг не доехал, это дефект нашей проводки, а не книга с дырами. **(3) `--keys-file` НЕ передаётся**: глагол $0 и без ключей, движок отказывает во флаге на всём, кроме `translate` (D20.4). ⚠ Ловушка exit **11** УЧТЕНА, а не унаследована: у `build` он значит «в книге ноль выходных юнитов», и дверь кладёт его в свой код `book_empty`, не приближаясь к словарю интейка, который на 11 УДАЛЯЕТ загрузку. `BuildReport` сверяется и уходит ОПЕРАТОРУ в лог (`config_drift`/`stale_unknown`/удалённые копии), на провод не идёт |
|
||||
| `tmctl export --json --pairs` | движок (`pipeline/export.go`) | — (одноразовая выдача на вызов) | `internal/runner/engine.go` `ExportArgs`/`Export` → `ingest.DecodeExport` → `internal/readmodel` | ✅ ⚠ **Единственный канал, несущий ТЕКСТ пары** — исходник и перевод; манифест несёт только структуру |
|
||||
|
|
@ -466,7 +476,7 @@ system_messages not found in type config.CapabilitiesConfig`. Диагноз с
|
|||
состояние `cgroup.subtree_control` среза `tm-runs.slice` в момент прогона — назван КАНДИДАТОМ и
|
||||
только: диагноз `PD-423` не установлен, и вносить его в рецепт как проверку нельзя.
|
||||
|
||||
Ожидание при всех четырёх: 18 пакетов, exit 0, **скипов 0**, линтер «0 issues». Замерено 29.08: с гейтами — 0 скипов на обоих деревьях; без них — exit 0 и **287 скипов на HEAD
|
||||
Ожидание при всех четырёх: 19 пакетов, exit 0, **скипов 0**, линтер «0 issues». Замерено 29.08: с гейтами — 0 скипов на обоих деревьях; без них — exit 0 и **287 скипов на HEAD
|
||||
`fbe6cf3`**, **304 на дереве пака P11** (пак добавил 17 пинов, гейченных тем же DSN). Число зависит
|
||||
от дерева, и переносить его между ними нельзя.
|
||||
|
||||
|
|
@ -578,10 +588,16 @@ KEY=VALUE; едет движку АРГУМЕНТОМ `--keys-file` на `transl
|
|||
⚠ Все они печатаются на старте с источником (`default`/`environment`/`file`) — PD-114; секреты и
|
||||
денежные суммы печатаются фактом наличия, без значения.
|
||||
|
||||
Админ-команды: `tmplatformctl grant --user <id> --usd 5 [--note ...] [--key ...]` ·
|
||||
`balance --user <id>` · `logins --user <id>` · `revoke --user <id>` · `books [--migratable]`
|
||||
(какие книги безопасно мигрировать при апгрейде движка — `deploy/README.md`) · `seed` (дев-стенд:
|
||||
аккаунт, кредит и книга через ЖИВОЙ интейк, §31).
|
||||
Админ-команды (список сверен с `usage` самого бинаря, `cmd/tmplatformctl/main.go:44-62`, — прежняя
|
||||
редакция была короче на шесть): `tmplatformctl grant --user <id> --usd <amount> [--note ...] [--key ...]` ·
|
||||
`adjust --user <id> --usd <amount> --note <text> [--key ...]` (КОРРЕКЦИЯ баланса второй строкой леджера, со знаком; деньги кладёт `grant` — строки леджера не редактируются, `cmd/tmplatformctl/main.go:135,160`) · `balance --user <id>` · `logins --user <id> [--limit <n>]` ·
|
||||
`revoke --user <id>` · `book add` (дев-интейк) · `book refresh --book <id>` (попросить читающую
|
||||
поверхность заново у книги, на которой её бросили) · `books [--migratable] [--abandoned]` (какие книги
|
||||
безопасно мигрировать при апгрейде движка, какие потеряли поверхность — `deploy/README.md`) ·
|
||||
`runs [--stalled]` · `run abandon --run <id> --reason <text> [--release-hold]` (терминальный вердикт
|
||||
оператора застрявшему прогону) · `run unquarantine --run <id>` (материализовать журнал карантинной
|
||||
попытки заново) · `seed` (дев-стенд: аккаунт, кредит и книга через ЖИВОЙ интейк, §31).
|
||||
⚠ `exit-marker <path> <unit>` в этот список не входит: её зовёт systemd как `ExecStopPost`, не оператор.
|
||||
|
||||
⚠⚠ **УСЛОВИЯ СТЕНДА КЛЮЧУЮТСЯ ПОЛЬЗОВАТЕЛЕМ И `$HOME`, А НЕ ИМЕНЕМ МАШИНЫ.** Замерено 04.09: две
|
||||
смены на ОДНОМ `hostname` (`DESKTOP-IN1MCEA`) видят противоположные условия — у одного пользователя
|
||||
|
|
|
|||
|
|
@ -1,7 +1,88 @@
|
|||
# Журнал зоны «Платформа»
|
||||
|
||||
## НАХОДКА В ЧУЖОЙ ЗОНЕ, ЖИВШАЯ ТОЛЬКО В ПЕРЕПИСКЕ — зеркало контракта у фронта (04.09, `textmachine-main-34`)
|
||||
|
||||
⚠ **Пишу сюда, потому что переписка между сессиями умирает вместе с сессиями, а канон велит долговечному знанию жить в репозитории.** Находка сделана по вопросу оркестратора «не построен ли фронт под старую версию контракта», передана ему сообщением — и до этой записи существовала ТОЛЬКО там.
|
||||
|
||||
**Фронт держит СВОЮ копию контракта:** `frontend/docs/api-contract/openapi.yaml`, `version: 0.2.3`, файл последний раз двигали лендингом `267aa35` от 30.08. Канон сегодня — `0.10.0`. Отставание **восемь миноров**.
|
||||
|
||||
⚠ **Хуже отставания то, что их гейт его не видит и увидеть не может:** `frontend/src/api/contract.test.ts` читает `${cwd}/docs/api-contract/openapi.yaml` — **своё же зеркало**, а не канон. Тест «версия фикстуры = версия спеки» сверяет копию с копией и остаётся зелёным при любом отставании; соседний тест сверяет только МАЖОР, а он у `0.2.3` и `0.10.0` одинаковый — ноль. Их собственный комментарий это признаёт: «It drifted a whole session unnoticed, because the client compares the MAJOR».
|
||||
|
||||
**Сравнение, ради которого запись и стоит в ЭТОМ журнале:** гейт платформы (`internal/gates/contract_test.go`) читает КАНОН с диска и поймал расхождение `0.9.0 → 0.10.0` в тот же час, когда оно возникло. Один класс гейта, разный источник истины — разница между «поймано за час» и «не поймано за восемь миноров». **Гейт, читающий свою копию факта, доказывает лишь, что копия согласна с копией.**
|
||||
|
||||
⚠ **Что при этом НЕ подтвердилось** (и это важно, потому что на гипотезе строился поиск): версия `0.2.3` уже описывала потолок в ГЛАВАХ — `CeilingBounds`: «Bounds of the run-ceiling scale, in CHAPTERS. The chapters → money conversion lives on the platform and is not exposed here in any form». Значит память владельца о денежном ползунке зеркалом фронта НЕ объясняется: искать надо раньше `0.2.3` и вне контрактных носителей.
|
||||
|
||||
**Зону фронта не трогала и в их журнал не писала** (`frontend/docs/frontend-PROGRESS.md` — их зона). Передано оркестратору; рекомендация — перенацелить их гейт версии на канон, иначе следующая смена единицы уедет к ним тем же способом, молча.
|
||||
|
||||
## ОТВЕТ НА ПИНГ №22 — ревизия отработана, и ЧЕТЫРЕ ЕЁ ПУНКТА ОКАЗАЛИСЬ ЧАСТИЧНО НЕВЕРНЫ (04.09, `textmachine-main-34`)
|
||||
|
||||
⚠ **Ни одно утверждение пинга не принято на веру.** Канон велит грунтовать выводы `file:line` и верифицировать спорное адверсариально, а пинг — заявления другой сессии. Каждый из 11 пунктов проверен отдельным агентом, которому задача ставилась **опровергать**, а не подтверждать. Итог: **7 подтверждено, 4 частично** — и в «частичных» есть прямо опровергнутое.
|
||||
|
||||
### ⛔ ЧТО В САМОМ ПИНГЕ ОКАЗАЛОСЬ НЕВЕРНЫМ — это важнее списка починок
|
||||
|
||||
1. **`PD-439` «семь строк вне словаря → две» — ОПРОВЕРГНУТО, ряд НЕ тронут.** Пере-счёт всех 447 строк регистра против словаря шапки дал ровно **семь** нарушителей, и это ровно те семь ID, которые ряд называет. Правка сделала бы регистр ЛОЖНЫМ. Это единственный пункт пинга, исполнение которого ухудшило бы дерево.
|
||||
2. **«Писатель `mined_delta` есть с обеих сторон» — неверно для платформы.** Движок — единственный писатель по замыслу (`pipeline/status.go`: «the engine remains their only writer»), платформа пишет только документ решений в свой каталог. В таблицу это ушло в исправленном виде.
|
||||
3. **«Каталог экспортов вне песочницы даёт каждому экспорту `deployment_error`» — неверный симптом.** Каталог создаёт САМ ДЕМОН на буте (`cmd/tmplatformd/runner.go:208`), и невозможность создать — **отказ старта демона**, а не отказ экспорта; если каталог есть, но только на чтение, падает подкаталог книги и код отказа `build_failed` (`internal/exports/exports.go:358`). Записан верный симптом, а не тот, что в пинге.
|
||||
4. **Пункты 10(b) и 10(c) НЕ написаны сознательно:** обе формулировки внесли бы в доки ложь. Половина (b) уже покрыта исправлением пункта 5.
|
||||
5. **Два моих же верификатора разошлись про `STATE_DIR`** — один утверждал, что дефолт вне `ReadWritePaths=`, другой что он и есть `StateDirectory=`. Разрешено чтением юнита: прав второй (`StateDirectory=tmplatform` → `/var/lib/tmplatform`, systemd создаёт и делает записываемым). Довод «зона поймала бы это сама» тут не работает — ровно это ложное объяснение и стояло в рантбуке полтора месяца.
|
||||
|
||||
### ⛔ ВТОРОЙ КРУГ: САМОПРОВЕРКА НАШЛА ОШИБКИ В МОЕЙ ЖЕ ПОЧИНКЕ — ни одного чистого файла из восьми
|
||||
|
||||
⚠ **Это главный результат ревизии, и он про метод, а не про доки.** Закончив правки, я пустила по СВОЕЙ готовой работе восемь агентов — по одному на изменённый файл, с задачей искать (1) ложь, ВНЕСЁННУЮ правкой, и (2) носители, которые правка сделала протухшими. **Чистых файлов не оказалось ни одного.**
|
||||
|
||||
**Самое опасное: я заменила одну ложь ПРОТИВОПОЛОЖНОЙ.** Юнит утверждал, что в его cgroup лежат все прогоны (до-`D39.106` посылка). Я написала «control plane and nothing else» — и это тоже неверно: **четыре движковых вызова идут ПРЯМЫМИ ДЕТЬМИ демона** и в его cgroup лежат — `manifest`/`status` (`internal/runner/engine.go:201`), сборка экспорта (`build.go:88`), `bank-apply` (`bankapply.go:73`) и сам `systemd-run` (`runner.go:107`). Транзиентный юнит — только `translate`. В юните теперь стоит проверенная формулировка и НАЗВАНЫ обе прежние ошибки, потому что читателю через полгода важно знать, что маятник качался дважды.
|
||||
|
||||
**Остальное, найденное по себе:**
|
||||
· `TimeoutStopSec=90` я объяснила через `stopGrace = 10 минут` — но это `TimeoutStopSec` ТРАНЗИЕНТНОГО юнита прогона (`runner.go:166`), а не грация, которую соблюдает демон. И тот же снятый довод дословно лежал ВТОРЫМ носителем в рантбуке — не заметила.
|
||||
· **Три ссылки на номера строк, которые сдвинул мой же дифф:** «строка 58», `tmplatformd.service:67`, и позже ещё четыре токенных якоря. Правя чужие уехавшие якоря, я наплодила своих. Лечение — не аккуратность, а **форма**: адрес пишется с токеном (`путь:строка`=`токен`), тогда гейт его проверяет; без токена он молча врёт.
|
||||
· `adjust` я описала как «пополнение» — он **коррекция** со знаком, деньги кладёт `grant` (`cmd/tmplatformctl/main.go:135,160`).
|
||||
· «`Ready` сверяет лишь `goose_db_version`» — проверок **две**: ещё наличие схемы очереди (`select to_regclass('river_job')`).
|
||||
· В ряду `PD-136` написала «абзац снят» — он **переписан**. Ровно та ошибка, за которую ряд и оспорен: «удалено» — утверждение о дереве. Там же «полтора месяца» пере-считано по истории в **26 дней** (`git log -S 'A RUN IS NOT A CHILD OF THIS UNIT'` → `d29e30c`, 09.08), и токен оспаривания приведён к грепаемой форме `ОСПОРЕНО(PD-136)`.
|
||||
· Пере-нацелила ПОЛОВИНУ пары якорей: `store.go:88→:110` сделала, `:214→:278` в §21 забыла — и те же три мёртвых адреса нашлись в ПРОД-комментарии `internal/runs/spawn.go`.
|
||||
· `PD-157` пере-нацелила на `:331` — литералы `book_usd/day_usd` на `:332`; промах на строку.
|
||||
· Добавила второй пункт `ENGINE_KEYS_PATH`, не убрав старый: список «четвёрки» стал из шести пунктов про пять переменных.
|
||||
· Мой же указатель в `README.md` («отчёт последнего пака первый сверху») стал ложным от МОИХ ЖЕ добавлений в верх журнала.
|
||||
· Переименование раздела осиротило `PD-71`, который звал его старым именем; правки в рантбуке осиротили якорь `PD-274`; §10 продолжал считать пять админ-операций при двенадцати.
|
||||
|
||||
⚠ **Вывод, ради которого этот раздел и написан: правка документации — это тоже изменение с побочными эффектами, и она нуждается в ревью исполнением ровно так же, как код.** Восемь агентов по своей работе нашли больше, чем одиннадцать по чужой. Мандат самопроверки в промтах зон (`CLAUDE.md`) сформулирован для кода и запросов к моделям — этот замер говорит, что он нужен и для доков.
|
||||
|
||||
⚠ **Носители ВНЕ зоны, которые эта волна сделала протухшими, я не трогала** — чужая зона, передано оркестратору пингом: `docs/architecture/14-api-contract/README.md` (строки про «15 операций из 20», «на двух создающих вызовах», открытую половину двери выдачи) и `docs/PROGRESS.md:6`. ✅ **ДВА из трёх закрыты им же в тот же день, коммит `9e103d9`** (две строки: 999 и 1008) — живых вхождений «15 операций из 20» и «на двух создающих вызовах» в `docs/` не осталось. ⚠ **Третий НЕ закрыт:** `docs/architecture/14-api-contract/README.md:1005` по-прежнему говорит «открыта только дверь платформы», хотя она построена лендингом `adf5e53`; и `docs/PROGRESS.md:6` держит цифры зеркала фронта «СЕМЬ миноров, `0.2.3` против `0.9.0`» при каноне `0.10.0` и восьми минорах. Оба остаются на оркестраторе. ⚠ Первая редакция этой строки сказала «закрыты» про все три — обобщение по двум грепнутым фразам. ⚠ Строку правлю сама, потому что без этой отметки она бы утверждала о чужой зоне то, что там уже неверно, — тот же класс, за который эта же ревизия и затевалась.
|
||||
|
||||
### ⛔ ТРЕТИЙ КРУГ: аудит ДВУХ файлов, которые второй круг не покрыл, нашёл ещё шесть моих ошибок
|
||||
|
||||
⚠ **Второй круг я пустила по восьми файлам и НЕ включила в него самый крупный — этот журнал.** Пробел закрыт отдельным проходом, и он окупился: шесть внесённых мной ошибок, из них две — повторение того же класса, который эта ревизия и разбирает.
|
||||
|
||||
1. **«Семь якорей пере-нацелены по проверенным целям» — проверка была верна и протухла от МОИХ ЖЕ дальнейших правок тех же файлов.** Я снял якоря автоматически по токену, убедился, что красных ноль, а потом ещё трижды правил `deploy/README.md` — и три якоря снова уехали. **Утверждение о проверке живёт ровно до следующей правки; честно только то, что проверено ПОСЛЕДНИМ действием.** Формулировка снята, якоря пере-сняты циклом «чинить, пока линтер не чист».
|
||||
2. **В пометке ряда `Д3` я поменял два адреса местами:** `const base, cap` стоит на `reconcile.go:424` (это первый якорь самого ряда), а `stoppedOnRequest` объявлена на `:1136` и зовётся на `:1005`. Написал ровно наоборот.
|
||||
3. **Дату лендинга зеркала фронта взял из текста коммита, а не из истории:** `267aa35` — **15.08**, не 30.08. Ошибка занижала возраст расхождения на две недели, то есть работала против собственного вывода.
|
||||
4. **«Носители вне зоны закрыты» — закрыты ДВА из трёх.** Обобщил по двум грепнутым фразам; третий (`14-api-contract/README.md:1005`, «открыта только дверь платформы») и цифры зеркала в `docs/PROGRESS.md:6` живы.
|
||||
5. **Ложь «отчёт последнего пака стоит первым» я исправил в `README.md` и оставил во втором носителе** — в примечании к переименованному заголовку, то есть ровно там, куда идёт сбитый онбордингом читатель.
|
||||
6. **«Линтер якорей при всём этом давал НОЛЬ»** — верно про дерево ДО волны; красным он стал от неё самой.
|
||||
|
||||
⚠ **Счёт кругов, ради которого раздел и стоит: одиннадцать агентов по ЧУЖОЙ работе нашли 11 пунктов, восемь по СВОЕЙ — ещё около двадцати, два по оставшимся файлам — ещё шесть.** Плотность находок на своей работе оказалась не ниже, чем на чужой. Это довод не в пользу большего числа агентов, а в пользу того, что **правка доков без ревью исполнением — такой же непроверенный код**.
|
||||
|
||||
### ЧТО ИСПРАВЛЕНО
|
||||
|
||||
· **Блокер деплоя (п.1):** `TM_PLATFORM_CTL_BIN` указывал на `/opt/textmachine/bin/`, которого рантбук НЕ создаёт ⇒ `os.Stat` не находит ⇒ `MarkerArgv` пуст ⇒ **каждый старт и резюм отвечает `503`**. Значение приведено к пути, куда рантбук ставит бинарь. ⚠ И снято ложное объяснение: пустая переменная — НЕ отказ (сиблинг-дефолт), отказывает НЕВЕРНЫЙ путь.
|
||||
· **Сверх-утверждение «без этих четырёх» (п.6):** из четырёх прогоны гейтит ОДНА (`ENGINE_BIN`); у `CTL_BIN` и `STATE_DIR` рабочие дефолты, `ENGINE_KEYS_PATH` даёт WARN и роняет первый ПЛАТНЫЙ вызов. Каждая из четырёх описана тем, что ломает именно она.
|
||||
· **Linger:** «без него не стартует НИ ОДИН прогон» снято как ложное для стенда — замер зоны H2 дал `Linger=no` при шести стартовавших прогонах; для боевого деплоя требование осталось.
|
||||
· **Инвентарь каналов (п.2):** путь `mined-delta` **деривируется**, ключи в `book.yaml` **ретайрнуты** (ключ в шаблоне уронил бы КАЖДУЮ новую книгу), писатель в движке есть. Две строки таблицы переписаны.
|
||||
· **§23 карантин (п.3):** «снятия нет, карантин необратим» — ручка построена паком P13 (`PD-426`), названа с командой, поведением и двумя пинами.
|
||||
· **Пол отката (п.4):** 5 → **15**, в трёх носителях (рантбук, §8, комментарий `pg_test.go`). ⚠ И названо то, чего не было ни в пинге, ни в доках: **пол зависит от ДАННЫХ, а не от схемы** — тестовая база честно катится до 5, и зелёный тест границу НЕ опровергает.
|
||||
· **Юнит (п.5):** снято обоснование `TimeoutStopSec=90` «грацией движка 30 с» (реально `stopGrace = 10 минут`, и прогон юниту не ребёнок) и снята до-`D39.106` посылка про cgroup, спорившая с шапкой ТОГО ЖЕ файла и с последним абзацем ТОГО ЖЕ блока.
|
||||
· **`PD-136` оспорен собственной проверкой:** ряд стоял `fixed` с формулировкой «снятые абзацы удалены», а абзац был на месте полтора месяца. Статус не меняю (норма шапки), пометка стоит. **Урок ряда: «удалено» в закрывающей формулировке — утверждение о ДЕРЕВЕ, и его проверяют грепом, а не памятью автора.**
|
||||
· **Открытое/закрытое (п.7):** `П-21` держал развилку, разрешённую кодом (`PD-369`, пак P12) · `П-17` говорил, что `operationId` третьему адресу не дан — дан минором `0.10.0` · `PD-447` со статусом `fixed(adf5e53)` стоял в секции «Открытые — minor» (**моя ошибка того же дня**) и перенесён в закрытую эру.
|
||||
· **Ловушка онбординга (п.8):** заголовок «Текущее состояние», лежавший на строке ~2587 из ~2851 и кончавшийся 24.08, **переименован** в «Состояние эры P8 — ИСТОРИЧЕСКИЙ раздел». Оба указателя `README.md` ведут теперь на ВЕРХ журнала. Лечил переименованием, а не предупреждением: заголовок, обещающий текущее, и есть ловушка.
|
||||
· **Числа и адреса (п.9):** `Idempotency-Key` на **трёх** создающих вызовах (и тот же счёт исправлен в коде — `internal/httpapi/idempotency.go`), карта `httpapi` знает `exports.go`, 18→19 пакетов, 40→50 запросов, список админ-команд сверен с `usage` бинаря (был короче на шесть), `20/14`→`21/18` операций **с датировкой обеих редакций**, семь якорей пере-нацелены. ⚠ **Формулировку «по проверенным целям» снимаю:** проверка была верна в момент снятия и протухла от МОИХ ЖЕ последующих правок тех же файлов. Верно только то, что проверено ПОСЛЕДНИМ действием, и оно — в строке гейтов ниже.
|
||||
· **Ряд `Д3` помечен, но НЕ пере-нацелен:** он внутри ЗАКРЫТОГО раунда ревью, и пере-указание якорей в законченном раунде переписывает чужой замер.
|
||||
· **Противоречие в этом журнале (п.11):** два абзаца спорили, погашен ли чужой стенд. Верен первый. Заодно исправлен счёт баз на кластере: их четыре, а не две.
|
||||
|
||||
⚠ **Почему пункт 1 не поймал ни один гейт и не поймает:** линтер якорей на дереве ДО этой волны давал НОЛЬ (красным он стал уже от неё самой — разбор во втором круге ниже). Всё найденное — ложь в ПРОЗЕ по верным адресам. Гейт проверяет, что цель существует и что в ней есть токен; он не проверяет, что ПРЕДЛОЖЕНИЕ вокруг адреса истинно. Это граница инструмента, а не его дефект, и закрывает её только чтение под конкретный вопрос.
|
||||
|
||||
## ⚠ ПИНГ ОРКЕСТРАТОРА №22 (04.09, вечер) — РЕВИЗИЯ ДОКУМЕНТАЦИИ ЗОНЫ ЦЕЛИКОМ
|
||||
|
||||
⚠ **Адреса ВНУТРИ этого пинга сняты ДО починки и частью уже не действительны** — текст пинга не переписываю (чужой замер), но читателю это знать надо: ряд `PD-447` уехал из «Открытых — minor» в закрытую эру, отчего каждая строка регистра ниже сдвинулась; заголовок «Текущее состояние» переименован; в `deploy/README.md` вставлено около сорока строк, и номера там больше не те. Что стало с каждым пунктом — в секции «ОТВЕТ НА ПИНГ №22» выше.
|
||||
|
||||
Внешний ревизор прочитал документацию зоны от первой строки до последней (README · BACKLOG ·
|
||||
deploy/README 561 стр. · юнит · STACK_DECISIONS 650 · этот журнал 2775 · регистр 615). Линтер якорей
|
||||
при этом ноль: **всё найденное — ложь в ПРОЗЕ, которую гейт не видит по построению.** Правит ЗОНА,
|
||||
|
|
@ -110,7 +191,7 @@ README, получает состояние двухнедельной давн
|
|||
|
||||
**Рецепт самодостаточен:** `<скретчпад сессии>/stand/UP.sh` — поднимает Postgres, если он упал, поднимает демона, дожидается `readyz` и кладёт cookie-jar. Пробы — `. <скретчпад>/stand/api.sh`, дальше `api GET /v0/capabilities`.
|
||||
|
||||
⚠ **СТЕНД ОПУЩЕН 04.09 после окончания приёмки — но опущен НАПОЛОВИНУ, и вторая половина жива НАРОЧНО.** Остановлен только демон на `8099`. ⛔ **Postgres на `55433` НЕ гасится ни при каких условиях.** На кластере ДВЕ базы: `tmstand34` (этот стенд) и `tmstand`. ⚠ **Причина запрета сменилась в тот же день и стала СИЛЬНЕЕ — пишу обе, потому что проверяемая и ложная причина хуже отсутствия причины.** Было: `tmstand` — база ЧУЖОГО ЖИВОГО стенда (демон на `8080`, `pid 1090739`, аптайм с ~31.08), и остановка кластера уронила бы чужую сессию. Стало: демона того стенда владелец велел погасить как артефакт, оркестратор погасил по PID, порт `8080` свободен — **но база осталась, и теперь она НИЧЬЯ**. Снести кластер значит снести данные, за которыми уже НЕКОМУ ПРИЙТИ, и пропажу никто не заметит. Запрет продублирован в самом `UP.sh` — читают рецепт, а не журнал.
|
||||
⚠ **СТЕНД ОПУЩЕН 04.09 после окончания приёмки — но опущен НАПОЛОВИНУ, и вторая половина жива НАРОЧНО.** Остановлен только демон на `8099`. ⛔ **Postgres на `55433` НЕ гасится ни при каких условиях.** На кластере не две базы, а четыре (`postgres`, `tmstand34`, `tmstand`, `tm_migr_probe`); значение имеют две — `tmstand34` (этот стенд) и `tmstand`. ⚠ **Причина запрета сменилась в тот же день и стала СИЛЬНЕЕ — пишу обе, потому что проверяемая и ложная причина хуже отсутствия причины.** Было: `tmstand` — база ЧУЖОГО ЖИВОГО стенда (демон на `8080`, `pid 1090739`, аптайм с ~31.08), и остановка кластера уронила бы чужую сессию. Стало: демона того стенда владелец велел погасить как артефакт, оркестратор погасил по PID, порт `8080` свободен — **но база осталась, и теперь она НИЧЬЯ**. Снести кластер значит снести данные, за которыми уже НЕКОМУ ПРИЙТИ, и пропажу никто не заметит. Запрет продублирован в самом `UP.sh` — читают рецепт, а не журнал.
|
||||
|
||||
⚠ **Данные платного прогона пережили остановку и сверены ПОСЛЕ неё** (`psql` по `tmstand34`): 2 книги · 10 глав · 14 экспортов · 5 прогонов; деньги по `credit_ledger` — `grant 0.600000`, `settlement -0.278319`, холды в ноль (`-1.512494` / `+1.512494`), **остаток `0.321681 USD`**. Списание сходится с отчётом пака до микродоллара. Поднять обратно — `UP.sh`; порт `8099` свободен.
|
||||
|
||||
|
|
@ -150,7 +231,7 @@ README, получает состояние двухнедельной давн
|
|||
|
||||
⚠ **Дефолт `8080` в коде НЕ меняется, и это решение, а не недосмотр.** Коллизия здесь — `tmplatformd` против `tmplatformd`, то есть дефолта против самого себя: любой другой номер даст ту же аварию на втором одновременном стенде, а цену смены заплатят все существующие деплои и доки, говорящие `8080`. Чинится не номер, а посылка «по адресу отвечают — значит это мой сервис». Ряд — `PD-447`, статус `fixed(дерево)`.
|
||||
|
||||
⚠ **Чужой стенд не тронут** (`pid 1090739`, база `tmstand`, аптайм с ~31.08): он не наш, а `pkill` по имени на этой машине бьёт по чужим. Вопрос владельцу о его судьбе несёт оркестратор.
|
||||
⚠ **Чужой стенд ЭТОЙ СЕССИЕЙ не тронут** (`pid 1090739`, база `tmstand`, аптайм с ~31.08): он не наш, а `pkill` по имени на этой машине бьёт по чужим; вопрос о его судьбе понёс владельцу оркестратор. ⚠ **Судьба решилась в тот же день, и абзац здесь отстал от ответа:** владелец велел погасить тот демон как артефакт, оркестратор погасил его по PID — `1090739` мёртв, порт `8080` свободен. **База `tmstand` при этом осталась и стала НИЧЬЕЙ, поэтому ⛔ запрет гасить кластер Postgres на `55433` не снят, а усилился:** за этими данными уже некому прийти, и пропажу никто не заметит. Разбор целиком — секция «СТЕНД ПЛАТНОГО ПРОГОНА» выше.
|
||||
|
||||
## НАБЛЮДЕНИЕ ЗА ЖИВЫМ ПОТОКОМ — что видела сессия, прогнавшая весь путь на настоящем движке и настоящих деньгах (заказ владельца через оркестратора №22, 04.09; сессия `textmachine-main-34`)
|
||||
|
||||
|
|
@ -387,7 +468,7 @@ run_CR2RN76NAKYKAJE5 · run_VSXPMJE53KJQQAOS
|
|||
|
||||
`grep -rn 'createExport\|getExport' internal/ --include=*.go | wc -l` → было **0**, стало **не 0**; смонтировано **18** маршрутов вместо 15.
|
||||
|
||||
**Три маршрута, а не два.** Канон называет две операции и ТРЕТИЙ адрес описывает словами (`Export.url`), `operationId` ему не давая. Он живёт в `contractSurface` с остальными — иначе не получил бы ни гарда сессии, ни капа тела, ни пина «без сессии 401».
|
||||
**Три маршрута, а не два.** ⚠ **Устарело тем же днём:** `operationId` `downloadExport` канон дал минором `0.10.0` (`58bca19`, `D39.194` п.3); маршрутов по-прежнему три. Канон называет две операции и ТРЕТИЙ адрес описывает словами (`Export.url`), `operationId` ему не давая. Он живёт в `contractSurface` с остальными — иначе не получил бы ни гарда сессии, ни капа тела, ни пина «без сессии 401».
|
||||
|
||||
**Решения, которые промт оставил мне, и доводы:**
|
||||
|
||||
|
|
@ -517,7 +598,10 @@ make vuln # No vulnerabilities found.
|
|||
|
||||
**Прибавка пака — 62 тест-функции, и вот они поимённо** (`grep -c '^func Test' <файл>` по новым файлам плюс `git diff --unified=0 … | grep -c '^+func Test'` по дописанным): `internal/exports/exports_test.go` 18 · `internal/httpapi/exports_test.go` 12 · `internal/pgstore/exports_test.go` 9 · `internal/runs/abandon_orphan_test.go` 6 · `internal/ingest/build_test.go` 5 · `internal/config/exports_test.go` 4 · `internal/runner/build_live_test.go` 3 · `cmd/tmplatformctl/abandon_proof_test.go` 3 · `cmd/tmplatformd/runner_test.go` +2. ⚠ Функций 62, а `=== RUN` верхнего уровня больше: часть из них — `t.Run` подтестами, и оба числа верны о разном. ⚠ Прежняя редакция этого абзаца называла `exports_test.go` 10 при девяти и «+54» — приёмка поймала обе, числа пере-сняты командой (счёт: `grep -c '^func Test' <файл>` по новым файлам плюс `git diff --unified=0 … | grep -c '^+func Test'` по дописанным). Пакетов 19 вместо 18 — прибавился `internal/exports`. Скипов и красных как не было, так и нет. ⚠ Из них **двадцать одна заведена НЕ при постройке**: двенадцать — собственным адверсариальным проходом и ратификацией 04.09, ещё девять — дофиксом по вердикту приёмки (доказательство ручки `PD-424`, согласие поллинга со ссылкой, публикация на отцепленном контексте, кап списания, исключающий клейм). ⚠ Числа сняты на замороженном дереве последним действием; движковый бинарь — из ЧИСТОГО `HEAD 3ba2d26`, до правок параллельной бэкенд-сессии, поэтому живые тесты не читают её движущиеся файлы.
|
||||
|
||||
**Док-гейты:** `python3 docs/scripts/counts.py --check` от корня — «Литералы сходятся с пере-счётом (5 проверок)», регистр `битая форма: []`, `хвост вне словаря: []`, **447 строк, открытых 101** (major 5 — было 3, прибавились `PD-440` и `PD-441`; minor прибавили `PD-443`, `PD-444`, `PD-446`, info — `PD-445`; `PD-442` и `PD-447` закрыты деревом). ⚠ Числа 442/97 в первой редакции были сняты ДО дофикса, 443/98 — до трёх рядов живого прогона (`H12`/`H13`/`H14`, заведены 04.09 по разнарядке оркестратора №22); поправлено оба раза. ⚠ **Заодно исправлена ЧУЖАЯ ошибка формы, которую я же и внесла:** ряд `PD-443` помечен `minor`, а стоял в секции `## Открытые — major`; перенесён в свою секцию. Гейт веса это НЕ ловил и поймать не мог — `counts.py` считает вес по ЯЧЕЙКЕ, а не по секции (`counts.py:272`), поэтому число `major 5` было верным при неверной раскладке. ⚠ Обратный случай остаётся и он НЕ мой: `PD-438` помечен `**major**` и стоит в секции minor — назван оркестратору, своей рукой чужой ряд не двигаю. `--lint` (кап вывода снят, иначе счёт лжёт — см. ниже) — **в моей зоне красных якорей НЕТ**: восемь хитов на четыре адреса пере-нацелены 04.09 на архивные копии промтов со сдвигом номеров **+7**.
|
||||
⚠ **БАТАРЕЯ ПОСЛЕ РЕВИЗИИ — ЗЕЛЕНА, и путь к этому стоит записать, потому что дважды она была КРАСНОЙ по причине, не связанной с деревом.** Итог на спокойной машине: `make check` **выход 0**, скипов 0, падений 0, линтер `0 issues`, `ALARM PD-count 12 (baseline 12)`, «every test ran: no host condition was missing».
|
||||
⚠ **Два прежних красных прогона — ГОЛОДАНИЕ ПО ПРОЦЕССОРУ, и различить это можно было только замером:** на машине шли тринадцать чужих `tmmutate` и батареи движка, `load average 42–48` на восьми ядрах. `TestAnEndlessBankApplyIsRefusedRatherThanRead` мерит ВРЕМЯ — перелив обязан убить процесс за 30 с, занял 71; ошибка при этом БЫЛА обнаружена, упало только утверждение о сроке, а изолированно тест даёт 3/3 `ok` за 1.9 с. `internal/runs` вставал по таймауту пакета без единого упавшего теста. **Правило, выведенное из этого: красный тест, мерящий время, на загруженной машине — не результат; прежде чем звать его дефектом, гоняй изолированно и смотри `uptime`.** ⚠ Обратная половина правила тоже сработала: второй красный оказался НАСТОЯЩИМ дефектом (`PD-448`), и отличило их именно то, что первый изолированно зелен, а второй краснел 2 из 2 в пакете.
|
||||
|
||||
**Док-гейты:** `python3 docs/scripts/counts.py --check` от корня — «Литералы сходятся с пере-счётом (5 проверок)», регистр `битая форма: []`, `хвост вне словаря: []`, **448 строк, открытых 102** (прибавился `PD-448`) (major 5 — было 3, прибавились `PD-440` и `PD-441`; minor прибавили `PD-443`, `PD-444`, `PD-446`, info — `PD-445`; `PD-442` и `PD-447` закрыты деревом). ⚠ Числа 442/97 в первой редакции были сняты ДО дофикса, 443/98 — до трёх рядов живого прогона (`H12`/`H13`/`H14`, заведены 04.09 по разнарядке оркестратора №22); поправлено оба раза. ⚠ **Заодно исправлена ЧУЖАЯ ошибка формы, которую я же и внесла:** ряд `PD-443` помечен `minor`, а стоял в секции `## Открытые — major`; перенесён в свою секцию. Гейт веса это НЕ ловил и поймать не мог — `counts.py` считает вес по ЯЧЕЙКЕ, а не по секции (`counts.py:272`), поэтому число `major 5` было верным при неверной раскладке. ⚠ Обратный случай остаётся и он НЕ мой: `PD-438` помечен `**major**` и стоит в секции minor — назван оркестратору, своей рукой чужой ряд не двигаю. `--lint` (кап вывода снят, иначе счёт лжёт — см. ниже) — **в моей зоне красных якорей НЕТ**: восемь хитов на четыре адреса пере-нацелены 04.09 на архивные копии промтов со сдвигом номеров **+7**.
|
||||
|
||||
⚠ **МЕТОД, стоивший мне ложного отчёта — называю, потому что он повторится у следующего.** `counts.py --lint` печатает не все находки: **кап вывода 25** (`counts.py:630`), а сортировка ставит вперёд тяжёлые — и хвост «файла нет» уезжает под черту. Я сняла свой счёт командой `--lint | grep '✗ platform/'` по ОБРЕЗАННОМУ выводу, получила ноль и доложила «в зоне красных нет». В зоне было **восемь**. ⚠ Вторая попытка была хуже первой: копия скрипта в `/tmp` дала ноль красных — и это был не результат, а ПАДЕНИЕ (`ROOT = Path(__file__).resolve().parents[2]`, у копии вне дерева родителей не хватает; трейсбек ушёл в тот же поток, а `$?` я прочитала от `tail`, не от python). **Ложный ноль от упавшего гейта неотличим от зелени, если смотреть только на число.** Рабочий приём: копия кладётся ВНУТРЬ дерева на той же глубине (`platform/docs/_lint_full.py` — своя зона, удаляется сразу), тогда корень резолвится верно: `sed 's/CAP = 25/CAP = 999/' docs/scripts/counts.py > platform/docs/_lint_full.py && python3 platform/docs/_lint_full.py --lint`. ⚠ И третий капкан того же дня: **цитата мёртвого адреса САМА становится якорем**. Первая редакция пере-нацеливания назвала прежние адреса в форме `` `путь:номер` `` — гейт прочитал их как якоря и вернул шесть красных. Прежний адрес пишется порознь: имя файла в кавычках, номер словами.
|
||||
|
||||
|
|
@ -528,8 +612,8 @@ make vuln # No vulnerabilities found.
|
|||
1. **`Export.failure_code` становится enum** — условие, которое канон сам себе поставил («becomes an enum with the first built format»), наступило (`contract-10` строки 243). Предлагаемый набор — то, что эта реализация ПРОИЗВОДИТ, а не то, что можно вообразить: `book_empty` (в книге ноль выходных юнитов) · `deployment_error` (движок отказал в конфигурации ДЕПЛОЯ: формат, которого он не знает, `book.yaml` без языкового тега, нечитаемый langpack, `--out`, который хост не берёт — чинит ОПЕРАТОР). ⚠ Имя не про формат нарочно: класс 10 покрывает все пять причин, и `format_unavailable` солгало бы про четыре из них · `build_interrupted` (сборка не кончилась и никто за ней не вернётся; повтор сходится) · `build_failed` (класс, которому эта сборка не нашла лучшего слова; у оператора есть строка лога). ⚠ Четыре и ни одного «прочее»: `build_failed` и есть «прочее», названное честно.
|
||||
2. **Механизм ссылки и её TTL.** Предлагаю записать то, что построено: ссылка АУТЕНТИФИЦИРОВАННАЯ на том же ориджине, а не подписанная капабилити. Довод в шапке `internal/httpapi/exports.go` и в пункте 2 выше; коротко — она строго сильнее токена и не требует деплойного секрета, который никто не ротирует. Формулировку канона «Minted for THIS response» стоит заменить на «bound to the authenticated owner», потому что нынешняя читается как обещание одноразового значения, которого продукт не даёт и без которого честнее.
|
||||
3. **Строка 241 — канон противоречит себе на ДОЧИТАННОЙ книге** (правка банка «takes effect on the NEXT run» против «finished work is not bought twice»). ⚠ **Живьём НЕ воспроизведено:** книга пака до конца не дочитана (`PD-440`), так что состояния «дочитанная книга с принятой правкой» у меня не было. Несу как есть, без замера.
|
||||
4. **Третий адрес двери** (`.../exports/{exportId}/content`) канон описывает словами и `operationId` ему не даёт. Предлагаю дать: сегодня это единственный маршрут поверхности, которого нет в перечне операций, и генерируемый клиент про него не знает — а браузер по нему НАВИГИРУЕТ, что и есть его способ существования.
|
||||
5. **Шестнадцатая причина флага.** Бэкенд-сессия завела `off_target_lang` (`backend/internal/pipeline/disposition.go`, ещё не заленджено). Приложению А нужен ряд: новый код замечания + ступень. ⚠ Фраза пишется по ДОККОММЕНТУ константы, а не по её имени — правило уже записано в компаньоне канона. Карту `internal/ingest/notes.go` НЕ дополняла: контрактного кода ещё нет, а изобрести его значило бы править канон своей рукой.
|
||||
4. **Третий адрес двери** (`.../exports/{exportId}/content`) канон описывает словами и `operationId` ему не даёт. Предлагаю дать: сегодня это единственный маршрут поверхности, которого нет в перечне операций, и генерируемый клиент про него не знает — а браузер по нему НАВИГИРУЕТ, что и есть его способ существования. **✅ РАТИФИЦИРОВАН оркестратором 04.09** нотой `D39.194` п.3 (контрактный минор `0.10.0`, `58bca19`): `operationId` — `downloadExport`, с объявленными `206` на Range, `410` на протухшую ссылку и `Content-Disposition` в заголовках ответа.
|
||||
5. **Шестнадцатая причина флага.** Бэкенд-сессия завела `off_target_lang` (`backend/internal/pipeline/disposition.go`, ещё не заленджено). Приложению А нужен ряд: новый код замечания + ступень. ⚠ Фраза пишется по ДОККОММЕНТУ константы, а не по её имени — правило уже записано в компаньоне канона. Карту `internal/ingest/notes.go` НЕ дополняла: контрактного кода ещё нет, а изобрести его значило бы править канон своей рукой. **✅ РАТИФИЦИРОВАН и ИСПОЛНЕН 04.09:** канон дал код `wrong_language` минором `0.10.0` (`D39.194`), и карта дополнена шестнадцатой строкой `off_target_lang → {wrong_language, StepAttention}` (`internal/ingest/notes.go`, коммит `7e204db`); ступень — провизорное решение зоны, колонка канона ⬜ ждёт слов владельца.
|
||||
|
||||
6. **`Export.url` объявлен `format: uri`, а отдаётся ОТНОСИТЕЛЬНЫЙ путь** — расхождение с ратифицированным контрактом, найдено адверсариальным проходом. Предлагала минор `uri` → `uri-reference`; **✅ РАТИФИЦИРОВАН оркестратором 04.09** нотой `D39.194` (контрактный минор `0.10.0`), правит он. ⚠ **ВХОЖДЕНИЕ ОДНО — как я и называла изначально.** Первая редакция этого пункта повторяла его сверку «вхождений ДВА»; он её ОТОЗВАЛ, прочитав второе место перед правкой: там поле `type` проблемы RFC 9457 со значением `about:blank`, а это по стандарту абсолютный URI, и трогать его нельзя. ⚠ Класс ошибки назван им прямо и стоит того, чтобы жить здесь, а не в оговорке: **ратификация по грепу без чтения предмета** — совпадение строки принято за совпадение смысла. Тот же класс, что мой собственный счёт красных якорей по обрезанному выводу. Довод принят в усиленном виде: абсолютный URL заставил бы сервис знать свой публичный ориджин, которого за edge-прокси он надёжно не знает — тот же класс, что `PD-101`. Кода это не касается: относительный путь и был тем, что дверь отдаёт.
|
||||
7. **`409 book_not_ready` на `createExport` — ✅ РЕШЕНО 04.09 ОРКЕСТРАТОРОМ И ИСПОЛНЕНО: код СНЯТ.** Я вынесла это как «канон спорит сам с собой»; оркестратор прочитал описание целиком и показал, что противоречия нет — есть одна недвусмысленная фраза «Nothing about a book's state conflicts with exporting it», которую нарушал КОД. Он прав, и `book_not_ready` на этой двери был изобретением кода. Снято: запрос принимается ВСЕГДА, а что получает книга без дерева глав — отвечает сам экспорт. Пины: `TestABookThatWasNeverCutIsAcceptedAndAnsweredByItsExportRatherThanRefused`, `TestNoBookStateIsARefusalOnThisDoor`, `TestTheWorkersReadKnowsWhetherTheBookWasEverCut`; посадки P1/P2/P3 — все три красят адресно.
|
||||
|
|
@ -2345,7 +2429,7 @@ D39.162, держать её как находку нельзя · Р5 → F5 ·
|
|||
| # | Вес | Что | Где |
|
||||
|---|---|---|---|
|
||||
| Д2 | breaks | **Неидемпотентный decline** поверхности подписанного сида, имеющей строку в дельте: первый вызов ПРИНЯТ, повтор ТОГО ЖЕ документа — 409 (гейт `decisions.go:374` судит ДО-состояние, которое свёртка сама стирает); при классе 15 предписанный ре-сенд отбивается ЦЕЛИКОМ — **обещание сходимости 503-ретрая, на котором стоит синхронная дверь, ломается**. Доказано исполнением через СОБСТВЕННЫЙ оракул репозитория (оракул 4 фаззера); фаззер структурно не достаёт (фикс-книга не пересекает сид с дельтой). Лечение: судить по ПОСТ-состоянию, как соседний `refuseInertDeclines` | `membank/decisions.go:374` |
|
||||
| Д3 | breaks | **Потеря/порча маркера выхода после стопа** (совместная с платформой): рестарт/ретрай проходит границу банка НАСКВОЗЬ — память предъявления покрывает карту, движок «continuing» одним WARN себе в журнал — оплаченный `verify_bank` стоп исчезает молча и навсегда (память append-only). Гард `LiftBankStop` (`reconcile.go:1122-1126`) писан против движка ДО памяти v16 и этот путь не держит | `reconcile.go:424`, `mining.go:216-227` |
|
||||
| Д3 | breaks | **Потеря/порча маркера выхода после стопа** (совместная с платформой): рестарт/ретрай проходит границу банка НАСКВОЗЬ — память предъявления покрывает карту, движок «continuing» одним WARN себе в журнал — оплаченный `verify_bank` стоп исчезает молча и навсегда (память append-only). Гард `LiftBankStop` (`reconcile.go:1122-1126`) писан против движка ДО памяти v16 и этот путь не держит | `reconcile.go:424`, `mining.go:216-227` | ⚠ **ПОМЕТКА 04.09, ряд НЕ пере-нацелен и пере-нацелен быть не может:** он внутри ЗАКРЫТОГО раунда ревью, а пере-указание якорей в законченном раунде переписывает чужой замер. Оба его адреса сегодня мертвы: гард `LiftBankStop` СНЯТ (в дереве осталось только `reconcile.go:615` «removed with the workaround»), а `reconcile.go:1122-1126` — теперь чужой код (`const base, cap`); защита переехала в ветку, которую называет `stoppedOnRequest` (`reconcile.go:424`). Читать ряд как запись о том, что было верно ТОГДА.
|
||||
|
||||
### Чистые оси (проверено — не опровергнуто)
|
||||
|
||||
|
|
@ -2584,7 +2668,13 @@ mutation-catch (спутать пары «числитель×база»); ко
|
|||
описи): называть их — правильно, и опись, недобравшая три файла из пяти, действительно стоила бы
|
||||
лендеру правки НОРМЫ. Продолжайте так же.
|
||||
|
||||
## Текущее состояние
|
||||
## Состояние эры P8 (записи до 24.08) — ИСТОРИЧЕСКИЙ раздел, НЕ текущее состояние
|
||||
|
||||
⚠ **Заголовок переименован 04.09.** Он звался «Текущее состояние», лежал на строке ~2587 из ~2851 и держал
|
||||
записи, кончающиеся 24.08: паков P9–P13 и «закрыть цикл» под ним НЕТ. `README.md` посылал сюда за
|
||||
состоянием зоны, и сессия, честно исполнившая онбординг, получала картину недельной давности. **Текущее
|
||||
состояние — ВЕРХ этого файла:** журнал обратно-хронологический; отчёт последнего пака — в верхней
|
||||
трети, ниже него могут стоять более свежие записи смены.
|
||||
|
||||
**⛔ ПИНГ ОРКЕСТРАТОРА №19 — 23.08, ЗОНЕ ПЛАТФОРМЫ.** ⚠ Его собственная шапка звала находки «тремя», а нумеровала ЧЕТЫРЕ — считать по нумерации. Живых из четырёх ДВЕ: находка 1 ниже дословно, находка 4 — строкой регистра. Две закрыты паком P9 и проверяются грепом, а не памятью: блокер провайдерских ключей (строка **211** единого бэклога) — ключи едут аргументом `--keys-file`, греп `TM_PLATFORM_ENGINE_KEYS_PATH`; дубль движковой конвенции пути (строка **213**) — `runner.projectDB` снесён, путь берётся из `artifacts.bank_export` манифеста. Находка 4 (мёртвое поле `ingest.StatusReport.UnsignedBankTerms`) жива и несёт свой якорь строкой `PD-396` (греп `UnsignedBankTerms` в регистре и в `platform/internal/ingest/resync.go`).
|
||||
|
||||
|
|
@ -2761,7 +2851,7 @@ migrate → повтор») можно строить; строка открыт
|
|||
приёмкой (мёртвая цитата текста ошибки схемы в рантбуке и в строке П-1; образец в `tmplatformctl`,
|
||||
зовущий голый `tmctl` из PATH), закрыты правками и проверяются грепом: `grep -rn 'expects vM'
|
||||
platform/deploy platform/BACKLOG.md` — ноль строк; рантбук несёт живой токен
|
||||
(`platform/deploy/README.md:202`=`schema_mismatch found=N expected=M`); образец несёт
|
||||
(`platform/deploy/README.md:249`=`schema_mismatch found=N expected=M`); образец несёт
|
||||
версионированный путь (`platform/cmd/tmplatformctl/runs.go:53`=`The VERSIONED path and never a bare`).
|
||||
|
||||
⚠ **Живым остаётся мнение оркестратора:** прогнать деплой-рантбук end-to-end на дев-стенде
|
||||
|
|
|
|||
|
|
@ -54,6 +54,11 @@ var alarmMarkers = []string{"деньг", "холд", "молча", "блоки
|
|||
// to write a base against a closure that did not exist. It exists now, and the gate says so:
|
||||
// `ALARM PD-168 LEFT the class (status "fixed(6ae3e76)", weight "minor, деньги")`.
|
||||
var alarmBaseline = []string{
|
||||
// PD-448 — гонка двух резюмов: проигравший получает отказ вместо прогона. Внесён 05.09 той же
|
||||
// правкой, что и ряд. Маркеры класса он несёт законно: ряд говорит о ДЕНЬГАХ (холд берётся
|
||||
// ровно один) и о том, что отказ приходит МОЛЧА для пользователя, дважды нажавшего кнопку.
|
||||
// Ниже major он стоит потому, что деньги не теряются: отказ случается ДО взятия холда.
|
||||
"PD-448",
|
||||
"PD-94", "PD-107", "PD-162", "PD-201", "PD-212",
|
||||
"PD-217", "PD-244", "PD-418", "PD-420", "PD-428", "PD-433",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import (
|
|||
"textmachine/platform/internal/pgstore"
|
||||
)
|
||||
|
||||
// idempotency.go: `Idempotency-Key` on the two writes that create something (canon
|
||||
// idempotency.go: `Idempotency-Key` on the three writes that create something (canon
|
||||
// §IdempotencyKey).
|
||||
//
|
||||
// The semantics are entirely the server's duty and are written out here rather than inferred:
|
||||
|
|
|
|||
|
|
@ -229,10 +229,15 @@ func TestARollbackSurvivesTheDataTheNewVocabulariesWrote(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
defer closeProvider()
|
||||
// Down to version 5 and no further, which is the DOCUMENTED floor of a real rollback: 00005's
|
||||
// own down path restores `email NOT NULL` and the unique index, and both are violated by rows
|
||||
// production writes (`deploy/README.md`, "Откат релиза: не ниже версии 5"). Rolling to zero here
|
||||
// would test that known limit instead of this migration.
|
||||
// Down to version 5 and no further. 00005's own down path restores `email NOT NULL` and the
|
||||
// unique index, and both are violated by rows production writes, so rolling to zero here would
|
||||
// test that known limit instead of this migration.
|
||||
//
|
||||
// ⚠ The DOCUMENTED floor of a real rollback is 15, not 5 (`deploy/README.md`, "Откат релиза: не
|
||||
// ниже версии 15"): 00015's down path narrows `runs_paused_reason_check` back to one value while
|
||||
// production writes three. That floor is about DATA, not schema — it bites only a base that has
|
||||
// held such a pause — so this base rolls past it honestly, and this test passing is not evidence
|
||||
// against the documented floor.
|
||||
if _, err := p.DownTo(ctx, 5); err != nil {
|
||||
t.Fatalf("rolling back a database that holds real rows: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -194,8 +194,9 @@ type meter struct {
|
|||
// ⚠ The RESERVED half of the engine's own comparison is deliberately not added, and the reason is a
|
||||
// line the first version of the formula did not account for. `store.Open` — the WRITE path every
|
||||
// `translate` takes — runs `recoverReservations`, which zeroes every `reserved_usd` of the book
|
||||
// before the first reservation is judged (backend/internal/store/store.go:88 and :214). `tmctl
|
||||
// status` is read-only and deliberately does NOT run that pass (store.go:108 says so), so the figure
|
||||
// before the first reservation is judged (backend/internal/store/store.go:110 and :278 — re-aimed
|
||||
// 04.09, the targets had drifted from :88 and :214). `tmctl status` is read-only and deliberately
|
||||
// does NOT run that pass (store.go:117-132 `OpenReadOnly` says so), so the figure
|
||||
// the platform reads is a LEFTOVER of a crashed process, guaranteed to be gone by the time the cap
|
||||
// is compared against anything. Adding it hands the run that much headroom BEYOND its hold: the
|
||||
// engine stops late, settlement is capped at the hold, and the account underpays while the ledger
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue