textmachine/platform/README.md

93 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# platform — control plane (SaaS-слой)
Зона записи сессии «Платформа». P0 собран 04.08 (скелет: HTTP · сессии · схема read-model ·
интерфейс ингеста), P1 — 05.08 (вход через OIDC · кредитный леджер · админ-CLI · деплой-юнит ·
закрытие регистра дефектов). Направление зоны — `docs/PLATFORM_DIRECTION.md`, критерии приёмки —
`docs/ENGINEERING_STANDARDS.md`, дефекты — `docs/DEFECT_REGISTER.md`, зонный журнал —
`docs/platform-PROGRESS.md` (весь прогресс зоны здесь, решение владельца 04.08), стек —
`docs/STACK_DECISIONS.md`.
Батарея зоны: `make check` (build · vet · fmt · lint · test -race). Тесты со схемой требуют
`TM_PLATFORM_TEST_DSN` (как поднять Postgres без root — `docs/STACK_DECISIONS.md`); без него они
пропускаются, и `check` называет пропуски вслух. `make vuln` и `make fuzz` — отдельными целями.
Бинари: `cmd/tmplatformd` (сервис) и `cmd/tmplatformctl` (админ: гранты, КОРРЕКТИРОВКИ (`adjust`), баланс, журнал входов,
отзыв сессий, дев-интейк `book add`, и `exit-marker`его зовёт systemd на конце прогона). Деплой — `deploy/`.
Метрики — отдельным слушателем (`TM_PLATFORM_METRICS_ADDR`, дефолт `127.0.0.1:9464`), формат
Prometheus; на контрактную поверхность они не выходят и наружу не привязываются (`docs/STACK_DECISIONS.md` §24).
Эффективная конфигурация печатается на старте с источником каждой настройки; секреты и денежные
суммы — фактом наличия, без значения (PD-114).
**Прогон — транзиентный systemd-юнит, а не ребёнок сервиса** (D39.106). Установка требует
`loginctl enable-linger`, иначе не стартует ни один прогон; почему именно так и что было
измерено — `docs/STACK_DECISIONS.md` §1520.
## ⚠ Git и зона (читать ДО первой строки кода)
**Платформенная сессия не коммитит — лендит оркестратор.** Канон — `../CLAUDE.md`, §Гардрейлы; продублировано здесь, потому что зона живёт своим онбордингом. Направление зоны (вход · деньги · стандарты · скорость) — `docs/PLATFORM_DIRECTION.md`; стандарты и критерии приёмки — `docs/ENGINEERING_STANDARDS.md`; дефекты и уязвимости — `docs/DEFECT_REGISTER.md` (каждая находка получает строку ДО закрытия).
1. **Писать только внутрь `platform/`.** Ничего за её пределами — ни `docs/`, ни `backend/`, ни `frontend/`, ни корневых файлов. Нужна правка вне зоны — пинг владельцу, её сделает оркестратор.
2. **Чужие незакоммиченные файлы в дереве не трогать**: параллельные сессии — норма.
3. **НИКАКИХ** `git add -A`, `reset --hard`, `amend`/`rebase`, перезаписи истории и `checkout` поверх грязного дерева.
4. ⚠ Ревью-гард модулей (D39.85): путь Go-модуля платформы **никогда** не вкладывать под путь движка — иначе он получит доступ к `backend/internal/*` по правилу префикса.
## Что здесь будет
Сервис между фронтом и движком перевода. Всё, что относится к ПОЛЬЗОВАТЕЛЯМ и не относится
к переводу:
- аутентификация и аккаунты — **есть (P1)**: вход через OIDC даёт только СОБЫТИЕ входа, сессия
своя; ключ личности `(provider, subject)`, почта не ключ. Оплаты нет и в бете не будет
(владелец 05.08): аккаунты живут на кредитном балансе, фри-тир — запись `grant` в леджер;
- библиотека книг: чья книга, права доступа, хранение исходников и экспортов — **приём есть (P5)**:
`POST /v0/books` принимает multipart потоково со своим потолком тела и своим дедлайном чтения,
кладёт исходник в каталог книги под `TM_PLATFORM_BOOKS_DIR` и ведёт книгу по статусам
`uploading → parsing → not_started | rejected`; разбор — $0-команда движка `tmctl manifest`.
⚠ Кто кладёт стартовый `book.yaml` в каталог новой книги — открытый вопрос на ратификации
(D39.110 §2b): без него разбор честно отказывает, шов назван `books.ErrNotProvisioned`;
- учёт денег **на пользователя****схема и операции есть (P1)**: append-only леджер в целых
микро-долларах, резервации, кэш баланса с инвариантом `balance == SUM(ledger)`. Защита прогона —
холд ДО спавна плюс книжный потолок движку (жёсткий стоп исполняет движок). ⚠ Потолок, который
получает движок, — это НАКОПЛЕННЫЙ потолок КНИГИ (`committed + reserved` за всю её историю плюс
купленный прирост), а не бюджет прогона: платформа переводит одно в другое сама (D39.122);
- остановка и продолжение перевода — **есть (P5)**: `POST /v0/runs/{id}/stop` пишет НАМЕРЕНИЕ стопа
в Postgres до сигнала (иначе стоп и авария — один и тот же выход движка, PD-152) и просит systemd;
`/resume` открывает новую попытку с ОСТАТКОМ бюджета прогона, переиспользуя механику
перезапуска реконсилятора;
- очередь задач и запуск воркеров, статусы прогонов, ретраи — **есть (P4)**: River на том же
Postgres; задание очереди выдаёт только РАЗРЕШЕНИЕ стартовать, а жизнь прогона ведёт
реконсилятор, который читает мир (Postgres · журнал книги · маркер выхода) и не ждёт процесса;
- SSE-поток прогресса во фронт (⚠ денежные суммы на провод и на экран НЕ идут — D39.84; пользователь
видит ОСТАТОК процентом — окон со сбросом больше нет, владелец 05.08).
## Чего здесь НЕ будет
Перевода. Движок (`../backend/`) остаётся как есть: один процесс на книгу, свой SQLite под
эксклюзивным flock (`store.Open` — «один процесс владеет файлом проекта»). Платформа его
ЗАПУСКАЕТ как воркер, а не поглощает. Причина та же, по которой движок не ветвится по паре
языков: пользователи и квоты ничего не меняют в проводе запроса, значит им нечего делать в
кодовой базе, где каждый байт свёрнут в снапшот-хеш.
## Известное требование к движку (не забыть)
**Глобальный брокер конкурентности.** `pipeline/ratelimit.go` строит рейт-гарды НА ПРОГОН,
а лимит провайдера — на весь аккаунт. N параллельных пользователей = N независимых гардов
против общего лимита (в комментарии там зафиксировано: mistral валит ~48% вызовов под
параллелизмом). Воркер должен отпрашиваться у платформы перед вызовом. Это единственная
по-настоящему новая механика на стыке.
## Стек
Пины, даты релизов и обоснования — [`docs/STACK_DECISIONS.md`](docs/STACK_DECISIONS.md) (зонный,
live-сверка 0405.08); общая записка по обоим новым сервисам — `../frontend/docs/STACK_DECISIONS.md` §5.
Коротко: Go 1.26.4 в `go.mod` (тулчейн сборки ≥1.26.5) · стандартный `net/http` + `ServeMux` без
роутер-библиотеки · PostgreSQL 18 · pgx v5.10.0 · goose v3.27.3 · очередь River v0.42.0 на том же
Postgres (запинена, ещё не подключена — П-3) · вход `x/oauth2` v0.36.0 + `go-oidc/v3` v3.20.0 ·
`x/time` v0.15.0 для лимита на `/auth/login` · `govulncheck` отдельной целью.
**Redis не заводим нигде** — зафиксировано как архитектурное «нет».
Прогресс наружу — SSE, события **пушит воркер**, а не фронт опрашивает read-model.
Аутентификация — одна серверная сессия в Postgres, два способа предъявления: `__Host`-кука для
браузера и `Authorization: Bearer` для десктопа и CLI; эндпоинты про куки не знают ничего.