16 KiB
Стандарты разработки и критерии приёмки платформы
Ратифицировано оркестратором №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)
-
make checkзелёный офлайн; скипы названы вслух. ⚠ СКОЛЬКО условий и какие — смотреть вdocs/STACK_DECISIONS.md, раздел «Гейты батареи», он единственный носитель (число жило в трёх файлах, они разошлись, и сессия, честно исполнившая этот пункт по устаревшей копии, объявляла приёмку выполненной при красном тесте). Условие, которое на этом хосте не выполнено, сессия НАЗЫВАЕТ вместе с числом скипов — «зелёная батарея» без него не значит ничего. Порядок подъёма PG без root — там же. -
Каждый деливерабл проверен ИСПОЛНЕНИЕМ (сервер поднят и опрошен, миграции применены дважды, констрейнты сработали поимённо, супервизор гонял настоящий процесс) — не чтением.
-
Мутации сажаются в КОПИИ дерева, и копия несёт КАНОН. Норма изоляции —
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). -
Заявленное в отчёте свойство несущего пути ОБЯЗАНО быть запинено тестом, который ловит мутацию этого свойства; сессия сама называет в отчёте, какой тест что пинит. Приёмка оркестратора сажает СВОИ мутации (урок P0: «в БД только хеш» было истинно, но не запинено — посадка выжила).
-
Адверсариальная самопроверка (author≠reviewer) до сдачи; найденное — в отчёт, не молча.
-
Новые находки — строками в
DEFECT_REGISTER.mdтем же деревом; закрытие дефекта — коммит + пинящий тест. -
Дерево не коммитится сессией — лендит оркестратор после адверсариальной приёмки (для несущих путей — с воркфлоу-верификаторами и посадками).
-
Построил механизм — грепни ОТКРЫТЫЕ строки регистра по своим файлам. Каждое совпадение получает диспозицию в отчёте: закрыть, сузить, пере-формулировать или оставить с причиной. Класс «строка
open, а лекарство уже в дереве» стоил зоне порядка 10% открытых строк и отправлял следующий пак чинить построенное. Греп — по ПОЛНЫМ путям, не по именам (замер 27.08: по именам 22 совпадения, почти все ложные —main.go/runner.go/config.goесть и вplatform/, и вbackend/; по полным путям — 2, оба настоящие). РатифицированоD39.159п. 7. ⚠ Правило симметрично: якорь строки, УБИТЫЙ твоим переездом, чинит тот, чей переезд его убил, и называет причину — не следующий читающий пак. ⚠ Дисциплина самого доказательства грепом (урок сдачи P8-REVIEW, перенесён сюда 02.09 по просьбе приёмки): обрезанный по ширине вывод грепа читается как ОТСУТСТВИЕ совпадения — так зона объявила «в промте совпадений ноль» на строке, которую греп нашёл. Предъявляй ЧИСЛО (grep -c) или полные строки, а не «я посмотрел».