добавлены плагины av-dev-tasks и av-dev-pipeline
Пара плагинов с намеренно проведённой границей: av-dev-tasks отвечает за то, что делаем и в каком порядке, av-dev-pipeline — за то, как ведём одну задачу. Зависимости между ними нет: управление задачами работает и с ручным исполнением, пайплайн — на проекте с любым учётом задач. - av-dev-tasks — преемник av-dev-backlog: цели вместо приоритетов, спринт под одну цель с заморозкой набора, различение вопроса и блокера, каденция «вопросы — разбор — переоценка — набор». Раскладка docs/tasks с items/, PLAN.md, BACKLOG.md, SPRINT.md, REJECTED.md; проверенное из av-dev-backlog перенесено, не переписано. - av-dev-pipeline — вынос того, что лежало копиями в healthlog и jellybit (3628 строк) и уже разошлось: цикл SDD, конвейер ревью с обязательным триажем, прогон нескольких задач разом. Проектная специфика вынесена в файл-бриф, charter'ы несут метод. Коммит фиксирует состояние на момент ревью: три прохода нашли блокирующие дефекты (нет шага, заводящего бриф; git rebase на занятой worktree ветке; sprint drop пишет наполовину) — они чинятся следующими коммитами. Сохранено как база, от которой видно правки.
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
# Шаблон брифа проекта
|
||||
|
||||
Скопируй в `docs/review-brief.md` и заполни. Контракт разделов — в
|
||||
[project-brief.md](project-brief.md); здесь только образец заполнения.
|
||||
|
||||
Курсивом даны пояснения — их из готового брифа убирают. Примеры взяты из двух
|
||||
разных проектов (коллектор данных с непрерывным потоком и связующий сервис вокруг
|
||||
внешних демонов), чтобы было видно, как один и тот же раздел выглядит при разной
|
||||
природе проекта.
|
||||
|
||||
---
|
||||
|
||||
## Проект
|
||||
|
||||
*Абзац: что делает — и чего не делает.*
|
||||
|
||||
> Коллектор выгрузок с телефона. Принимает доставки, хранит их и отдаёт другим
|
||||
> сервисам. Это **хранилище, а не аналитика**: принять, дедуплицировать,
|
||||
> сохранить, отдать. Не переименовывать поля источника, не интерпретировать
|
||||
> значения; свёртка считается только в ответе на запрос.
|
||||
|
||||
> Связующий сервис между качалкой и медиасервером: принимает задание, качает,
|
||||
> распознаёт содержимое, раскладывает файлы ссылками. **Не медиатека и не
|
||||
> плеер** — ничего не хранит сверх метаданных о раскладке.
|
||||
|
||||
## Инварианты
|
||||
|
||||
*Проверяемое свойство + последствие + severity по умолчанию. Цитируются
|
||||
формулировкой.*
|
||||
|
||||
- **Точка сохраняется дословно.** Незнакомое поле не отбрасывается, число не
|
||||
округляется при записи. Нарушение — необратимая потеря: сырой архив живёт
|
||||
14 дней, дальше истина только в свёртке. По умолчанию `critical`.
|
||||
- **Источник неприкосновенен.** Только `mkdir`/`link(2)`/`unlink` собственных
|
||||
ссылок; файлы под каталогом загрузок не трогаются никогда. Нарушение —
|
||||
повреждение чужих данных, необратимое. По умолчанию `critical`.
|
||||
- **Сохранили — значит приняли.** Код ответа отражает доставку, а не разбор:
|
||||
непонятое содержимое — `200`, тело уже на диске. Нарушение стоит доставки,
|
||||
которую отправитель не повторит. По умолчанию `critical`.
|
||||
- **Секреты и данные пользователя не в логах.** Тело запроса — только на `DEBUG`
|
||||
и с обрезкой. По умолчанию `critical`.
|
||||
- **Агрегации при записи нет.** Нарушение искажает историю молча и
|
||||
диагностируется только сверкой с внешним источником, то есть месяцами позже.
|
||||
По умолчанию `major`, `critical` — если испорченное невосстановимо.
|
||||
|
||||
## Гейт
|
||||
|
||||
- **Команда:** `task gate BASE=<база>`; база по умолчанию —
|
||||
`git merge-base HEAD master`, на `master` — `HEAD~1`.
|
||||
- **Логи шагов:** `tmp/gate/<шаг>.log`. Сводка печатает `OK`/`FAIL`/`WARN`/`SKIP`;
|
||||
краснит гейт только `FAIL`.
|
||||
- **Шаги:** сборка, `vet`, линтеры, форматирование, тесты, повторный прогон на
|
||||
флаки, `-race`, покрытие изменённых строк, накат миграций с нуля, поиск
|
||||
секретов, `govulncheck`.
|
||||
- **Красят безусловно** *(перечислить с причиной — это главная часть раздела)*:
|
||||
- `no-user-data` — файл из каталога данных попал под контроль версий: убрать
|
||||
обычным коммитом уже нельзя;
|
||||
- `config-samples` — структура конфига изменилась, а образец нет: забытое поле
|
||||
обнаруживается не тестом, а тем, что через полгода о нём никто не знает;
|
||||
- `migrations` — миграции не накатываются с нуля: восстановление перестаёт
|
||||
работать ровно тогда, когда оно нужно;
|
||||
- `er-schema` — миграция тронута, а схема в документации не обновлена.
|
||||
- **Чего в гейте намеренно нет:** прогон на живом корпусе (`task verify:archive`)
|
||||
— минута работы и данные, которых нет ни на какой другой машине. У этой
|
||||
проверки краснота не видна никому до следующей задачи, которая до неё
|
||||
дотянется, — говори об этом в границах покрытия.
|
||||
|
||||
## Команды
|
||||
|
||||
- Карта проекта для архитектурного прохода: `task review:context > tmp/review-context.md`
|
||||
- Поднять изменение вживую: `task restart`, логи — `task logs`
|
||||
- Тесты и линт: `task test`, `task lint`
|
||||
- Дорогое, вручную: `task verify:archive` (минута, живые данные)
|
||||
- **Запускать запрещено:** ничего, что пишет в `./data`, в рабочую БД и в боевой
|
||||
каталог архива. Замеры — только на копиях в `./tmp`.
|
||||
|
||||
## Прод и поток
|
||||
|
||||
- **Где:** один статический бинарь в контейнере на домашнем сервере, перед ним
|
||||
обратный прокси с TLS, SQLite на диске. Ни оркестратора, ни реплик, ни дежурной
|
||||
смены.
|
||||
- **Внешние зависимости и как каждая отказывает:** прокси — рвёт соединение на
|
||||
длинном теле; диск — заполняется и тормозит; СУБД — отдаёт «занято» под
|
||||
параллельной записью; приложение-источник на телефоне — молча перестаёт слать.
|
||||
*(В другом проекте здесь были бы качалка, медиасервер, LLM и база метаданных, и
|
||||
каждая — со своим «отвечает медленно», а не только «упала».)*
|
||||
- **Кто заметит отказ:** один пользователь-владелец, в лучшем случае вечером, а
|
||||
скорее не заметит вовсе.
|
||||
- **Характер потока:** телефон шлёт непрерывно и молча; обратной связи у
|
||||
отправителя нет, об отказах он не сообщает, расписание плавает. Тихо
|
||||
сломавшаяся доставка — главный эксплуатационный риск.
|
||||
- **Числа (с провенансом):** нижний слой — порядка 135 тыс. точек в сутки
|
||||
(замер, `docs/local-research.md`); тела доходили до 42 МБ (там же); запись —
|
||||
read-modify-write под конкурентными доставками (`docs/architecture.md`).
|
||||
- **Обратимость:** падение сервиса обратимо — отправитель дошлёт широким
|
||||
проходом. Потеря или порча точки необратима. Поэтому тихая порча весит больше,
|
||||
чем «сервис вернул 500».
|
||||
|
||||
## Модель угроз
|
||||
|
||||
- **Недоверенное:** тело доставки целиком (имена метрик, единицы, формы точек,
|
||||
метки времени, глубина вложенности, размер); заголовки доставки, часть которых
|
||||
участвует в решениях; содержимое архива внешнего экспорта (имена файлов внутри
|
||||
zip мы не формировали); параметры читающего API.
|
||||
- **Из чего строятся пути и ключи:** файл сырого архива —
|
||||
`raw/ГГГГ/ММ/ДД/<ulid>.json.gz`, дата берётся из времени приёма, имя — из
|
||||
генератора идентификаторов; ключ записи — `метрика + слой + начало + конец`,
|
||||
источник в ключ не входит.
|
||||
- **Разграничение:** статические токены в `Authorization: Bearer`, раздельные на
|
||||
запись и на чтение; конфиг под `0600`.
|
||||
- **Что дороже:** данные пользователя дороже токена. Путь, по которому значение
|
||||
доезжает до лога выше `DEBUG`, до ответа с ошибкой или до `testdata` в git, —
|
||||
полноценная находка, а не замечание по гигиене.
|
||||
- **Вне модели:** злоумышленник в локальной сети; вредоносный оператор;
|
||||
компрометация поставщика данных; мультиарендность. Находки этих классов не
|
||||
выводятся — они никогда не будут исправлены.
|
||||
|
||||
## Карта
|
||||
|
||||
- Актуальные спеки: `openspec/specs/<capability>/spec.md`
|
||||
- Дельта-спеки изменения: `openspec/changes/<id>/specs/*/spec.md`
|
||||
- Конвенции прозой: `docs/conventions.md`. Механизировано и потому **не
|
||||
проверяется проходом по конвенциям**: форма логов, `fmt.Print*`/`os.Getenv`/
|
||||
`time.Now` мимо единых точек, сравнение ошибок, сторонние пакеты ошибок —
|
||||
всё это правила в `.golangci.yml`.
|
||||
- Архитектура и решения: `docs/architecture.md`
|
||||
- Журнал проскочивших дефектов: `docs/review-journal.md`
|
||||
- **Единые точки:** идентификаторы — `internal/ident`; время — `store.Now()`;
|
||||
разбор дат входного формата — один парсер в `internal/parse`; маппинг доменной
|
||||
ошибки в код ответа — одна точка в `internal/httpapi`; путь приёма — `ingest`,
|
||||
общий для HTTP и CLI. Инвентарь целиком выгружает `task review:context`.
|
||||
- **Нумерованные артефакты:** миграции — `internal/store/migrations/NNNN_*.sql`,
|
||||
номер монотонный, следующий свободный смотреть там же.
|
||||
- Задачи: `docs/backlog/` *(пайплайн только читает и сообщает исход)*
|
||||
- Реальные пакеты для тестов разбора: `internal/parse/testdata` — там данные
|
||||
пользователя с вычищенными токенами, наружу не копировать
|
||||
- Временное: `./tmp` (не системный `/tmp`)
|
||||
- **Не трогать:** `./data` — боевой архив и БД
|
||||
|
||||
## Типовые узлы
|
||||
|
||||
*Род узла + 3–5 специфичных проверяемых свойств.*
|
||||
|
||||
- **Разбор входного формата** — поведение на усечённом и враждебном входе,
|
||||
границы размера, отсутствие паники, детерминизм, судьба незнакомых полей.
|
||||
- **HTTP-обработчик приёма** — валидация формы конверта до записи, лимит тела и
|
||||
архивная бомба, что попадает в ответ, а что в лог, отсутствие доменной логики
|
||||
в транспорте.
|
||||
- **Обработчик читающего API** — предсказуемость размера ответа, поведение при
|
||||
пустом диапазоне, коды ответа на невозможный запрос.
|
||||
- **Репозиторий** — границы транзакции, конкурентная запись того же ключа,
|
||||
откуда берутся время и id, что возвращается при отсутствии записи,
|
||||
идемпотентность повторной записи.
|
||||
- **Файловое хранилище с ретеншеном** — атомарность записи, поведение при
|
||||
неполной записи и нехватке места, что удаляется и по какому критерию, можно ли
|
||||
удалить лишнее.
|
||||
- **CLI-команда пересборки** — идемпотентность повторного прогона, поведение при
|
||||
отмене на середине, что остаётся после падения, отчёт для человека.
|
||||
- **Клиент внешнего сервиса** — таймаут, протяжка `context`, поведение при
|
||||
«медленно» против «упало», ретраи и их граница.
|
||||
|
||||
## Триггеры
|
||||
|
||||
- `deep`: миграция в `internal/store/migrations/`, новый пакет `internal/*`,
|
||||
изменение контракта читающего API, правило слияния или вывод слоя.
|
||||
- «Видимое снаружи» (то есть `standard`): эндпоинт, форма ответа, код ответа
|
||||
приёма, формат лога.
|
||||
- `reimpl` запускается, когда изменение вводит **новое правило слияния,
|
||||
идентичности или разбора**.
|
||||
|
||||
## Недоступно проверке
|
||||
|
||||
- Поведение внешнего приложения-источника на следующем его обновлении.
|
||||
- Что реально лежит в системе-источнике: сверить можно только ручным экспортом,
|
||||
а он делается раз в 2–3 месяца.
|
||||
- Поведение таблицы под объёмом нескольких лет истории и реальный профиль
|
||||
нагрузки.
|
||||
- Завязка внешних потребителей на текущую форму ответа.
|
||||
- Суждение «этой функциональности не должно существовать».
|
||||
Reference in New Issue
Block a user