Correct the zone docs the audit caught lying: the runbook on empty language pairs, the acceptance criterion on skips, the sqlc decision and a phantom file

This commit is contained in:
Claude (backend session) 2026-08-22 18:21:53 +03:00
parent 4e43b310f4
commit ca886c0f61
6 changed files with 31 additions and 8 deletions

View file

@ -10,7 +10,7 @@
| ID | Хвост | Вес | Источник | | ID | Хвост | Вес | Источник |
|---|---|---|---| |---|---|---|---|
| П-1 | **HTTP/SSE-слой и сервисная обвязка** (экс-строка 96 единого бэклога) — ⚠ **ЧАСТЬ ИСПОЛНЕНА:** аутентификация, сессии и CSRF построены P1; тейлер `events.jsonl` с курсором и карантином проекции, реконсилятор и пять ручек `/v0` — P4/P5; ПОТРЕБИТЕЛЬСКАЯ половина шва (коды выхода, фолд присваиванием, потолки) — P6. **ОСТАЁТСЯ** читающая поверхность: SSE-эндпоинт, ручки глав/юнитов/замечаний, проекция банка — это пак P7. Состав ниже — исходная постановка: read-API поверх готовых `OpenReadOnly`-путей движка; SSE-события ПУШИТ воркер, фронт read-model не опрашивает (каждый read-вызов движка — дорогой ре-ингест, до строки 100 единого); аутентификация ратифицирована D39.84: одна серверная сессия в Postgres — `__Host`-кука браузеру · `Authorization: Bearer` десктопу/CLI · principal создаётся ТОЛЬКО в middleware, CSRF только на cookie-пути; ⚠ порядок деплоя: read-путь движка схему НЕ мигрирует — отказ это exit 13 + токен `schema_mismatch found=N expected=M` (`backend/internal/store/migrate.go`, `SchemaMismatchError`; прежний якорь `store.go:135-143` и цитата «schema vN … expects vM» протухли, D39.134), а лечение — `tmctl migrate` НОВЫМ бинарём (рантбук прогнан живьём P7); **форма потока ратифицирована D39.85 (`docs/research/23`):** воркер-обёртка платформы супервайзит процесс tmctl, ингестит NDJSON-события идемпотентным апсертом (run_id, seq) в Postgres (Reporting Database, не полный CQRS), SSE — из Postgres; ре-синк на обрыве — `tmctl status --json`; живой SQLite движка НЕ читать (анти-паттерн, аргументы в research/23 §4); ревью-гард: путь Go-модуля платформы никогда не вкладывать под `textmachine/backend/*` | P7 (остаток: читающая поверхность) | D39.81, D39.84, D39.85, STACK_DECISIONS §5 | | П-1 | **HTTP/SSE-слой и сервисная обвязка** (экс-строка 96 единого бэклога) — ⚠ **ЧАСТЬ ИСПОЛНЕНА:** аутентификация, сессии и CSRF построены P1; тейлер `events.jsonl` с курсором и карантином проекции, реконсилятор и пять ручек `/v0` — P4/P5; ПОТРЕБИТЕЛЬСКАЯ половина шва (коды выхода, фолд присваиванием, потолки) — P6. **ОСТАЁТСЯ** читающая поверхность: SSE-эндпоинт, ручки глав/юнитов/замечаний, проекция банка — это пак P7. Состав ниже — исходная постановка: read-API поверх готовых `OpenReadOnly`-путей движка; SSE-события ПУШИТ воркер, фронт read-model не опрашивает (каждый read-вызов движка — дорогой ре-ингест, до строки 100 единого); аутентификация ратифицирована D39.84: одна серверная сессия в Postgres — `__Host`-кука браузеру · `Authorization: Bearer` десктопу/CLI · principal создаётся ТОЛЬКО в middleware, CSRF только на cookie-пути; ⚠ порядок деплоя: read-путь движка схему НЕ мигрирует — отказ это exit 13 + токен `schema_mismatch found=N expected=M` (`backend/internal/store/migrate.go`, `SchemaMismatchError`; прежний якорь `store.go:135-143` и цитата «schema vN … expects vM» протухли, D39.134), а лечение — `tmctl migrate` НОВЫМ бинарём (рантбук прогнан живьём P7); **форма потока ратифицирована D39.85 (`docs/research/23`):** воркер-обёртка платформы супервайзит процесс tmctl, ингестит NDJSON-события идемпотентным апсертом (run_id, seq) в Postgres (Reporting Database, не полный CQRS), SSE — из Postgres; ре-синк на обрыве — `tmctl status --json`; живой SQLite движка НЕ читать (анти-паттерн, аргументы в research/23 §4); ревью-гард: путь Go-модуля платформы никогда не вкладывать под `textmachine/backend/*`**ДИСПОЗИЦИЯ 22.08 (аудит доков): строка ЗАКРЫТА, живого хвоста нет.** Последний остаток — читающая поверхность — был вынесен отдельной строкой **П-17** и исполнен паком P7 (D39.153), а «часть исполнена» перестала что-либо значить ещё тогда. Норма шапки этого файла требует у каждой петли диспозиции — вот она; ссылку «до строки 100 единого бэклога» держать не нужно, та строка закрыта 09.08. | P7 (остаток: читающая поверхность) | D39.81, D39.84, D39.85, STACK_DECISIONS §5 |
| П-2 | **Глобальный брокер рейт-лимитов провайдеров** (экс-строка 97): гарды движка per-процесс (`pipeline/ratelimit.go:11`, mistral ~48% отказов под параллелизмом), а лимит провайдера — на ВЕСЬ аккаунт: N прогонов = N независимых гардов против общего лимита; воркер отпрашивается у платформы перед вызовом | ДО второго параллельного пользователя | D39.81, D39.84 | | П-2 | **Глобальный брокер рейт-лимитов провайдеров** (экс-строка 97): гарды движка per-процесс (`pipeline/ratelimit.go:11`, mistral ~48% отказов под параллелизмом), а лимит провайдера — на ВЕСЬ аккаунт: N прогонов = N независимых гардов против общего лимита; воркер отпрашивается у платформы перед вызовом | ДО второго параллельного пользователя | D39.81, D39.84 |
| П-3 | **Очередь и конкурентность по книге** — ⚠ **ИСПОЛНЕНО P4 в той форме, которая была нужна:** River на том же Postgres, `MaxAttempts: 1` (повтор не доделывает работу, а спавнит второй движок — работу восстанавливает реконсилятор), одновременность по книге закрыта не лизами, а частичным уникальным индексом `runs_one_live_per_book` в БД: двух живых прогонов на книге не бывает, потому что движок держит эксклюзивный лок на файле проекта. **ОСТАЮТСЯ** ровно те части, которые нужны только со ВТОРЫМ хостом: пиннинг книги к хосту и лизы `book_leases` с heartbeat — сегодня хост один, и лиза без второго хоста охраняет от самой себя | до второго хоста | STACK_DECISIONS §5 и §19, D39.84 | | П-3 | **Очередь и конкурентность по книге** — ⚠ **ИСПОЛНЕНО P4 в той форме, которая была нужна:** River на том же Postgres, `MaxAttempts: 1` (повтор не доделывает работу, а спавнит второй движок — работу восстанавливает реконсилятор), одновременность по книге закрыта не лизами, а частичным уникальным индексом `runs_one_live_per_book` в БД: двух живых прогонов на книге не бывает, потому что движок держит эксклюзивный лок на файле проекта. **ОСТАЮТСЯ** ровно те части, которые нужны только со ВТОРЫМ хостом: пиннинг книги к хосту и лизы `book_leases` с heartbeat — сегодня хост один, и лиза без второго хоста охраняет от самой себя | до второго хоста | STACK_DECISIONS §5 и §19, D39.84 |
| П-4 | **Учёт токенов/денег per-user + бюджет-гейт ДО старта задачи** (сырьё уже считает движок: `request_log` + `internal/ledger`; помнить: леджер = нижняя граница — строка 78 единого) | Ф3-пак платформы | platform/README, D39.84 | | П-4 | **Учёт токенов/денег per-user + бюджет-гейт ДО старта задачи** (сырьё уже считает движок: `request_log` + `internal/ledger`; помнить: леджер = нижняя граница — строка 78 единого) | Ф3-пак платформы | platform/README, D39.84 |

View file

@ -106,7 +106,7 @@ Prometheus; на контрактную поверхность они не вы
|---|---| |---|---|
| `cmd/tmplatformd` | демон: сборка зависимостей, очередь, свипы. `runner.go` — единственное место, где зона склеивается | | `cmd/tmplatformd` | демон: сборка зависимостей, очередь, свипы. `runner.go` — единственное место, где зона склеивается |
| `cmd/tmplatformctl` | админ-CLI и `exit-marker`, который systemd зовёт на конце прогона | | `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` и `notes.go` (переводы словарей: read-модель → провод) | | `internal/httpapi` | ВСЯ контрактная поверхность: `v0.go` (маршруты, библиотека, интейк, прогоны) · `reading.go` (главы, пары, замечания, банк) · `stream.go` (SSE) · `capabilities.go` · `problem.go` (модель ошибок) · `conditional.go` (ETag/304 + gzip; SSE через него НЕ проходит — потому и не сжимается) · `idempotency.go` · `project.go` и `reading.go` (переводы словарей: read-модель → провод) |
| `internal/auth`, `internal/login` | сессии, CSRF, вход. Принципал создаётся ТОЛЬКО в мидлваре | | `internal/auth`, `internal/login` | сессии, CSRF, вход. Принципал создаётся ТОЛЬКО в мидлваре |
| `internal/books` | интейк: приём файла, каталог книги, рендер стартового `book.yaml`, разбор через `tmctl manifest` | | `internal/books` | интейк: приём файла, каталог книги, рендер стартового `book.yaml`, разбор через `tmctl manifest` |
| `internal/runs` | жизнь прогона: допуск, спавн транзиентного юнита, реконсилятор, деньги на границе попытки | | `internal/runs` | жизнь прогона: допуск, спавн транзиентного юнита, реконсилятор, деньги на границе попытки |

View file

@ -87,8 +87,13 @@ TM_PLATFORM_METRICS_ADDR=127.0.0.1:9464
грант по умолчанию НОЛЬ и начисляется руками — `tmplatformctl grant`. Строка `=5` в этом блоке грант по умолчанию НОЛЬ и начисляется руками — `tmplatformctl grant`. Строка `=5` в этом блоке
включала обратно ровно тот самообслуживаемый безлимитный грант, который дефолт выключает. включала обратно ровно тот самообслуживаемый безлимитный грант, который дефолт выключает.
**`TM_PLATFORM_LANGUAGE_PAIRS` обязателен.** Пустой список делает `/capabilities` и интейк **`TM_PLATFORM_LANGUAGE_PAIRS` обязателен, и с акта 5 P7 правило ЖЁСТЧЕ, чем описывала прежняя
противоположными: провод говорит «этот деплой не умеет ни одной пары», а интейк принимает любую. редакция этого абзаца** (испр. 22.08 аудитом доков — она утверждала обратное: «интейк принимает
любую»). Инстанс, который ПРИНИМАЕТ ЗАГРУЗКИ и не объявил ни одной ДОСТУПНОЙ пары, **не стартует
вовсе** (`internal/config/config.go`, проверка `IntakeEnabled() && len(AvailablePairs()) == 0`);
объявленная, но недоступная пара отклоняется интейком кодом `unsupported_pair`. То есть «не объявлено
ничего» и «объявлена эта пара» — разные ответы, и деплой больше не может принять книгу, которую он
способен только провалить.
## Шаблон книги (`TM_PLATFORM_BOOK_TEMPLATE`) — артефакт ОПЕРАТОРА ## Шаблон книги (`TM_PLATFORM_BOOK_TEMPLATE`) — артефакт ОПЕРАТОРА
@ -131,7 +136,7 @@ ceilings:
предохранителем: она делает такую остановку различимой и честной, а не дефолтом, который её вызывает. предохранителем: она делает такую остановку различимой и честной, а не дефолтом, который её вызывает.
Битый или отсутствующий шаблон — **беда деплоя, а не книги**: загрузки не отклоняются и не удаляются, Битый или отсутствующий шаблон — **беда деплоя, а не книги**: загрузки не отклоняются и не удаляются,
книги ждут в `parsing` (метрика `books_parsing`), в логе — ERROR с причиной. Починили файл — ближайший книги ждут в `parsing` (метрика `tm_platform_books_in_intake{status="parsing"}`), в логе — ERROR с причиной. Починили файл — ближайший
свип разбирает всё накопившееся. свип разбирает всё накопившееся.
## Апгрейд ПЛАТФОРМЫ: миграция и бинарь едут вместе ## Апгрейд ПЛАТФОРМЫ: миграция и бинарь едут вместе

View file

@ -45,7 +45,7 @@
| PD-298 | bug | info | `internal/pgstore/readmodel.go` `ListNotes`, `internal/pgstore/sink.go` `unitDone` | **Снятие флага с замечания дельта-чтение выразить не может.** Резолюция, пере-разрешённая как не-`flagged` (редрайв), обновляет строку и двигает `revision`, но дельта фильтруется предикатом `ur.flagged` — строка не возвращается, и клиент никогда не узнаёт, что замечание снято: оно остаётся на экране навсегда. Канон §AfterVersion: «A DELETION cannot be expressed this way», и требует одного из двух ответов — `resync_required` либо `400 version_too_old`; здесь не даётся ни один. ⚠ НЕ подтверждено, что движок вообще пере-издаёт `unit_done` для той же тройки (глава, юнит, волна) с `flagged=false` — комментарий `sink.go` это УТВЕРЖДАЕТ («a redrive re-attacks a flagged one»), но чтением движка не сверено. Порядок: сначала сверка у движка, потом либо счётчик замены для замечаний, либо строка «переход недостижим» ⚠ **ДИСПОЗИЦИЯ АКТА 5 (сверка с движком контрактной сессией 20.08): переход НЕДОСТИЖИМ и механизма не строим.** Движок объявляет вердикт юнита один раз на волну, его announce-once-леджер не пере-announce-ит, поэтому единственная пере-доставка — ТОТ ЖЕ вердикт. Инвариант записан в коде (`sink.go` `unitDone`): если что-то научится снимать флаг, фолд обязан выдать кадр — иначе счётчик разъедется со списком молча. Строка держится открытой этим долгом, а не живым дефектом | open | приёмка P7 → доработка 20.08 (сверка) | | PD-298 | bug | info | `internal/pgstore/readmodel.go` `ListNotes`, `internal/pgstore/sink.go` `unitDone` | **Снятие флага с замечания дельта-чтение выразить не может.** Резолюция, пере-разрешённая как не-`flagged` (редрайв), обновляет строку и двигает `revision`, но дельта фильтруется предикатом `ur.flagged` — строка не возвращается, и клиент никогда не узнаёт, что замечание снято: оно остаётся на экране навсегда. Канон §AfterVersion: «A DELETION cannot be expressed this way», и требует одного из двух ответов — `resync_required` либо `400 version_too_old`; здесь не даётся ни один. ⚠ НЕ подтверждено, что движок вообще пере-издаёт `unit_done` для той же тройки (глава, юнит, волна) с `flagged=false` — комментарий `sink.go` это УТВЕРЖДАЕТ («a redrive re-attacks a flagged one»), но чтением движка не сверено. Порядок: сначала сверка у движка, потом либо счётчик замены для замечаний, либо строка «переход недостижим» ⚠ **ДИСПОЗИЦИЯ АКТА 5 (сверка с движком контрактной сессией 20.08): переход НЕДОСТИЖИМ и механизма не строим.** Движок объявляет вердикт юнита один раз на волну, его announce-once-леджер не пере-announce-ит, поэтому единственная пере-доставка — ТОТ ЖЕ вердикт. Инвариант записан в коде (`sink.go` `unitDone`): если что-то научится снимать флаг, фолд обязан выдать кадр — иначе счётчик разъедется со списком молча. Строка держится открытой этим долгом, а не живым дефектом | open | приёмка P7 → доработка 20.08 (сверка) |
| PD-299 | standards | info | `internal/httpapi/conditional.go` `acceptsGzip` | **`Accept-Encoding: identity;q=0` не отвечает `406`.** Клиент, потребовавший ЛЮБОГО кодирования кроме identity, получает identity. Половина RFC 9110 §12.5.3, которую правка PD-268 не закрыла: gzip-сторона (именованное кодирование выигрывает у `*`, нулевой вес — отказ) закрыта и пиньётся, эта — нет. Достижимо только специально сконструированным клиентом; ни один генерённый по контракту клиент так не делает | open | доработка 20.08 (сверка находок против дерева) | | PD-299 | standards | info | `internal/httpapi/conditional.go` `acceptsGzip` | **`Accept-Encoding: identity;q=0` не отвечает `406`.** Клиент, потребовавший ЛЮБОГО кодирования кроме identity, получает identity. Половина RFC 9110 §12.5.3, которую правка PD-268 не закрыла: gzip-сторона (именованное кодирование выигрывает у `*`, нулевой вес — отказ) закрыта и пиньётся, эта — нет. Достижимо только специально сконструированным клиентом; ни один генерённый по контракту клиент так не делает | open | доработка 20.08 (сверка находок против дерева) |
| PD-368 | hardening | info | `internal/config/config.go`, `internal/runs/reconcile.go` `phaseBudget` | **Пара `TM_PLATFORM_SWEEP_BUDGET`/`TM_PLATFORM_RUN_BUDGET` не проверяется на когерентность на буте, и поднять ОДИН из них — тихий no-op:** фазе достаётся половина прохода, поэтому любое значение `RunBudget` от половины прохода и выше наблюдаемо неотличимо от дефолта. Ревью заходило сюда как в major («поднять бюджет = объявить здоровый прогон застрявшим») и **это опровергнуто исполнением**: поднятие ручки не меняет вообще ничего, вердикт при дефолтах существует и без оператора, а строгая проверка `RunBudget < SweepBudget/2` отвергла бы сами шиппящиеся дефолты (60 с против 60 с). Остаётся эргономика: поднимать надо `SweepBudget`, и об этом не сказано нигде, кроме доккоммента | open | воркфлоу-ревью волны 2 (P8-FIX), находка опровергнута, остаток зафиксирован | | PD-368 | hardening | info | `internal/config/config.go`, `internal/runs/reconcile.go` `phaseBudget` | **Пара `TM_PLATFORM_SWEEP_BUDGET`/`TM_PLATFORM_RUN_BUDGET` не проверяется на когерентность на буте, и поднять ОДИН из них — тихий no-op:** фазе достаётся половина прохода, поэтому любое значение `RunBudget` от половины прохода и выше наблюдаемо неотличимо от дефолта. Ревью заходило сюда как в major («поднять бюджет = объявить здоровый прогон застрявшим») и **это опровергнуто исполнением**: поднятие ручки не меняет вообще ничего, вердикт при дефолтах существует и без оператора, а строгая проверка `RunBudget < SweepBudget/2` отвергла бы сами шиппящиеся дефолты (60 с против 60 с). Остаётся эргономика: поднимать надо `SweepBudget`, и об этом не сказано нигде, кроме доккоммента | open | воркфлоу-ревью волны 2 (P8-FIX), находка опровергнута, остаток зафиксирован |
| PD-373 | doc | info | `internal/readmodel/readmodel.go:64`=`const maxAttempts = 5`, `deploy/README.md:248`=`После пяти неудач` | **У числа попыток материализации ДВА носителя и ничего между ними.** Код держит `const maxAttempts = 5`, рантбук оператора пишет «После пяти неудач долг списывается». **Посажена мутация оркестратором вне списка автора:** `maxAttempts` 5 → 500000, батарея зелёная — пин `readmodel_test.go` ездит `for attempts := range maxAttempts`, то есть доказывает МЕХАНИЗМ при любом значении константы, что само по себе правильно. Незакрытым остаётся другое: подняли константу — рантбук молча начал лгать оператору о том, когда платформа сдаётся. ⚠ Тот же ход у `runs.StalledAfter` проверен и НАРУШЕНИЯ НЕ ДАЛ: там носитель ровно один (рантбук пишет «сколько неудач подряд» без числа), мутация тоже выжила и это законно. Лечение — либо гейт на второй носитель ровно той формы, что пак построил для `ContractVersion` (`internal/gates/contract_test.go` читает канон, а не копию числа), либо число уходит из прозы | open | приёмка P8-FIX (посадка мутации оркестратором №18) | | PD-373 | doc | info | `internal/readmodel/readmodel.go:64`=`const maxAttempts = 5`, `deploy/README.md:253`=`После пяти неудач` | **У числа попыток материализации ДВА носителя и ничего между ними.** Код держит `const maxAttempts = 5`, рантбук оператора пишет «После пяти неудач долг списывается». **Посажена мутация оркестратором вне списка автора:** `maxAttempts` 5 → 500000, батарея зелёная — пин `readmodel_test.go` ездит `for attempts := range maxAttempts`, то есть доказывает МЕХАНИЗМ при любом значении константы, что само по себе правильно. Незакрытым остаётся другое: подняли константу — рантбук молча начал лгать оператору о том, когда платформа сдаётся. ⚠ Тот же ход у `runs.StalledAfter` проверен и НАРУШЕНИЯ НЕ ДАЛ: там носитель ровно один (рантбук пишет «сколько неудач подряд» без числа), мутация тоже выжила и это законно. Лечение — либо гейт на второй носитель ровно той формы, что пак построил для `ContractVersion` (`internal/gates/contract_test.go` читает канон, а не копию числа), либо число уходит из прозы | open | приёмка P8-FIX (посадка мутации оркестратором №18) |
| PD-374 | doc | info | `docs/STACK_DECISIONS.md` «Гейты батареи», `internal/runner/systemd_test.go` `systemdOrSkip`, `Makefile` цель `check` | **Рецепт объявляет у батареи ДВА гейта и ждёт «скипов 0» — а условий три, и третье не названо.** Кроме `TM_PLATFORM_TEST_DSN` и пары `TM_PLATFORM_TEST_ENGINE_BIN`/`_BOOK_TEMPLATE` есть `systemdOrSkip`: без ДОСТИЖИМОГО пользовательского менеджера systemd три теста `internal/runner` скипаются. Поймано пере-прогоном батареи при приёмке: на этом хосте `/run/user/1000` не существует (сессия logind не поднята), поэтому «скипов 0» недостижимо в принципе — `make check` дал 18 пакетов, exit 0, линтер 0 issues и **3 скипа**. Мимо: сама цель `check` печатает над списком скипов «set TM_PLATFORM_TEST_DSN», отправляя читателя к ручке, которая тут ни при чём. Отчёт пака честен и это подтверждает — он мерил отдельно и получил 0, что верно на хосте с живым менеджером. Лечение: назвать третий гейт в рецепте вместе с двумя и не обещать «скипов 0» без него; заодно сделать сообщение цели `check` не называющим одну переменную из трёх | open | приёмка P8-FIX (пере-прогон батареи оркестратором №18) | | PD-374 | doc | info | `docs/STACK_DECISIONS.md` «Гейты батареи», `internal/runner/systemd_test.go` `systemdOrSkip`, `Makefile` цель `check` | **Рецепт объявляет у батареи ДВА гейта и ждёт «скипов 0» — а условий три, и третье не названо.** Кроме `TM_PLATFORM_TEST_DSN` и пары `TM_PLATFORM_TEST_ENGINE_BIN`/`_BOOK_TEMPLATE` есть `systemdOrSkip`: без ДОСТИЖИМОГО пользовательского менеджера systemd три теста `internal/runner` скипаются. Поймано пере-прогоном батареи при приёмке: на этом хосте `/run/user/1000` не существует (сессия logind не поднята), поэтому «скипов 0» недостижимо в принципе — `make check` дал 18 пакетов, exit 0, линтер 0 issues и **3 скипа**. Мимо: сама цель `check` печатает над списком скипов «set TM_PLATFORM_TEST_DSN», отправляя читателя к ручке, которая тут ни при чём. Отчёт пака честен и это подтверждает — он мерил отдельно и получил 0, что верно на хосте с живым менеджером. Лечение: назвать третий гейт в рецепте вместе с двумя и не обещать «скипов 0» без него; заодно сделать сообщение цели `check` не называющим одну переменную из трёх | open | приёмка P8-FIX (пере-прогон батареи оркестратором №18) |
| PD-6 | hardening | info | `internal/auth/csrf.go:51` | GET освобождён от CSRF (верно), но SSE-хендшейк — GET с амбиентной кукой: origin-чек хендшейка потока (STACK §5) не покрыт ничем. Закрыть при постройке SSE (P1) | open | приёмка P0 (security-линза) | | PD-6 | hardening | info | `internal/auth/csrf.go:51` | GET освобождён от CSRF (верно), но SSE-хендшейк — GET с амбиентной кукой: origin-чек хендшейка потока (STACK §5) не покрыт ничем. Закрыть при постройке SSE (P1) | open | приёмка P0 (security-линза) |
| PD-23 | hardening | info | `internal/pgstore/migrations/00001_identity.sql` | Журнал входов растёт без ретенции и чистится только каскадом при удалении аккаунта. Нужен свип по возрасту (год?) — вопрос политики, не кода | open | самопроверка P1 | | PD-23 | hardening | info | `internal/pgstore/migrations/00001_identity.sql` | Журнал входов растёт без ретенции и чистится только каскадом при удалении аккаунта. Нужен свип по возрасту (год?) — вопрос политики, не кода | open | самопроверка P1 |

View file

@ -60,8 +60,14 @@
## 3. Критерии приёмки сессии (Definition of Done) ## 3. Критерии приёмки сессии (Definition of Done)
1. `make check` зелёный офлайн; с `TM_PLATFORM_TEST_DSN` — та же батарея плюс живая схема 1. `make check` зелёный офлайн; скипы названы вслух. ⚠ **«Скипов ноль» требует ТРЁХ условий, а не
(порядок подъёма PG без root — `STACK_DECISIONS.md`); скипы названы вслух и их ноль при DSN. одного** (испр. 22.08 — прежняя редакция называла только DSN и позволяла объявить приёмку
выполненной с молча пропущенной третью батареи): `TM_PLATFORM_TEST_DSN` (схема) ·
`TM_PLATFORM_TEST_ENGINE_BIN` + `TM_PLATFORM_TEST_BOOK_TEMPLATE` (живой рендер конфигурации) ·
ДОСТИЖИМЫЙ пользовательский менеджер systemd (`internal/runner`, `systemdOrSkip` — на хосте без
logind-сессии три теста скипаются и «скипов 0» недостижимо в принципе, `PD-374`). Условие, которое
на этом хосте не выполнено, сессия НАЗЫВАЕТ вместе с числом скипов — «зелёная батарея» без него не
значит ничего. Порядок подъёма PG без root — `STACK_DECISIONS.md`.
2. Каждый деливерабл проверен ИСПОЛНЕНИЕМ (сервер поднят и опрошен, миграции применены дважды, 2. Каждый деливерабл проверен ИСПОЛНЕНИЕМ (сервер поднят и опрошен, миграции применены дважды,
констрейнты сработали поимённо, супервизор гонял настоящий процесс) — не чтением. констрейнты сработали поимённо, супервизор гонял настоящий процесс) — не чтением.
3. Заявленное в отчёте свойство несущего пути ОБЯЗАНО быть запинено тестом, который ловит мутацию 3. Заявленное в отчёте свойство несущего пути ОБЯЗАНО быть запинено тестом, который ловит мутацию

View file

@ -103,6 +103,18 @@ hosted IdP (связка PII + доступность, а свою сессию
## 3. Стандарты и стек: что взять, что не брать ## 3. Стандарты и стек: что взять, что не брать
> ⚠⚠ **ВЕРХНИЙ СЛОЙ 22.08 — ЧИТАТЬ ПРЕЖДЕ ВСЕГО НИЖЕ. По `sqlc` действует НЕ то решение, что записано
> в этом разделе:** владелец 20.08 сказал **БЕРЁМ** (D39.153 п.6а), а 22.08 уточнил — **отдельной
> сессией, не внутри содержательного пака** (D39.154 п.10): правок много, мешать их с другой работой
> нельзя. Граница взятия НАЗВАНА паком P8-FIX и она узкая: пять файлов без единой склейки —
> `credits.go`(15) · `identity.go`(13) · `idempotency.go`(7) · `sessions.go`(5) · `observe.go`(1) =
> **41 запрос**; остальное (25 склеек из 147, 15 фрагментов-констант, read-модель) для sqlc
> недостижимо по построению. Носитель работы — `BACKLOG.md` **П-19**. Отказ P7 ниже остаётся как
> история пака, а не как действующее решение. ⚠ Тем же паком построена ЗАМЕНА того, ради чего sqlc
> звали: гейт `pgstore.TestEverySQLStatementParsesAgainstTheMigratedSchema` планирует ЖИВЫМ Postgres
> каждый собранный запрос пакета против мигрированной схемы — то есть покрывает и склейки, которых
> sqlc не видит, и именно в них случились оба рантайм-падения зоны.
>
> ⚠ **ПЕРЕ-ПОДПИСАНО D39.132 п.2б** (баннер «ратификация за оркестратором» снят как отработанный): > ⚠ **ПЕРЕ-ПОДПИСАНО D39.132 п.2б** (баннер «ратификация за оркестратором» снят как отработанный):
> `oapi-codegen` — КАНДИДАТ, решение за паком, который его берёт; `sqlc` привязан к P7; факт про River > `oapi-codegen` — КАНДИДАТ, решение за паком, который его берёт; `sqlc` привязан к P7; факт про River
> поправлен. **Решение P7 по обоим — НЕ БРАТЬ, с доводом, а не молчанием:** > поправлен. **Решение P7 по обоим — НЕ БРАТЬ, с доводом, а не молчанием:**