textmachine/platform/docs/ENGINEERING_STANDARDS.md

120 lines
16 KiB
Markdown
Raw Permalink 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.

# Стандарты разработки и критерии приёмки платформы
> Ратифицировано оркестратором №14 по решению владельца 04.08: платформа считает токены пользователей
> и держит их логины — «ошибки недопустимы»; качество и отказоустойчивость выше скорости фич.
> Каждый промт платформенной сессии обязан ссылаться на этот документ; отступление = пинг, не тихая девиация.
> ✅ **РЕВЬЮ-ШАПКА (оркестратор №22, 06.09) — ДОКУМЕНТ ЖИВОЙ И НЕСУЩИЙ, РЕЗАТЬ НЕЧЕГО.** Прочитан целиком:
> нормы зоны плюс критерии приёмки (§3 Definition of Done), почти каждое утверждение с замером и ценой.
> ⚠ **Поправки, внесённые на месте позже 04.08 — перечнем, потому что слово «единственная» в первой редакции
> этой шапки было неверно (испр. по ревью 06.09):** §3 — норма изоляции мутаций `D39.113` (копия дерева ВМЕСТЕ с
> каноном контракта) · §3 — вердикт посадки судится по ДЕЛЬТЕ против чистой базы той же копии (`PD-395`) · §3 —
> греп открытых строк регистра по ПОЛНЫМ путям, не по именам (замер 27.08, `D39.159` п.7) · §3 — дисциплина
> доказательства грепом: обрезанный вывод читается как отсутствие совпадения (перенесено 02.09). И крупнейшая: §1 — **`D39.84` больше НЕ запрещает деньги на
> проводе**, половину запрета отозвал владелец 05.09 (`D39.196` п.2, эррата 05.09-а). Живой остаток запрета
> назван там же четырьмя пунктами; отдельная и НЕ отозванная норма — **денег нет в INFO-логах** (`PD-99`).
> ⚠ Ссылка на этот файл обязана стоять в КАЖДОМ платформенном промте: из семи прежних сослались пять.
## 1. Рамка
- **Несущие пути** — auth/сессии/CSRF, всё денежное (usage, лимиты, биллинг), ингест/супервизия движка,
миграции. Изменение несущего пути = «тяжёлый» класс: полный цикл приёмки §3, без исключений.
- **Сторонние тулы прежде самописного** (решение владельца 04.08): проверенные библиотеки предпочитаются
своему коду везде, КРОМЕ похода в ядро переводов (tmctl зовётся процессом, обвязка своя) и мест, где
stdlib и есть индустриальный стандарт (net/http, database/sql-слой через pgx). Новая зависимость —
строка в `STACK_DECISIONS.md` с датой релиза и «зачем», ратифицирует оркестратор.
-**D39.84 БОЛЬШЕ НЕ ЗАПРЕЩАЕТ ДЕНЬГИ НА ПРОВОДЕ — половину запрета ОТОЗВАЛ ВЛАДЕЛЕЦ 05.09**
(`D39.196` п.2, эррата 05.09-а; дословно: «я такого правила не ставил, надо от него избавляться»).
Наружу ВЫХОДЯТ деньгами: баланс аккаунта, потолок заказа, холд и оценка заказа. Живой остаток
запрета — ПТ-33/ПТ-35 в четырёх пунктах, и он НЕ отменялся: цены моделей · стоимость стадий ·
стоимость ВЫЗОВОВ (что списано по факту) · структура НАШИХ расходов. ⚠ И различение `D39.203`, без
которого пункт про вызовы читается неверно: наружу идёт ДО-ВЫЗОВНАЯ ОЦЕНКА (резервация до звонка),
а запрет написан про СТОИМОСТЬ — сеттлмент не выходит. **Отдельная и НЕ отозванная норма: денег нет
в INFO-логах ни в каком виде** (`PD-99`) — её эта строка держит по-прежнему ·
D39.85 (шов: NDJSON → апсерт `(engine_run_id, seq)` → SSE из Postgres; живой SQLite движка не читать) ·
ПТ-34 (noindex/no-store на всём) · языко-агностичность.
## 2. Базовые стандарты (проверяются каждой приёмкой)
**Безопасность** — базовая линия OWASP **ASVS 5.0.0, целевой L2** (главы, релевантные зоне: V6
аутентификация · V7 сессии · V10 OAuth/OIDC) + OWASP API Security Top 10 2023 (API1 BOLA = проверка
владения книгой на каждом `/v0`-маршруте; API4 = класс PD-2). Что берём и чего не берём из
инструментов — `PLATFORM_DIRECTION.md` §3. Конкретика:
- Сессии: одна серверная, opaque-токен ≥256 бит из crypto/rand, в БД только хеш, немедленный отзыв,
два срока (idle скользит, absolute нет). Плейнтекст-токен не персистится, не логируется и живёт
в памяти только окно обработки запроса (решение владельца 04.08).
- Секреты — только из окружения/секрет-менеджера; в репо и в логи не попадают НИ на каком уровне.
- Ввод: только параметризованные запросы; каждый body-принимающий хендлер несёт `http.MaxBytesReader`
и дедлайн (гейт PD-2 регистра); закрытые словари валидируются на входе И констрейнтами БД.
- Ответы: problem+json; внутренности и оракулы (unknown-vs-expired токен) на провод не текут;
заголовки ПТ-34+nosniff+no-referrer — мидлварью на всё, не дисциплиной хендлера.
- `govulncheck` (`make vuln`) — перед каждым лендингом; красный = блокер лендинга.
**Отказоустойчивость:**
- Ингест идемпотентен (`(engine_run_id, seq)`, high-water mark в одной транзакции с эффектом);
at-least-once — норма, дубль — не ошибка.
- Graceful shutdown с реальным дренажем; пул с лимитами; транзиентные ошибки БД — ретраи с джиттером;
миграции идемпотентны, down-путь существует и гоняется тестом.
- Отказ зависимости — деградация с диагностикой, не тишина: проглоченная ошибка без лога и без
комментария-обоснования = дефект (класс PD-5/PD-16).
**Наблюдаемость** — базовая линия **практики именования Prometheus** (базовые единицы: секунды и
байты; `_total` у счётчиков; единица в имени, не в лейбле) + **четыре золотых сигнала** SRE на
вопрос «что мерить» (`PD-115`). Конкретика:
- структурные логи (route-pattern, не сырой путь — id пользователя/книги в логи не текут),
`X-Request-Id` на каждом ответе, ошибки видимы на своём уровне;
- метрики отдаются в формате Prometheus на ОТДЕЛЬНОМ слушателе (`STACK_DECISIONS` §24); лейбл несёт
паттерн маршрута, никогда путь — иначе это и неограниченная кардинальность, и библиотека
пользователя в индексе оператора;
- Money-числа — только в приватных таблицах, ни в логах, ни в ответах, ни в метриках (кроме
P-5-статуса процентами);
- ⚠ оси **ops** (релиз/откат, ёмкость, восстановление) и **конфигурация** внешнего эталона
по-прежнему не имеют — PD-115 открыт ими.
**Контракт-первичность:** поверхность = `docs/architecture/14-api-contract/openapi.yaml`; расхождение
кода со спекой = дефект чей-то один: либо спека правится через ратификацию, либо код. Генерация
типов/стабов из спеки предпочтительнее ручного дрифта (`oapi-codegen` — КАНДИДАТ, решение за паком,
который его возьмёт: `PLATFORM_DIRECTION.md` §3).
## 3. Критерии приёмки сессии (Definition of Done)
1. `make check` зелёный офлайн; скипы названы вслух. ⚠ **СКОЛЬКО условий и какие — смотреть в
`docs/STACK_DECISIONS.md`, раздел «Гейты батареи», он единственный носитель** (число жило в трёх
файлах, они разошлись, и сессия, честно исполнившая этот пункт по устаревшей копии, объявляла
приёмку выполненной при красном тесте). Условие, которое на этом хосте не выполнено, сессия
НАЗЫВАЕТ вместе с числом скипов — «зелёная батарея» без него не значит ничего. Порядок подъёма
PG без root — там же.
2. Каждый деливерабл проверен ИСПОЛНЕНИЕМ (сервер поднят и опрошен, миграции применены дважды,
констрейнты сработали поимённо, супервизор гонял настоящий процесс) — не чтением.
3. **Мутации сажаются в КОПИИ дерева, и копия несёт КАНОН.** Норма изоляции — `D39.113`: правка в
рабочем дереве теряет незакоммиченную работу параллельных сессий, поэтому посадка идёт в копию, и
каждому агенту своя, вместе со своим стендом (база, порты, каталоги). ⚠ **Копировать надо ВМЕСТЕ с
каноном контракта**, иначе копия структурно красная и вердикты лгут:
`cp -a --parents platform docs/architecture/14-api-contract <куда>/`. Голая `cp -a platform` даёт
постоянный `FAIL` у `gates.TestTheAnnouncedContractVersionIsTheOneTheCanonRatified` — гейт читает
канон по пути ВЫШЕ корня модуля (`internal/gates/contract_test.go:14`), — и цена не в потерянном
времени: базовый красный того же теста МАСКИРУЕТ дельту, поэтому мутация самой `ContractVersion`
выходит «выжившей» и даёт ЛОЖНУЮ находку «константа не запинена». С каноном копия зелёная.
Вторая половина того же правила: **вердикт посадки судится по ДЕЛЬТЕ против чистой базовой линии
ТОЙ ЖЕ копии и по ТОПИЧНОСТИ упавшего теста, а не по цвету батареи** — иначе известный флейк даёт
ложное «пойман» и тихо теряет находку о недостающем пине (`PD-395`).
4. Заявленное в отчёте свойство несущего пути ОБЯЗАНО быть запинено тестом, который ловит мутацию
этого свойства; сессия сама называет в отчёте, какой тест что пинит. Приёмка оркестратора сажает
СВОИ мутации (урок P0: «в БД только хеш» было истинно, но не запинено — посадка выжила).
5. Адверсариальная самопроверка (author≠reviewer) до сдачи; найденное — в отчёт, не молча.
6. Новые находки — строками в `DEFECT_REGISTER.md` тем же деревом; закрытие дефекта — коммит + пинящий тест.
7. Дерево не коммитится сессией — лендит оркестратор после адверсариальной приёмки
(для несущих путей — с воркфлоу-верификаторами и посадками).
8. **Построил механизм — грепни ОТКРЫТЫЕ строки регистра по своим файлам.** Каждое совпадение
получает диспозицию в отчёте: закрыть, сузить, пере-формулировать или оставить с причиной.
Класс «строка `open`, а лекарство уже в дереве» стоил зоне порядка 10% открытых строк и
отправлял следующий пак чинить построенное. Греп — по ПОЛНЫМ путям, не по именам (замер 27.08:
по именам 22 совпадения, почти все ложные — `main.go`/`runner.go`/`config.go` есть и в
`platform/`, и в `backend/`; по полным путям — 2, оба настоящие).
Ратифицировано `D39.159` п. 7. ⚠ Правило симметрично: якорь строки, УБИТЫЙ твоим переездом,
чинит тот, чей переезд его убил, и называет причину — не следующий читающий пак.
**Дисциплина самого доказательства грепом** (урок сдачи P8-REVIEW, перенесён сюда 02.09 по
просьбе приёмки): обрезанный по ширине вывод грепа читается как ОТСУТСТВИЕ совпадения — так зона
объявила «в промте совпадений ноль» на строке, которую греп нашёл. Предъявляй ЧИСЛО (`grep -c`)
или полные строки, а не «я посмотрел».