Прежняя git-история утрачена при переносе проекта на машину владельца (снапшот без .git). Хэши коммитов в docs/reports/* относятся к утраченной истории. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
759 lines
86 KiB
Markdown
759 lines
86 KiB
Markdown
# Литания перед стартом нового проекта
|
||
|
||
> **Как применять:** положи этот файл в корень нового проекта и дай Claude Code вместе
|
||
> с первой задачей: «Прочитай prestart-litany.md, определи профиль проекта, разверни
|
||
> описанную систему триажем под этот проект, затем приступай к задаче: …».
|
||
>
|
||
> **Источники:** BAZA.CRM (ERP, 22 модуля, Django+React+Celery, боевой прод), EventPlan
|
||
> (геометрический SaaS, FastAPI+React), АПЕКС-М (IoT: FastAPI+React+ESP32, боевой VPS) —
|
||
> плюс аудит всех 17 проектов рабочей папки от 2026-07-26 (Ad-Tycoon, REC dimmer,
|
||
> мониторинг, HS+auth, лендинги и др. — см. litany-audit-2026-07-26.md). Каждая грабля
|
||
> в разделе VI оплачена реальным инцидентом — источник указан в скобках.
|
||
>
|
||
> **Устройство:** ядро сформулировано стек-нейтрально; конкретика стека — в примерах
|
||
> *курсивом* и тегах `[Docker]` `[React]` `[Windows]` `[железо]`… Неприменимое к твоему
|
||
> профилю и стеку отсекается триажем (раздел 0), а не игнорируется молча.
|
||
>
|
||
> **Ссылки на литанию — только слагом** (`` `gotcha-…` `` у граблей, названием пункта у
|
||
> правил), никогда номером: перенумерация родительского документа тихо ломает все
|
||
> производные правила *(EventPlan: ссылки «V.15», «VI.10» из rules/ и ADR-0004 били в
|
||
> пустоту уже через 2 часа после финальной правки литании)*.
|
||
|
||
---
|
||
|
||
## 0. Развёртывание (порядок строгий)
|
||
|
||
### 0.1. Профиль проекта — первый вопрос, до триажа
|
||
|
||
| Профиль | Признаки | Примеры |
|
||
|---|---|---|
|
||
| **D — документ** | Артефакт = один документ, кода нет | инструкция, руководство |
|
||
| **S — статика** | Один экран/лендинг, нет БД и пользователей; правит не-программист | лендинг, брендбук |
|
||
| **A — одиночный артефакт** | Скетч, скрипт, утилита; один файл-проект | генератор, трей-клиент |
|
||
| **M — продукт без своего прода** | Код, тесты, релизы — но нет своего сервера/БД/денег | игра, расширение, десктоп |
|
||
| **XL — полное ядро** | Прод, БД, деньги, пользователи, контейнеры, железо в поле | ERP, SaaS, IoT-платформа |
|
||
|
||
Без профилей мелкие проекты выпадают из процесса целиком, а не частично: 6 из 17
|
||
проектов папки жили вообще без git, один версионировался зипами `(4)…(8).zip`.
|
||
|
||
### 0.2. Минимальное ядро — действует ВСЕГДА, даже для D и S
|
||
|
||
1. **git с первого файла** + `.gitattributes` (`* text=auto eol=lf`) и `.gitignore` —
|
||
ДО первого `git add`. Никаких копий-папок и зипов как версий.
|
||
2. **Секрет-гигиена** — включая документацию, примеры команд и **рабочий каталог**:
|
||
секреты вне git всё равно попадают в бэкапы диска, скриншоты и контекст агента,
|
||
получившего доступ к папке *(recovery-коды открытым текстом в корне папки проектов)*.
|
||
3. **Опасные действия письменно**; на боевом — только команды, выполняет человек.
|
||
4. **Бэкап перед перезаписью боевого файла** (ротация N копий).
|
||
5. **Версия и дата на артефакте**: подвал сайта, штамп «версии на момент составления»
|
||
в доке, `v1.0 · дата` в README.
|
||
6. **Плейсхолдеры помечены** (`?`, TODO владельцу) и сверяются перед выкладкой.
|
||
7. **Мини-ритуал закрытия**: обновить дату, строка «что сделали», всё отложенное —
|
||
письменно в момент откладывания.
|
||
8. **Развилки — через AskUserQuestion**, очевидные дефолты — принять и озвучить.
|
||
9. **Один ADR** даже в S — на 5 строк, но с обязательным «Компромиссом».
|
||
|
||
### 0.3. Профильные добавки (сверх ядра)
|
||
|
||
- **D:** SoT — исходник (HTML/markdown), PDF — производный артефакт, пересобирается
|
||
вместе с ним; раздел «версии ПО на момент составления»; нумерация до третьего уровня
|
||
(чтобы ссылаться на шаг в переписке); визуальные классы опасности (danger/warn/ok)
|
||
вместо прозы; владелец и дата пересмотра.
|
||
- **S:** контент отделён от кода **одним файлом** («правишь это, остальное не трогай»);
|
||
индексация закрыта по умолчанию (`Disallow: /` + noindex), открытие — сознательный
|
||
акт по чеклисту (robots, аналитика, schema.org, PageSpeed, Вебмастер/Search Console,
|
||
privacy-policy не заглушка); deny-list «что не выкладывать на прод» (локальный
|
||
редактор, дизайн-канвас); бюджет веса страницы и картинок; документ передачи
|
||
не-разработчику (варианты правки по возрастанию сложности, лимиты хостинга).
|
||
- **A:** один README-витрина: что это · пины/константы **одной таблицей** · как
|
||
собрать · troubleshooting «симптом → причина»; все физические/средовые константы
|
||
в одном месте файла; способ проверить **без** железа/боевого стека + одна строка
|
||
«чего он не проверяет».
|
||
- **M:** + чистое ядро и детерминизм (II), версионирование локального состояния (I),
|
||
адаптер платформы (I), CI-матрица ОС (II), профиль публичного релиза (V), роли (IV).
|
||
- **XL:** литания целиком.
|
||
|
||
### 0.4. Триаж
|
||
|
||
Для D/S/A — «профиль + список отклонений» ≤10 строк в README. Для M/XL — полный,
|
||
каждый пункт → **IN / DEFERRED / OUT** с причиной, итог — одним ADR. Формат вердиктов
|
||
(проверен дважды: EventPlan ADR-0004, АПЕКС ADR-024):
|
||
|
||
| Пункт | Вердикт | Причина / условие |
|
||
|---|---|---|
|
||
| … | IN / DEFERRED / OUT | … |
|
||
|
||
+ обязательный раздел «Компромиссы — что осознанно потеряли». DEFERRED — только
|
||
с условием активации («появятся формы → схемная валидация»), не «потом». Осознанный
|
||
legacy-путь получает зеркальное — **условие снятия** («удалить, когда ни одного
|
||
устройства с plaintext-токеном»), иначе он вечен. Мёртвое правило убивает доверие
|
||
ко всем правилам.
|
||
|
||
### 0.5. Стартовый бриф и вопросы
|
||
|
||
**Вопросы одним заходом** (AskUserQuestion): стек и версии тулчейна, число артефактов
|
||
версии, есть ли фронтенд / воркеры / прод / железо, ОС разработчика. Бриф проекта
|
||
пишется по шаблону, проверенному дважды *(HS+auth PROJECT_BRIEF, REC dimmer bootstrap)*:
|
||
принципы-обязательства → **«Чего НЕ делать»** (явные запреты) → этапы с подтверждением
|
||
человека → «что нужно на выходе» → финал **«ничего не пиши в файлы, пока не согласуем;
|
||
вопросы без ответа в документах — задай списком, не делай допущений»**. Полезный
|
||
парный документ — памятка человеку «как ставить задачу»: шаблон [ЗОНА][ДЕЙСТВИЕ],
|
||
формат баг-репорта, «чего НЕ указывать — уже в CLAUDE.md» *(АПЕКС how_to_ask.md)*.
|
||
|
||
### 0.6. Каркас и финал развёртывания
|
||
|
||
Развернуть каркас из разделов I–V по итогам триажа. Финал: показать пользователю
|
||
Module Map и список созданных правил на ревью → отдельный коммит каркаса → только
|
||
затем первая боевая задача.
|
||
|
||
### 0.7. Обратный поток граблей
|
||
|
||
Копия литании в проекте — read-only архив, её не редактируют. Новые грабли проекта
|
||
пишутся в живой раздел `docs/architecture.md#Грабли проекта` с источником; раз в
|
||
несколько сессий обобщаемое мигрирует в мастер-литанию *(Самолётики: финал «дописывай
|
||
в VI» оказался невыполним — копия заморожена, дописывать было некуда)*.
|
||
|
||
Раздел VI — постоянный чеклист на всю жизнь проекта, не одноразовое чтение.
|
||
|
||
Всё общение и документация — на русском. Идентификаторы кода, пути API, хэши и версии
|
||
не переводить — по ним ищут в коде; жаргон — с оригиналом в скобках при первом
|
||
упоминании *(BAZA d64605c)*.
|
||
|
||
## I. Архитектура — принципы, проверенные боем
|
||
|
||
1. **Модульность:** один модуль/приложение = одна бизнес-область. Cross-module общение —
|
||
только события или явные сервис-вызовы через абстракции; прямой импорт внутренностей
|
||
чужого домена запрещён. Каждый новый модуль в момент создания попадает в Module Map (III.1).
|
||
2. **Сервисный слой:** контроллер/view/handler = parse → call service → respond, ноль
|
||
логики. Модель = структура данных. Вся бизнес-логика в сервисах; при росте делить на
|
||
подмодули СРАЗУ (реэкспорт сохраняет внешний контракт), не ждать god-файла.
|
||
3. **Статусы = конечный автомат:** защищённое поле + именованные переходы с
|
||
source/target/permission + side-effects внутри перехода. Прямое присваивание статуса
|
||
запрещено — это обход прав, аудита и эффектов.
|
||
4. **Аудит-след обязателен:** каждая транзакция создаёт запись в журнале через **единую
|
||
точку применения эффекта**. Мутация в обход журнала — критическая ошибка. Контракт
|
||
логирования — таблицей «событие → что логировать» с именованным логгером, иначе
|
||
«аудит обязателен» выполняется на глаз *(BAZA CLAUDE.md)*.
|
||
5. **Soft delete** для доменов с документами/деньгами, двухуровневый: документы/деньги =
|
||
`is_active + deleted_at`; справочники = только `is_active`. Физический delete запрещён;
|
||
связи на audit-модели — PROTECT.
|
||
6. **Деньги = Decimal** (12,2) или целые минорные единицы. Float рядом с money-полями —
|
||
блокирующая ошибка. Общий принцип: **число без объявленной единицы — баг, ждущий
|
||
повода**; единицы измерения и система координат — часть контракта, объявляются
|
||
письменно один раз *(EventPlan: «единицы — миллиметры», СК от самой длинной стены)*.
|
||
Есть стандартный формат обмена для домена (GeoJSON, iCal, ISO-коды) — брать его,
|
||
не изобретать: бесплатные валидаторы и общий язык со всеми слоями.
|
||
7. **Миграции схемы append-only:** существующие не редактировать; каждая data-миграция
|
||
обратима; большие таблицы — expand → backfill → contract. Механика раннера (glob vs
|
||
реестр, порядок применения, идемпотентность) — в README рядом с миграциями, иначе
|
||
каждая сессия изобретает своё *(АПЕКС)*. У грабли «дрейф миграций» записан не только
|
||
запрет, но и рецепт восстановления *(BAZA: 5 шагов через MigrationLoader)*.
|
||
8. **Внешние вызовы:** только асинхронно (очередь), идемпотентные ключи, retry с backoff,
|
||
верификация вебхуков, audit-лог каждого вызова. Внешняя платформа/SDK (маркетплейс,
|
||
магазин, игровая площадка, платёжка) — за адаптером с **двумя реализациями: боевой и
|
||
Local-заглушкой**; вход в приложение — в try/catch с фолбэком; требования модерации —
|
||
чеклистом ДО релиза *(Ad-Tycoon: PlatformAdapter, «вечный чёрный экран» без фолбэка)*.
|
||
Недоступные из твоей инфраструктуры внешние сервисы — письменно списком, иначе агент
|
||
раз за разом предлагает нерабочее *(АПЕКС: Telegram на RU-VPS, protomaps)*.
|
||
9. **Парные операции симметричны с рождения:** если есть create-эффект — в том же
|
||
коммите пишется cancel-эффект и ТЕСТ на пару (`gotcha-pair-asymmetry`).
|
||
10. **Без хардкода — в меру.** Значение, которое может измениться, повториться или
|
||
зависеть от окружения/клиента — в константу/конфиг/env/данные. Самоочевидные
|
||
единичные значения не выносить. **Доменные параметры** — редактируемые данные
|
||
(сид + переопределение на сущность), не константы; вычислительное ядро принимает их
|
||
параметром и остаётся чистым *(EventPlan ADR-0003)*. То же — **матрица прав и
|
||
фиче-флаги**: данные в БД, редактируемые без релиза *(BAZA)*. Параметры, зависящие
|
||
от экземпляра (калибровки, коэффициенты per-device/per-tenant) — хранятся при
|
||
экземпляре, не в коде `[железо]` *(АПЕКС: NVS, калибровка минимум в 3 точках)*.
|
||
Настроечная константа (таймаут, батч, буфер) — с единицей, формулой следствия и
|
||
направлением компромисса, иначе следующая сессия крутит её вслепую *(VOX: DMA-буферы
|
||
«4 мс; меньше → щелчки; больше → латентность»)*.
|
||
11. **Contract First для многослойных контрактов.** Контракт, потребляемый несколькими
|
||
артефактами — таблица слоёв в CLAUDE.md, изменение обновляет ВСЕ слои одним
|
||
коммитом. Wire-протокол версионируется отдельно от приложений — два разных SoT,
|
||
с письменной матрицей «что бампает протокол» (optional-поле → minor, required/
|
||
переименование → major, багфикс payload не бампает) *(АПЕКС versioning.md)*.
|
||
**Порядок выката — часть контракта:** сервер раньше клиента, новые поля optional
|
||
(expand→contract для провода). Совместимость — **декларация клиента**
|
||
(`api_version_min` в манифесте, клиент сам блокируется), а не блокировка сервером:
|
||
отклонение данных по версии создаёт слепые зоны ровно у проблемных клиентов
|
||
*(АПЕКС ADR-016)*. Агент, меняющий контракт, обязан спросить: «нужно ли обновить
|
||
остальные слои?» Удалил поле из контракта — докажи grep'ом, что его никто не
|
||
читает *(АПЕКС: orphan-читатели top-level `version`)*.
|
||
12. **Docker-first** (если проект контейнерный): ничего не запускается на хосте; базовый
|
||
compose + явные именованные override-файлы; healthchecks на всех сервисах; non-root;
|
||
секреты только env. Осознанное отклонение — строка в реестре долгов «принято
|
||
осознанно» + **явный запрет альтернативной команды** («никогда `docker compose build
|
||
frontend`») — запрет важнее правила: именно его агент нарушит по инерции *(АПЕКС)*.
|
||
13. **Fail-safe: неопределённое состояние = безопасное.** Заглушки нереализованных
|
||
модулей отказывают в безопасную сторону (недописанный чек прав — запрещает,
|
||
недописанный лимит — блокирует, фича-флаг по умолчанию выключен); где отказ
|
||
физически опасен — второй барьер вне кода `[железо]` *(REC dimmer: аппаратная
|
||
подтяжка — ресет/прошивка/Hi-Z = ключи закрыты без участия кода)*. **Деградация
|
||
вместо отключения**, и «плохие данные» ≠ «нет данных»: политики разные, обе
|
||
спроектированы явно; **нет данных — не гадать** *(REC dimmer: отказ датчика →
|
||
вентилятор на максимум, лимиты НЕ применяются)*. Отсутствие показателя ≠ ноль:
|
||
прочерк, а не 0 *(tray-monitor; мониторинг: `absent()` — отдельный алерт)*.
|
||
Fail-open/fail-closed выбирается **на каждую точку отдельно, по длительности окна
|
||
риска** *(АПЕКС ADR-017: HTTP fail-open — окно один запрос; WS fail-closed — канал
|
||
жил бы часами)*; код ошибки, который UI трактует как команду, не получает второго,
|
||
инфраструктурного смысла. Отказ канала связи ≠ отказ узла — различие моделируется
|
||
в протоколе, эскалация зависит от того, что известно *(АПЕКС ADR-011)*.
|
||
14. **Персистентное состояние вне транзакционной БД** (сейв, localStorage, NVS, файл
|
||
конфига, кэш) — та же дисциплина, что схема БД: `version` + контрольная сумма +
|
||
**атомарная запись** (temp + rename; два слота пинг-понгом на flash) + миграция при
|
||
загрузке + roundtrip-тест; битое состояние не роняет приложение — старт с чистого /
|
||
дефолтов с указанием места поломки *(REC dimmer EEPROM-блоб; мониторинг mktemp+mv;
|
||
tray-monitor; Ad-Tycoon: `// TODO: restore staff` — купленное исчезало)*.
|
||
15. **Права — на уровне выборки, не UI:** выдача фильтруется по пользователю в самом
|
||
запросе (IDOR), permission-классы объявлены явно на каждом эндпоинте *(BAZA)*.
|
||
Пагинация на всех list-эндпоинтах, явный ordering (недетерминированный порядок
|
||
ломает пагинацию), запрет N+1.
|
||
16. **Retention фиксируется раньше механизма:** для каждой таблицы, растущей от
|
||
активности (телеметрия, логи, события, аудит) — «сколько храним» решено письменно,
|
||
компенсирующий контроль (индекс, мониторинг объёма) — сразу *(АПЕКС ADR-019)*.
|
||
Класс данных, запрещённый к отправке во внешние сервисы **включая облачные AI**, —
|
||
объявлен письменно ДО первой интеграции *(АПЕКС: 152-ФЗ → только локальная модель)*.
|
||
17. **Один назначенный писатель** — там, где блокировать нельзя (ISR, UI-поток, горячий
|
||
путь): ресурс пишет ровно один владелец, обмен через двойной буфер/очередь; это
|
||
парный паттерн к «блокировка на счётчиках» (`gotcha-increment-race`) *(REC dimmer:
|
||
CCR пишет только ISR TIM6)*. Эксклюзивный ресурс (COM-порт, файл-лок, сокет)
|
||
освобождается явно до передачи следующему; поток без фреймов — дренаж запоздалых
|
||
ответов и фильтрация незапрошенного `[железо]` *(АПЕКС release process)*.
|
||
18. **Не создавай второй канал доставки для того, без чего не работает первый:** конфиг,
|
||
ключ, сертификат, схема — если их доставка идёт другим путём, чем код, отказ
|
||
доставки убивает возможность починки *(АПЕКС ADR-022: CA вшиты в прошивку — битая
|
||
отдельная партиция значила бы мёртвый TLS = мёртвый OTA = физическая перепрошивка)*.
|
||
19. **Файлы:** бинарники не в БД — хранится путь; превью асинхронно; статику/медиа
|
||
отдаёт прокси мимо приложения, приватное — через X-Accel-Redirect/аналог *(BAZA)*.
|
||
Каждый подписчик события — с уникальным идентификатором подписки (dispatch_uid):
|
||
двойное подключение = двойные эффекты, почти не диагностируется *(BAZA)*.
|
||
20. **Автономный/демо/offline-режим — как обычный источник данных, а не ветка `if` во
|
||
всём тракте:** подделывается вход, не тракт *(REC dimmer: STATIC-кадр — обычный
|
||
валидный сигнал для FSM, пайплайн без спецслучаев)*.
|
||
|
||
## II. Код и гейты
|
||
|
||
1. **Гейты с ПЕРВОГО коммита:** форматтер + импорт-сорт + линтер + типы в pre-commit и
|
||
CI. Для нового кода — 0 warnings сразу; для унаследованного долга — **рэтчет**
|
||
warn→error с зафиксированным потолком и планом, не разовая зачистка *(BAZA ADR-051)*.
|
||
Гейты двух уровней: **hard блокирует, soft предупреждает** *(АПЕКС)*. Поведение при
|
||
провале — письменно: остановиться, показать упавший гейт + полный stderr, не чинить
|
||
молча; обход по команде — `NOTE: committed with failing <gate>` в тело коммита.
|
||
Обоснованный пропуск гейта — с условием возврата («тесты — план 0.8.0»), иначе
|
||
«нулевая терпимость» обходится тихо. Неиспользуемая disable-директива = ошибка.
|
||
Ложное срабатывание любого сканера гасится **самым узким скоупом** (allow по пути),
|
||
не глобальным игнором класса правила *(BAZA trivy)*. Локальный гейт и CI — один
|
||
инструмент одной версии. Форматтеры не трогают append-only артефакты (глобальный
|
||
exclude на миграции — правило нарушает автоформат, а не человек) *(BAZA 2026-07-21)*.
|
||
*Инструменты по стеку: Python — black/isort/ruff/mypy; TS — eslint + `tsc -b --noEmit`.*
|
||
2. **Правило, проверяемое машиной — хук, а не абзац.** Каждый инвариант литании,
|
||
выразимый регуляркой/AST, дублируется PreToolUse-хуком в момент формулировки —
|
||
иначе живёт до первой спешки *(BAZA: 12 блокирующих — float у денег, физический
|
||
delete, правка миграции, секреты, DEBUG в prod…)*. Блокирующие и напоминающие хуки —
|
||
два разных класса; Stop-хук — для напоминаний о ритуалах. Обязательные спутники
|
||
хука: список исключений (migrations/, node_modules/, собственные литералы паттернов),
|
||
тесты на сам хук, быстрый путь выхода (оверхед на каждый tool call). Покрытие
|
||
хука проверяется по **всем шеллам**, которыми реально пользуется агент
|
||
(`gotcha-hook-shell-blind`).
|
||
3. **Каждый слой каркаса проверяй сразу, не в конце:** после каждого слоя — прогон
|
||
гейтов. Ошибки ловятся дёшево у источника *(EventPlan)*.
|
||
4. **Тулчейн и системные зависимости проверяй фактически ДО написания конфигов:**
|
||
запусти и убедись, не предполагай. Версии пинуй; что тянет либа — в ADR/Dockerfile
|
||
в момент выбора *(EventPlan: corepack pnpm; Shapely→GEOS, weasyprint→pango)*.
|
||
5. **Типы обязательны** на всех функциях. **Функции ≤40 строк** без явного обоснования.
|
||
6. **Тесты зеркалят структуру**, фабрики, AAA. Тест = полная жизненная цепочка, не
|
||
изолированный вызов. Каждый багфикс приносит тест. Интеграционные — на реальной БД,
|
||
мок только для внешних сервисов. Фича, меняющая контракт, обновляет свои тесты в том
|
||
же коммите (`gotcha-feature-test-debt`). Для вычислительного ядра цепочка — не то:
|
||
там **инварианты алгоритма в docstring** как контракт + property-based тесты, автор
|
||
инварианта и автор теста — разные агенты *(EventPlan: hypothesis)*. Цифра покрытия —
|
||
только с конфигурацией замера («69% с greenlet, без него фиктивные 56%»); расхождение
|
||
тестовой и прод-СУБД — задокументированное ограничение, не умолчание *(АПЕКС)*.
|
||
7. **Тесты wiring — отдельный класс от тестов логики.** Юнит-тест, импортирующий объект
|
||
напрямую, не проверяет, что система его находит: любая регистрация «по конвенции»
|
||
(задачи, сигналы, плагины, роуты) требует теста «всё, на что ссылается конфиг,
|
||
реально зарегистрировано» (`gotcha-silent-unregistered`). **Orphan-check:** публичная
|
||
функция ядра без единого вызывающего — баг интеграции или мёртвый код
|
||
(`gotcha-green-modules-dead-system`). Конфиг-поле без потребителя — ложь в диффе;
|
||
конфиг валидируется при старте (fail fast), единицы измерения совпадают с кодом
|
||
*(Ad-Tycoon: в конфиге 2, в коде хардкод 0.02 — расхождение в 100 раз)*.
|
||
8. **Тест без боевой среды — спроектирован, не случаен.** Ядро не импортирует ни строчки
|
||
среды исполнения И ни строчки вендорского SDK — это две разные оси изоляции, обе
|
||
проверяются **сборкой** (второй target «на хосте»), а не дисциплиной *(REC dimmer:
|
||
core/ не видит hal/; АПЕКС ADR-021: изоляция окупается тестируемостью даже без
|
||
миграции; VOX: dsp.h собирается на ПК)*. Внутри ядра запрещены Date.now() /
|
||
Math.random(): часы и seeded RNG инжектируются — детерминизм = воспроизводимые
|
||
тесты *(Ad-Tycoon)*. Матрица средств тестирования — с честным «чего НЕ покрывает
|
||
ни один» *(VOX: Wokwi vs WAV-стенд)*. Мок-режим — полноценный режим приложения со
|
||
сценарием по расписанию, включая негативные состояния *(tray-monitor: три сервера
|
||
со сдвинутыми фазами, один намеренно без датчика)*. Симулятор внешней системы —
|
||
первоклассный артефакт со своими правилами: те же схемы, что у прод-кода (не
|
||
копипаст полей), seed, префикс SIM_/флаг is_simulated, assert «не прод» *(АПЕКС)*.
|
||
Для доменов с накопительной динамикой — headless-прогон N шагов ботом с проверкой,
|
||
что метрики двигаются *(Ad-Tycoon: «7 дней, seed 42» показал экономический тупик)*.
|
||
Смоук-сценарий на то, что уже ломалось — для слоёв, не берущихся юнит-тестами
|
||
(GUI, трей, жизненный цикл процесса) *(tray-monitor)*.
|
||
9. **Фоновые задачи** (если есть очередь): никакой бизнес-логики в таске — таска
|
||
вызывает сервис; queue, time_limit, retry с backoff, идемпотентность; передавать id,
|
||
не объекты.
|
||
10. **Фронтенд** (если есть): strict-типизация без `any`; единый api-клиент (auth +
|
||
refresh + interceptor в одном месте); server state отдельно от клиентского *(у нас
|
||
react-query + zustand)*; формы со схемной валидацией *(RHF + zod)*; дизайн-система
|
||
классов вместо inline-стилей; error boundary на каждый маршрут **с key**; скачивание
|
||
защищённых файлов — только через api-клиент (blob); единый паттерн деталей сущностей.
|
||
**Строки UI — в один ресурс с первого дня**, даже в одноязычном проекте: ретрофит
|
||
i18n дороже в разы, а правки текстов не должны требовать правки кода *(Ad-Tycoon:
|
||
файл есть, UI захардкожен — местами показывались ключи)*. **Бюджеты числами**
|
||
(размер сборки, TTI, запросы) — проверяются при добавлении тяжёлого, не «когда
|
||
станет медленно».
|
||
11. **CI — это не только гейты:** CI слушает **рабочую** ветку — сменил ветвенную
|
||
модель → в тот же коммит триггеры CI/хуков (`gotcha-ci-wrong-branch`); для
|
||
кроссплатформенного артефакта — матрица `ubuntu + windows` (автоматически ловит всю
|
||
группу Windows-граблей); CI **собирает** релизный артефакт (ловит зависимость от
|
||
файла вне git); гейт «чистый клон собирается и стартует»; supply-chain в базовом
|
||
наборе (audit зависимостей, скан образа, встроенный `check --deploy` фреймворка —
|
||
бесплатный гейт, который никто не включает); coverage-порог *(BAZA)*. Свой
|
||
CI-раннер не селить рядом с боевыми сервисами — 1600 тестов = OOM соседям *(BAZA)*.
|
||
Прод-образ намеренно без dev-тулинга; ожидаемое «падение pytest на проде» —
|
||
задокументированная фича, не поломка *(BAZA)*.
|
||
12. **Лицензионный гейт:** политика лицензий стороннего кода письменно (для закрытого
|
||
продукта — только пермиссивные); новый компонент — сначала строка в
|
||
THIRD_PARTY_LICENSES.md, потом в сборку *(REC dimmer)*.
|
||
|
||
## III. Бумажная работа (карта + документация + версии)
|
||
|
||
Развернуть с первого дня — «потом» не наступает (`gotcha-docs-drift`).
|
||
|
||
1. **Карта навигации:** Module Map в CLAUDE.md (строка на модуль: путь — назначение —
|
||
ключевые сущности) + navigation-док (дерево каталогов; таблица сущностей —
|
||
генерируется grep'ом, не по памяти; «задача → где искать»). Список крупных файлов —
|
||
не как список, а как **порог (параметр) + команда генерации + дата сверки +
|
||
автопроверка в обе стороны** («перерос, но не в списке» и «в списке, но похудел»)
|
||
*(АПЕКС check-integrity)*. Правило контекст-экономии первым пунктом workflow;
|
||
большие файлы — поиск → чтение диапазона; правило передавать субагентам.
|
||
2. **Документация — один индекс и один гейт.** Два независимых дерева = двойная цена
|
||
синхронизации *(BAZA проходила)*. Артефакт, потребляемый кодом (changelog в UI,
|
||
схема в сборке), законно живёт в коде — но обязан быть в таблице маршрутизации
|
||
ритуала закрытия. Формат — под проект и потребителя, выбор зафиксировать.
|
||
**Документация проходит гейт как код**: strict-сборка доков (битые ссылки, файлы
|
||
вне nav) — дерево без гейта гниёт по ссылкам быстрее, чем по содержанию *(АПЕКС:
|
||
mkdocs --strict)*. Состав дерева: index, architecture, api-reference, decisions
|
||
(ADR), versioning, onboarding, runbook (+production-runbook при проде), по-модульные
|
||
доки; штамп «Обновлено: дата | версия». **Runbook — в формате «Симптом →
|
||
Диагностика → Фикс»**, не описание системы *(×4 проекта независимо)*. Второй
|
||
потребитель — человек вне разработки (сборщик, заказчик): его версия **генерируется**
|
||
из основной, не пишется дважды *(VOX: md→html конвертер «открыть двойным щелчком»)*.
|
||
README = витрина: версия + badge, таблица модулей (число = факт!), старт.
|
||
3. **ADR:** нетривиальное решение (≥2 реальных альтернатив) → Контекст / Альтернативы /
|
||
Решение / Причины / **Компромисс** (обязателен). Существующие ADR не редактируются.
|
||
«Почему не X» — рядом с решением, особенно где ошибка необратима *(Pulse generator:
|
||
таблица отвергнутых MOSFET с причинами)*. Ключ связывания систем — решение с
|
||
**организационным следствием**, и оно пишется в Компромисс *(HS+auth: маппинг по
|
||
email ⇒ «почты сотрудников нельзя переиспользовать» — правило для отдела кадров)*.
|
||
4. **Ритуал закрытия сессии** (скилл с триггер-фразой): «закрываем сессию» → дифф
|
||
сессии (`git log` + `git status`) → свести затронутые зоны (модуль → дока + карта;
|
||
процесс → правило; решение → ADR; бамп → changelog; закрытый долг → реестр) →
|
||
отдельный `docs(sync)`-коммит. **Если сводить нечего — сказать это явно, не молчать**
|
||
(иначе ритуал незаметно отмирает) *(АПЕКС)*. Канонические имена/URL при сведении
|
||
сверяются grep'ом с кодом — SoT имени всегда код *(BAZA: `/healthz` числился
|
||
`/api/health/` в пяти доках)*. После docs-сессии — короткий список реально
|
||
исполняемых изменений *(АПЕКС post-rules-refactor)*. Штамп ≠ гарантия актуальности.
|
||
5. **Статусы в планах — по факту кода, в обе стороны.** Фантомные хвосты: пункт числится
|
||
открытым, а код давно есть — перед реализацией «незакрытого» сверка с кодом *(BAZA:
|
||
2 из 4 хвостов)*. Сводки и индексы правятся **атомарно** с деталью. **Реестр
|
||
«Критические долги» — в НАЧАЛЕ roadmap** (не в подвале): приоритет, слой, условие
|
||
закрытия; закрытие — двухфазно: коммит-фикс, затем коммит-пометка с точным SHA
|
||
первого (смешивать нельзя — SHA фикса ещё не существует в момент правки таблицы)
|
||
*(АПЕКС)*. Чек-лист `[x]` — только если формулировка однозначно совпадает с диффом;
|
||
буллеты закрытых релизов — неизменяемая история. **План фазы — в репозитории, не в
|
||
памяти агента**, с блоком «Ключевые решения (не пере-решать)» со ссылками на ADR —
|
||
прививка от пере-обсуждения решённого *(АПЕКС 0.7.0-execution-plan)*. Опционально —
|
||
HTML-дашборд прогресса с блоком DATA, собираемым из git, обновляется в ритуале
|
||
закрытия *(Dashboard/BAZA)*.
|
||
6. **Версии:** SemVer; письменно объявленный SoT-файл на каждый артефакт; версия
|
||
**наблюдаема на самом артефакте** (health-эндпоинт, UI «Что нового», экран About
|
||
устройства). MAJOR.MINOR синхронны только для платформенной пары; прошивки/утилиты/
|
||
протокол — независимы; при >2 артефактах — **матрица совместимости в changelog
|
||
минорного релиза** *(АПЕКС)*. Канал релиза (dev/beta/rc) — pre-release suffix SemVer,
|
||
не отдельная колонка: парсер сам сравнит `0.1.0-debug.1 < 0.1.0-rc.1 < 0.1.0`;
|
||
префикс артефакта — только в UI/тегах, не в коде *(АПЕКС)*. Changelog — только
|
||
заметное пользователю, не коммит-лог; запись в changelog обязательна — без неё бамп
|
||
запрещён. Бамп ТОЛЬКО по явной команде; ритуал «готовимся к бампу»: git log с
|
||
прошлого бампа → атомарные сценарии ручной проверки, **сгенерированные по диффу, не
|
||
по памяти** (файл чеклиста в .gitignore) → прогон по одному через AskUserQuestion
|
||
(на ✗ — стоп) → полный тест-прогон → два зелёных дают право бампать. Сокращённый
|
||
гейт для PATCH рассматривался и письменно отвергнут — «рождаются вторые патчи в тот
|
||
же день» *(BAZA ADR-068)*. Тег ставится локально; **push тега — отдельное «да»,
|
||
каждый раз** *(АПЕКС)*.
|
||
|
||
## IV. Организация работы с Claude
|
||
|
||
1. **Модель ветвления объявляется письменно в ADR** — trunk-based или dev/master, по
|
||
числу людей и наличию ревью, а не по привычке *(АПЕКС живёт trunk-based, ADR-024;
|
||
BAZA — dev/master)*. Для dev/master: master = стабильный прод, merge только бампом;
|
||
режим стабилизации — dev держится потоком фиксов, фичи во временных `feature/*`,
|
||
постоянную третью ветку не заводить *(BAZA ADR-068)*.
|
||
2. **Делегирование:** нетривиальное — субагентам с явным именованием; main loop
|
||
оставляет себе git, гейты, ревью диффов, интеграцию. **Нижняя граница:** правку 1–3
|
||
строк не делегировать — оверхед дороже. **Нет подходящего агента — остановиться и
|
||
спросить, не выдумывать.** Определения агентов модель-нейтральны: модель и effort —
|
||
во frontmatter, в прозе имён моделей нет (протухают за недели) *(АПЕКС ea13116;
|
||
контрпример — «Opus 4.8» в CLAUDE.md EventPlan)*. Дорогой агент-архитектор
|
||
вызывается по письменному списку **обязательных** триггеров — и только по ним.
|
||
Субагенты не порождают субагентов: лиды возвращают план делегирования текстом,
|
||
исполнителей запускает main loop. Три проверенных архетипа — субагент как **бюджет
|
||
контекста**: reviewer-хранитель пронумерованных инвариантов («не переписывай сам»;
|
||
«чем грозит»), test-runner на дешёвой модели («только вердикт и текст упавших»),
|
||
doc-scout («файл:строка + цитата, не пересказывай») *(REC dimmer)*. Шкала
|
||
оркестрации по размеру: роли в одном файле → именованные субагенты → генерируемая
|
||
студия. Независимых — параллельно, одним сообщением. Субагентам передавать правило
|
||
контекст-экономии и запрет на commit/push.
|
||
3. **Опасные команды** — список письменно в CLAUDE.md с первого дня: rm -rf,
|
||
reset --hard, down -v, DROP, force-push, `git clean -f`, `git checkout --` с потерей
|
||
правок, `docker system prune`, `volume rm`, `dd`, `mkfs`, `pkill -9`, erase-flash,
|
||
любые действия на прод. Выполнение — только после письменного подтверждения, одно
|
||
«да» = один вызов. **Текст без хука — декларация:** список дублируется блокирующим
|
||
хуком, покрытие хука — по всем шеллам (II.2). Письменно же — известные
|
||
escape-вектора ask-gate: `cd x && git push`, `sh -c`, `eval`, pipe через `xargs` —
|
||
договорённость не использовать, распространена на субагентов *(BAZA)*. Опасны они
|
||
не только потерей данных, но и потерей **улик**: сначала понять причину, потом
|
||
чистить *(АПЕКС runbook)*.
|
||
4. **Не переспрашивай и не угадывай:** решение с очевидным дефолтом — прими, озвучь,
|
||
двигайся. Развилку, меняющую архитектуру/данные/UX — AskUserQuestion **до** кода.
|
||
Несколько вопросов — одним заходом.
|
||
5. **TodoWrite** на всё многошаговое; планы — в `docs/plans/`, не в корне. Двухфазность:
|
||
фикс отдельным коммитом, roadmap/доки — отдельным (причину см. III.5).
|
||
6. **Бюджет CLAUDE.md:** там только то, что нужно КАЖДУЮ сессию — постоянная статья
|
||
расхода контекста. Ритуалы — **скиллы** с триггер-фразами; правила — файлы с `globs`
|
||
по зонам кода (грузятся по файлу) *(оба боевых проекта пришли независимо: АПЕКС
|
||
279→149 строк, BAZA rules→skills)*. `.claudeignore` на шумные каталоги — с обратным
|
||
правилом: ничто, на что ссылаются правила или память, не скрыто (указатели в
|
||
невидимое) *(АПЕКС)*. Гигиена памяти: индекс обязателен (нет в MEMORY.md = невидимо
|
||
при recall), лимит размера (большой файл = документ, в docs/), память ≠ хранилище
|
||
документов. **У метаданных проекта — свой прогоняемый чек** (битые ссылки CLAUDE.md,
|
||
захардкоженные счётчики, таблица крупных файлов, покрытие хуков): их дрейф не
|
||
роняет ни один тест *(АПЕКС check-integrity.ps1)*. Одна точка входа для агента,
|
||
инструменто-нейтральная: смена инструмента = миграция содержимого, чужие каталоги
|
||
удаляются тем же коммитом (`gotcha-two-agent-configs`).
|
||
7. **Систематически нарушаемое правило — дефект системы, а не людей.** Гайд не
|
||
покрывает реальную потребность — расширь систему и задокументируй, а не копи
|
||
исключения *(BAZA: один класс кнопки закрыл годы техдолга)*. Осознанное исключение
|
||
пишется **в самом правиле** — с границей применимости *(BAZA viewsets-only)*.
|
||
Заморозка направления фиксируется в правиле явно («i18n ЗАМОРОЖЕН: … не трогать»),
|
||
иначе очередная сессия «доводит до ума» замороженный слой *(BAZA)*. Правило,
|
||
невыполнимое по построению («не портировать вручную» между двумя деревьями одного
|
||
кода) — сигнал менять структуру, не дисциплину: одномоментный перенос, старое
|
||
read-only *(АПЕКС ADR-020)*.
|
||
8. **Гигиена сессии:** следить за автокомпактом — ссылки на docs/ позволяют перечитать
|
||
первоисточник после сжатия контекста; сессия ушла не туда — дешевле поправить
|
||
CLAUDE.md и начать новую, чем выправлять *(REC dimmer)*. Конфиги быстро меняющихся
|
||
сторонних сервисов — по актуальной официальной документации, не по памяти модели;
|
||
версии сверяются ДО генерации конфигов (`gotcha-llm-stale-configs`).
|
||
9. **Протокол отладки** (именованный ритуал с триггер-фразой): снаружи внутрь
|
||
(UI → сеть → бэкенд → БД); **не чинить первую найденную причину** — проверить, нет
|
||
ли слоёв под ней; подтвердить фикс фактически до объявления победы; пойманное —
|
||
в memory/грабли *(BAZA tyranid-hunt)*.
|
||
|
||
## V. Прод, эксплуатация, наблюдаемость
|
||
|
||
1. **Боевые серверы — только команды, никакого «я сделаю сам».** На прод/VPS агент не
|
||
выполняет НИЧЕГО самостоятельно. Он выдаёт готовые команды блоком, выполняет человек.
|
||
Одно письменное подтверждение = одно действие; «да» из прошлого раза не переносится.
|
||
*(АПЕКС)*
|
||
2. **Деплой-чеклист письменно и всегда целиком:** build ВСЕХ артефактов («изменения не
|
||
появились» = собрал не всё — артефактов деплоя больше одного, перечисли ВСЕ и способ
|
||
сборки каждого, `gotcha-partial-build`) → перезапуск ВСЕХ производных контейнеров →
|
||
прокси → статусы → логи воркеров → smoke-тест. Частичные варианты — **флаги одного
|
||
скрипта** (`--backend`/`--frontend`), не отдельные инструкции *(АПЕКС)*.
|
||
3. **Деплой — один канонический скрипт с проверками внутри** *(BAZA deploy.sh)*: отказ
|
||
при грязном дереве/чужой ветке; список пересоздаваемых сервисов **вычисляется из
|
||
compose**, не хардкодится (новый воркер попадает в деплой сам); бэкап — при
|
||
непустом плане миграций; ожидание healthy по inspect с таймаутом, не «на глаз»;
|
||
smoke = версия из health-эндпоинта, не 200 OK; при провале health-gate скрипт
|
||
**печатает точную команду отката**. **Точка отката для проекта с миграциями — пара
|
||
(коммит, дамп)**, образа `:prev` недостаточно: откат кода через миграционный рубеж
|
||
без отката БД падает на отсутствующих колонках. Rollback — отдельный парный скрипт:
|
||
подтверждение словом, страховочный дамп текущего состояния ДО восстановления,
|
||
`--code-ref`, финальная сверка схемы с кодом *(BAZA rollback.sh)*. Фронт-статика:
|
||
новая сборка рядом (`dist_new`) → атомарный swap, старая = точка отката *(АПЕКС)*.
|
||
4. **Секрет-гигиена, три слоя раздельно: код / состояние / секреты.** `.env` в
|
||
`.gitignore`, `.env.example` с плейсхолдерами и маркерами `[SECRET] → менеджер`;
|
||
шифрованные секреты (SOPS+age) коммитятся намеренно, ключ вне репо; учётки внешних
|
||
SaaS — в парольном менеджере, не в `.env` *(HS+auth)*. Клиентские секреты —
|
||
платформенный механизм (DPAPI/keychain) + письменно «от чего это НЕ защищает»
|
||
*(tray-monitor)*. **Секрет в URL = секрет в access-логе и Referer** — маскирование
|
||
в логах приложения + `access_log off` на эндпоинте *(АПЕКС: JWT в query WS)*.
|
||
Предупреждать о риске ДО команды, которая выведет секрет в терминал/историю.
|
||
Утечка = ротация в ту же сессию *(BAZA: PAT)*.
|
||
5. **Канонические пути и имена — письменно в момент создания:** путь на сервере, имена
|
||
контейнеров, бакетов *(АПЕКС: /opt/signe/ vs «угаданный» /opt/signe-server/)*.
|
||
Плюс **реестр занятого**: порты/адреса/имена проверяются на конфликт с соседями,
|
||
включая вендорские дефолты *(REC dimmer: дефолтный IP ноды = дефолт диммера;
|
||
Grafana 3001 из-за forgejo)*. Порядок старта зависимостей (VPN раньше docker) — либо
|
||
в юните (`After=`), либо задокументирован как штатный симптом *(мониторинг)*.
|
||
6. **Наблюдаемость** (при проде — обязательна): **внешний наблюдатель** — всё, что
|
||
запущено на самом сервере, не видит отказ самого сервера *(АПЕКС)*. Health-эндпоинт
|
||
как liveness («200 degraded») → мониторинг по ключевому слову `"status":"ok"`, не по
|
||
коду ответа; публичный health не утекает текст исключений. **Dead man's switch на
|
||
всю периодику** (бэкап, отчёты, синк): «должно было запуститься и не запустилось» —
|
||
отдельный класс отказов, cron сам о себе не сообщит; сигналы «упал» / «успех
|
||
устарел» / «успехов не было ни разу» / «метрики вообще нет» слепы по отдельности —
|
||
нужны все *(мониторинг: BackupNeverSucceeded не видит BackupTooOld)*; ping только на
|
||
успех, grace с запасом, таймзона сверяется, не предполагается. Структурированные
|
||
логи + сквозной request_id — с первого дня, задним числом невозможно *(АПЕКС)*.
|
||
Пороги живут на сервере; клиент отображает решения, не принимает их; цвета клиента =
|
||
правила стека («жёлтая цифра значит то же, что жёлтая иконка») *(tray-monitor)*.
|
||
Healthcheck проверяет живой процесс, а не прокси-артефакт (файл/лог/таймстамп);
|
||
устранив причину — пройди по проверкам, которые на неё опирались
|
||
(`gotcha-proxy-healthcheck`). Предохранители в скриптах: опасная конфигурация не
|
||
стартует, обход — только явным флагом с говорящим именем
|
||
(`ALLOW_PUBLIC_UNAUTHENTICATED=1`) и перечислением того, что утечёт *(мониторинг)*.
|
||
7. **Бэкап не существует, пока не проверено восстановление.** Дамп — вне тома с
|
||
данными; проверка целостности (`--verify`); retention письменно (daily/weekly/
|
||
monthly); **один SoT расписания** (`gotcha-double-cron`); процедура restore
|
||
написана; **поимённый список критических артефактов с ценой пропуска каждого**
|
||
*(HS+auth: noise.private_key — иначе перерегистрация всех узлов)*. Проверка на
|
||
**bootstrap paradox**: пройди по зависимостям процедуры восстановления — нет ли
|
||
тех, что сами восстанавливаются этой процедурой *(HS+auth: бэкап на NAS, доступном
|
||
через ту же mesh-сеть; REC dimmer: «сбить IP с панели → починить с панели» — пункт
|
||
приёмки)*. Внешнее хранилище/LFS считается рабочим только после скачивания объекта
|
||
с чистой машины *(Ad-Tycoon: LFS отдавал 404, 31 файл утерян безвозвратно)*.
|
||
8. **Приёмка и необратимость.** Приёмочный чек-лист — парный артефакт спеки, и он
|
||
**диктует наблюдаемость**: сначала «как проверяем», из этого — какую диагностику
|
||
обязан отдавать продукт (счётчики, uptime, версия на самом устройстве) *(REC
|
||
dimmer)*. **Ворота этапа:** неизвестность закрывается ДО необратимых/дорогих работ;
|
||
условные этапы не проектируются заранее; фазовый гейт — измеримый пользовательский
|
||
результат с правом остановить продукт («загрузил → план ≤3 мин против 2 ч; нет
|
||
„вау“ → пересматриваем») *(EventPlan roadmap)*; ценность раньше «умной» части —
|
||
ML/распознавание после подтверждения тупого варианта. Отказные сценарии
|
||
обновления — часть приёмки: битый файл → отказ по CRC, питание в момент записи →
|
||
грузится старое; «что деградирует во время обновления» записано *(REC dimmer)*.
|
||
В пайплайне с необратимыми шагами guard выполняется **до первого необратимого**
|
||
*(АПЕКС: compat-check до записи identity)*. Запас по предельным параметрам вместо
|
||
защиты, когда отказ необратим («полевик умрёт раньше любой защиты»). Критерий
|
||
приёмки чисто-архитектурного рефакторинга — **байт-идентичный наблюдаемый выход**
|
||
до/после, иначе это не рефакторинг *(АПЕКС ADR-021)*. Debug-функциональность
|
||
физически отсутствует в релизном артефакте (`#ifdef`/сборка), а не «выключена
|
||
настройкой»: debug-роуты, mock-режимы, фейковый логин *(АПЕКС)*.
|
||
9. **Профиль «публичный релиз»** — отдельный от своего прода: как только артефакт
|
||
уходит в чужой магазин/Marketplace/публичный домен — LICENSE + поле в манифесте,
|
||
README-витрина со скриншотами, CHANGELOG, иконка, локализация пользовательских
|
||
доков (внутренние — на одном языке, и это записано), чеклист модерации ДО релиза
|
||
*(claude-office-dashboard)*.
|
||
10. **Межпроектные контракты:** значения, которые один проект ВЫДАЁТ, а другой
|
||
ПОТРЕБЛЯЕТ — таблицей с явным эмитентом и потребителем, «совпадают байт в байт»,
|
||
включая trailing slash и порт *(HS+auth: OIDC)*. Перечислить ВСЕ пары «кто → кому
|
||
ходит»: браузер ≠ контейнер, оба должны достучаться — поломки живут в паре, о
|
||
которой не подумали. **Общий эталон проверяется первым** при «не работает»: земля
|
||
для сигналов, NTP для токенов (clock skew валит exp/iat), кодировка для текста,
|
||
таймзона для дат — и записывается в предусловия *(Pulse generator + HS+auth,
|
||
независимо)*. `[железо]` Однократные ручные предусловия среды («выпаять флеш»,
|
||
modprobe модуля, драйвер) — письменный чек-лист, не проза. Комплект артефактов
|
||
железного проекта: pinout · схема подключения · BOM · порядок сборки · приёмка
|
||
(три проекта пришли независимо); **SoT по «ногам» — документ**, в коде ровно один
|
||
заголовок (pins.h), его отражающий; при противоречии прав документ *(REC dimmer)*.
|
||
|
||
## VI. Литания против граблей
|
||
|
||
> Каждая — реальный инцидент; чти их, дабы не повторить. Теги — к какому стеку/профилю
|
||
> применима; при триаже неприменимые группы отсекай целиком. Ссылайся слагом, не номером.
|
||
|
||
### Контейнеры и деплой `[Docker]`
|
||
|
||
1. `gotcha-migration-drift` **Дрейф миграций** `[+Django]`. makemigrations в контейнере
|
||
без bind-mount → файл живёт в слое образа, БД помнит, git нет; rebuild — файл исчез.
|
||
Потеряно 18 файлов. → makemigrations ТОЛЬКО через dev-override с bind-mount; после —
|
||
`git status`; CI-гейт «чистый migrate с нуля». Рецепт восстановления — записан (I.7).
|
||
*(BAZA)*
|
||
2. `gotcha-stale-workers` **Воркеры на старом образе** `[+очередь]`. `up -d app` не
|
||
трогает worker/beat/asgi — очередь выполняет старый код, симптомов не даёт. →
|
||
деплой-чеклист V.2 целиком; список сервисов вычисляется из compose. *(BAZA)*
|
||
3. `gotcha-proxy-cache` **Прокси кэширует upstream.** После пересборки — 502 или старый
|
||
фронт. → лечить причину конфигом: `resolver 127.0.0.11 valid=10s` + переменные
|
||
upstream в каждом location — после этого рестарт nginx не нужен; ритуал рестарта —
|
||
подстраховка, знай, что он маскирует незакрытую причину. *(BAZA, вылечено 3ae5995)*
|
||
4. `gotcha-partial-build` **Артефактов деплоя больше одного.** Пересборка бэкенда не
|
||
деплоит фронт (в контейнере он или нет — неважно); «изменения не появились» — почти
|
||
всегда это. → перечисли ВСЕ артефакты и способ сборки каждого. *(BAZA, АПЕКС)*
|
||
5. `gotcha-pidfile` **Pidfile в контейнере.** `docker restart` сохраняет ФС →
|
||
осиротевший pidfile → вечный рестарт, периодика молча стоит. → pidfile в
|
||
one-process-контейнере не нужен никогда. Парная: healthcheck, проверявший pidfile,
|
||
стал вечно-ложным после лечения — устранив причину, пройди по проверкам, которые на
|
||
неё опирались (`gotcha-proxy-healthcheck`). *(BAZA)*
|
||
6. `gotcha-interval-schedule` **Интервальные расписания сбрасываются рестартом**
|
||
планировщика → суточная задача «каждые 24ч» не срабатывает никогда. → crontab-выражения
|
||
для суточных/еженедельных. *(BAZA)*
|
||
7. `gotcha-silent-unregistered` **Расписание ≠ реестр** `[+очередь]`. Задача, не
|
||
попавшая в реестр воркера (autodiscover не видит подпакет), отбрасывается МОЛЧА —
|
||
beat шлёт, воркер отвечает «unregistered», симптом — тишина. Юнит-тесты, импортирующие
|
||
задачу напрямую, бага не видят. → тест «расписание ⊆ реестр» + проверка в
|
||
деплой-скрипте; у планировщика нет громкого режима отказа — создай его. *(BAZA, прод)*
|
||
8. `gotcha-dev-prod-settings` **Dev-стек на prod-настройках.** Забытый override →
|
||
prod-whitelist хостов → 400 на локальном домене. → явные именованные override-файлы
|
||
и одна задокументированная команда запуска dev-стека. *(BAZA)*
|
||
9. `gotcha-exec-bit` **Права/окружение контейнера ≠ хоста** `[+Windows]`. Скрипт без +x,
|
||
том с чужим uid → Permission denied. → скрипты звать через `sh <путь>`; явные права
|
||
томов; проверка после первого up. *(BAZA)*
|
||
10. `gotcha-rootowned-bindmount` **Bind-mount на несуществующий путь** — docker создаст
|
||
каталог от root, задача не от root писать не сможет. → создавать каталоги заранее
|
||
в скрипте. *(мониторинг)*
|
||
11. `gotcha-docker-bypasses-ufw` **Границу доступа определяет bind-адрес, не фаервол.**
|
||
Docker пишет правила в nat в ОБХОД ufw — `ufw deny 9090` порт не закроет. → bind
|
||
на tailnet/localhost; публичный bind — осознанное решение. *(мониторинг)*
|
||
12. `gotcha-container-oom` **Контейнерный лимит памяти без внутреннего лимита процесса**
|
||
→ OOM-kill вместо деградации. → лимит процесса ниже лимита контейнера. *(АПЕКС)*
|
||
13. `gotcha-env-special-chars` **Спецсимволы `#$!%&` в пароле** ломают парсер
|
||
.env/compose → контейнер в вечном Restarting. *(АПЕКС)*
|
||
14. `gotcha-compose-env-drift` **Дефолты в compose разошлись с .env.example** → стек
|
||
поднимается с другими именами БД/юзера. → один источник дефолтов. *(АПЕКС)*
|
||
|
||
### Данные и логика
|
||
|
||
15. `gotcha-journal-bypass` **Мутация в обход журнала.** Ручное списание меняло остаток
|
||
напрямую — журнал слеп, аудит дыряв. → ЕДИНАЯ точка применения эффекта, все пути
|
||
через неё. *(BAZA)*
|
||
16. `gotcha-pair-asymmetry` **Асимметрия парных операций.** Создание покрывало 2 метода
|
||
оплаты, отмена — 1: проводка-сирота, отчёт врал. → create+cancel парой, тест на цикл
|
||
create→cancel→след чист. *(BAZA)*
|
||
17. `gotcha-increment-race` **Гонки на инкрементах.** Два параллельных движения читают
|
||
один остаток → lost update. → блокировка (select_for_update) внутри транзакции на
|
||
любых счётчиках с первого дня + конкурентный тест. Где лочить нельзя (ISR, UI) —
|
||
один назначенный писатель (I.17). *(BAZA)*
|
||
18. `gotcha-placeholder-data` **Плейсхолдер, притворяющийся данными.** Черновые значения
|
||
без пометки уезжают как «проверенные». → помечать (`?`, TODO владельцу); гейт перед
|
||
прод — плейсхолдеров не осталось. *(EventPlan)*
|
||
19. `gotcha-number-null` **Number(null) === 0** `[React/формы]`. Трансформ на маунте:
|
||
null FK → 0 → «выбрана» несуществующая запись. → null-безопасные setValueAs. *(BAZA)*
|
||
20. `gotcha-green-modules-dead-system` **Модули зелёные — система мертва.** 74 честных
|
||
юнит-теста, архитектура по гайдлайнам — а ключевая функция ядра не вызывается
|
||
нигде, автосейва не существует (ноль вызовов), софтлок за 12 минут. → orphan-check
|
||
«кто вызывает» по каждой публичной функции ядра; интеграционный тест полного цикла;
|
||
для накопительных доменов — headless-прогон N шагов с проверкой, что метрики
|
||
двигаются. *(Ad-Tycoon, независимое ревью)*
|
||
21. `gotcha-orphan-config` **Конфиг-поле без потребителя.** Поля загружаются,
|
||
валидируются — и никем не читаются; рядом код с хардкодом, расходящимся с конфигом
|
||
в 100 раз. → вынес в конфиг = есть потребитель и тест, что читается именно оно;
|
||
валидация при старте; единицы сходятся. *(Ad-Tycoon)*
|
||
22. `gotcha-gitignored-dependency` **Зависимость от артефакта вне git.** Детект платформы
|
||
завязан на плагин из каталога в .gitignore — работает только на машине разработки,
|
||
в чистом клоне и CI ломается молча. → гейт «чистый клон собирается и стартует».
|
||
*(Ad-Tycoon)*
|
||
|
||
### Версии и контракты
|
||
|
||
23. `gotcha-version-sot` **Несколько «источников истины» версии.** pyproject vs
|
||
настройки vs package.json расползаются. → SoT-файлы письменно, остальное не
|
||
считается. *(BAZA)*
|
||
24. `gotcha-version-symmetry` **Автовыравнивание версий «для симметрии».** PATCH
|
||
независим — ровнять = фейковые релизы. → бамп только изменившегося. *(BAZA)*
|
||
25. `gotcha-lexicographic-semver` **Лексикографическое сравнение версий.**
|
||
`"0.10.0" < "0.9.0"` строками — стреляет в OTA, когда MINOR доходит до 10. →
|
||
сравнение только semver-парсером. И вторая линия: **guard от даунгрейда на стороне
|
||
клиента** — `strcmp(...)!=0` означало «ставлю всё, что отличается»; серверная
|
||
монотонность фида — единая точка отказа, способная забрикать флот одной ошибочной
|
||
публикацией; намеренный откат — «старый код под более высокой версией».
|
||
*(АПЕКС, ADR-023)*
|
||
26. `gotcha-feature-test-debt` **Тестовый долг фичи.** Фича изменила ответ эндпоинта,
|
||
тесты не тронули — красный набор всплыл через неделю у чужой сессии. → тесты
|
||
контракта в том же коммите, полный прогон перед push. *(BAZA)*
|
||
27. `gotcha-tsc-no-b` **`tsc` без `-b` не проверяет project references** `[TS]`.
|
||
Локально зелёный, сборка на сервере падает. → `tsc -b --noEmit` при
|
||
composite/references. *(АПЕКС)*
|
||
|
||
### Windows и кодировки `[Windows]`
|
||
|
||
28. `gotcha-gitattributes` **Переносы строк без `.gitattributes`.** autocrlf →
|
||
LF↔CRLF-дрейф, в Linux-контейнере ломаются скрипты с CRLF. → `* text=auto eol=lf`
|
||
с ПЕРВОГО коммита. *(EventPlan: 80+ предупреждений)*
|
||
29. `gotcha-non-ascii-toolchain` **Не-ASCII там, где системная кодировка.** Три канала:
|
||
(а) файлы, которые парсит тулчейн — только ASCII *(АПЕКС: PlatformIO упал на
|
||
кириллице в partitions_apex.csv — cp1251)*; (б) **stdout вспомогательных скриптов**
|
||
на Windows — cp1251, `UnicodeEncodeError` на любом спецсимволе →
|
||
`PYTHONIOENCODING=utf-8` или ASCII-вывод *(воспроизведено дважды при аудите)*;
|
||
(в) **stdin хуков** читать как UTF-8 байты явно — иначе кириллица-мохито
|
||
*(office-dashboard 2c047ee)*.
|
||
30. `gotcha-windows-bindmount` **Windows bind-mount врёт.** Задержка синхронизации →
|
||
контейнер видит старый файл. → docker cp или --no-cache при сборке фронта. *(BAZA)*
|
||
31. `gotcha-case-insensitive-headers` **`HTTPClient.h` vs `HttpClient.h`** — две
|
||
библиотеки, различающиеся регистром имени, конфликтуют на регистронезависимой ФС.
|
||
*(АПЕКС)*
|
||
|
||
### Инфраструктура `[инфра/прод]`
|
||
|
||
32. `gotcha-shared-proxy` **Общий реверс-прокси: глобальные объекты и Host.**
|
||
Одноимённые `map`/`upstream` → «duplicate map»; proxy_pass по IP без `Host $host` →
|
||
400 от whitelist. → имена глобальных объектов уникальны per-vhost; Host всегда.
|
||
*(BAZA)*
|
||
33. `gotcha-fail2ban-self` **Авто-защиты банят свою автоматизацию.** fail2ban целевой
|
||
системы банит настройщика после 2 SSH-коннектов (ssh-copy-id = 2 = бан). → свой IP
|
||
в доверенные ДО работ. *(BAZA: NAS)*
|
||
34. `gotcha-pipefail` **Пайп без `set -o pipefail`** → код возврата от последней команды:
|
||
упавший pg_dump + живой gzip = пустой архив и «успех». Зелёный статус над битым
|
||
дампом хуже отсутствия бэкапа. *(мониторинг)*
|
||
35. `gotcha-tls-bare-ip` **Запрос по голому IP при сертификате на домен** провалит
|
||
TLS-проверку — в инструкциях и конфигах всегда домен. *(АПЕКС)*
|
||
36. `gotcha-crypto-defaults` **Дефолты криптоутилит небезопасны.** htpasswd по умолчанию
|
||
bcrypt cost 5 (32 раунда) — задавать стоимость явно (`-C 12`). *(мониторинг)*
|
||
37. `gotcha-https-redirect-proxy` **HTTPS-редирект за SSL-прокси хостинга** =
|
||
бесконечный редирект: `RewriteCond %{HTTPS} off` не видит прокси. → редиректы —
|
||
средствами панели/прокси; за прокси о схеме судят по `X-Forwarded-Proto`.
|
||
*(лендинг; родня `gotcha-shared-proxy` — за прокси реальность не та, что видит
|
||
приложение)*
|
||
38. `gotcha-robots-secret` **Секретный путь в robots.txt.** Строка `Disallow` сама
|
||
выдаёт путь любому, кто откроет robots. → секретные пути не упоминать нигде
|
||
публично. *(лендинг)*
|
||
39. `gotcha-double-cron` **Два механизма расписания** (`/etc/cron.d/*` и `crontab -e`) →
|
||
задача дважды за ночь. → SoT расписания — один файл, создаётся setup-скриптом.
|
||
*(АПЕКС)*
|
||
40. `gotcha-nginx-details` **Мелочи nginx:** `expires` + `add_header Cache-Control`
|
||
вместе = дублирующийся заголовок; на Debian пользователь `www-data`, не `nginx`.
|
||
*(АПЕКС)*
|
||
|
||
### Процесс
|
||
|
||
41. `gotcha-docs-drift` **Документация отстаёт молча.** Месяц без сведения = 35 файлов
|
||
лжи и день работы 10 субагентов. → ритуал закрытия сессии (III.4), каждый раз.
|
||
*(BAZA)*
|
||
42. `gotcha-god-files` **God-файлы съедают контекст и дисциплину.** serializers на 940
|
||
строк, страница на 1290. → делить при росте, карта крупных файлов с автопроверкой
|
||
(III.1), точечное чтение. *(BAZA)*
|
||
43. `gotcha-phantom-tails` **Фантомные хвосты плана.** «Незакрытые» пункты roadmap
|
||
давно сделаны — сессия готова повторить работу. → статусы по факту кода, сверка
|
||
перед реализацией (III.5). *(BAZA: 2 из 4)*
|
||
44. `gotcha-silent-later` **Молчаливое «потом».** Каждое «потом допишем» без записи
|
||
исчезает. → всё отложенное — письменно (план, ADR-компромисс, реестр долгов,
|
||
memory) в момент откладывания. *(BAZA)*
|
||
45. `gotcha-ci-wrong-branch` **CI слушает не ту ветку.** Workflows на master, работа
|
||
месяц шла в dev — гейты тихо перестали существовать. → сменил ветвенную модель →
|
||
в тот же коммит триггеры CI/хуков/автоматики. *(BAZA d9804c2)*
|
||
46. `gotcha-hook-shell-blind` **Хук слеп к реальному шеллу.** Matcher `Bash`, работа
|
||
идёт в PowerShell — защита существует и не работает. → покрытие хука по всем
|
||
шеллам, проверяется скриптом целостности. *(АПЕКС)*
|
||
47. `gotcha-renumbered-parent` **Перенумерация родительского документа** тихо ломает все
|
||
производные ссылки. → ссылаться слагом; при реструктуризации — прогнать ссылки в
|
||
производных. *(EventPlan: ссылки на литанию били в пустоту через 2 часа)*
|
||
48. `gotcha-two-agent-configs` **Две конфигурации агента.** Проект вёлся двумя
|
||
инструментами, конфигурация дважды мигрировала и рассинхронизировалась. → одна
|
||
инструменто-нейтральная точка входа; чужие каталоги удаляются тем же коммитом.
|
||
*(Ad-Tycoon ADR-010)*
|
||
49. `gotcha-automated-prompt` **Системные инъекции неотличимы от пользователя.**
|
||
Автоуведомление фоновой задачи пришло как «промпт пользователя» и снимало
|
||
экстренную остановку. → механика, реагирующая на «пользователь написал», обязана
|
||
фильтровать автоматику. *(office-dashboard v0.13.2)*
|
||
50. `gotcha-llm-stale-configs` **Конфиги по памяти модели.** Синтаксис быстро меняющихся
|
||
сервисов, выдуманный по памяти, не совпадает с текущей версией *(HS+auth: Authentik
|
||
2026.x убрал Redis)*. → сверка с актуальной официальной докой; версии пинуются и
|
||
записываются ДО генерации.
|
||
51. `gotcha-modal-drag-close` **Модалка закрывается при drag-выделении текста**
|
||
`[React]` (mouseup ловится на бэкдропе). → закрытие по клику, начатому и
|
||
завершённому на бэкдропе. *(АПЕКС)*
|
||
52. `gotcha-404-swallows-detail` **Глобальный 404-хендлер глотает содержательные
|
||
`detail` роутеров.** → хендлер пропускает существующий detail. *(АПЕКС)*
|
||
|
||
---
|
||
|
||
**Финал:** литания прочитана, профиль определён, триаж проведён, каркас развёрнут и
|
||
отревьюен — можно начинать. Новые грабли проекта — в живой раздел
|
||
`docs/architecture.md#Грабли проекта` (копия литании read-only), обобщаемое —
|
||
периодически в мастер-литанию: она живёт, пока её кормят инцидентами.
|