go-learn/prestart-litany.md
sab.code.lab 1c85091186 chore: восстановление репозитория из снапшота v0.3.1
Прежняя git-история утрачена при переносе проекта на машину владельца
(снапшот без .git). Хэши коммитов в docs/reports/* относятся к утраченной
истории.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 09:34:02 +02:00

759 lines
86 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Литания перед стартом нового проекта
> **Как применять:** положи этот файл в корень нового проекта и дай 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), обобщаемое —
периодически в мастер-литанию: она живёт, пока её кормят инцидентами.