chore: восстановление репозитория из снапшота v0.3.1

Прежняя git-история утрачена при переносе проекта на машину владельца
(снапшот без .git). Хэши коммитов в docs/reports/* относятся к утраченной
истории.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
sab.code.lab 2026-08-07 09:34:02 +02:00
commit 1c85091186
184 changed files with 33303 additions and 0 deletions

759
prestart-litany.md Normal file
View file

@ -0,0 +1,759 @@
# Литания перед стартом нового проекта
> **Как применять:** положи этот файл в корень нового проекта и дай 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. Каркас и финал развёртывания
Развернуть каркас из разделов IV по итогам триажа. Финал: показать пользователю
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, гейты, ревью диффов, интеграцию. **Нижняя граница:** правку 13
строк не делегировать — оверхед дороже. **Нет подходящего агента — остановиться и
спросить, не выдумывать.** Определения агентов модель-нейтральны: модель и 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), обобщаемое —
периодически в мастер-литанию: она живёт, пока её кормят инцидентами.