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