120 lines
16 KiB
Markdown
120 lines
16 KiB
Markdown
# Стандарты разработки и критерии приёмки платформы
|
||
|
||
> Ратифицировано оркестратором №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`)
|
||
или полные строки, а не «я посмотрел».
|