docs: документация переведена на канон av-dev-pm
- беклог и план переехали в docs/tasks (38 задач, 11 целей), слаги переименованы с транслита на английские, 85 ссылок поправлены - conventions.md разобран в docs/conventions/, local-research.md — в docs/research/, review-journal.md — в docs/review.md с разделом настройки конвейера; заведены security.md, adr/ и .pm.json - шаг docs.py check добавлен в task gate; поведение в architecture.md помечено девятью маркерами долга, database.md получил настройки с числовым значением
This commit is contained in:
@@ -3,7 +3,11 @@
|
||||
Памятка для работы над healthlog. Перед задачей прочитай также
|
||||
[docs/passport.md](docs/passport.md) (цель, сценарии, референсы),
|
||||
[README.md](README.md), [docs/architecture.md](docs/architecture.md),
|
||||
[docs/conventions.md](docs/conventions.md) и [docs/plan.md](docs/plan.md).
|
||||
[docs/conventions/README.md](docs/conventions/README.md),
|
||||
[docs/security.md](docs/security.md) и [docs/tasks/PLAN.md](docs/tasks/PLAN.md).
|
||||
|
||||
Документация ведётся по канону `av-dev-pm` (версия в `docs/.pm.json`);
|
||||
раскладку проверяет `av-dev-pm:canon`, содержимое ведёт `av-dev-pm:docs`.
|
||||
|
||||
## Что это
|
||||
|
||||
@@ -24,43 +28,50 @@ Module path — `git.vakhrushev.me/av/healthlog`.
|
||||
|
||||
## Инварианты
|
||||
|
||||
- **Точки хранятся дословно.** Часовой объект держит точки ровно в том виде,
|
||||
в каком их прислал HAE. Начнём что-то отбрасывать внутри точки — потеряем
|
||||
безвозвратно.
|
||||
- **Хранилище — свёртка по журналу.** Экспорт Apple это снапшот всей истории,
|
||||
Что нарушать нельзя. `severity` рядом с формулировкой — по ней проходы ревью
|
||||
присваивают вес находке, а не выводят его заново.
|
||||
|
||||
- **Точки хранятся дословно.** `critical`, необратимо. Часовой объект держит
|
||||
точки ровно в том виде, в каком их прислал HAE. Начнём что-то отбрасывать
|
||||
внутри точки — потеряем безвозвратно.
|
||||
- **Хранилище — свёртка по журналу.** `critical`, необратимо.
|
||||
Экспорт Apple это снапшот всей истории,
|
||||
доставки HAE после его даты — события поверх. Состояние всегда пересобираемо:
|
||||
`import(экспорт) + replay(доставки по received_at)`. Поэтому сырой архив
|
||||
живёт до следующего проверенного экспорта (~2 ГБ за квартал), а свёртка
|
||||
обязана быть детерминированной. Что не восстанавливается — `stateOfMind`
|
||||
(его в экспорте нет) и верхние слои за периоды с удалёнными доставками;
|
||||
каталог обязан говорить об этом честно, а не досчитывать молча.
|
||||
- **Сохранили — значит приняли.** Код ответа отражает доставку, а не разбор:
|
||||
битый JSON — 400, непонятое содержимое — 200.
|
||||
- **Ничего не теряем молча.** Идентичность — координаты
|
||||
- **Сохранили — значит приняли.** `critical`, необратимо: отказ приёма теряет
|
||||
доставку навсегда. Код ответа отражает доставку, а не разбор: битый JSON —
|
||||
400, непонятое содержимое — 200.
|
||||
- **Ничего не теряем молча.** `critical`, обратимо пересборкой — но только
|
||||
пока архив жив. Идентичность — координаты
|
||||
(`метрика + слой + начало + конец`), у точки-измерения конец равен началу:
|
||||
под одной меткой лежит до трёх записей сна. Ключ одной формы для всех точек —
|
||||
отдельного класса «эпизодных метрик» нет. `source` в ключ не входит, он
|
||||
нестабилен. Хеш
|
||||
канонизированного содержимого остался детектором изменений. При
|
||||
нестабилен. Хеш канонизированного содержимого остался детектором изменений. При
|
||||
столкновении выигрывает **более полная** точка, а не последняя. Изменение
|
||||
запечатанного часа — `WARN`, но данные всё равно пишутся.
|
||||
- **Дыры закрываются сами.** Три прохода разной глубины (5 минут / сутки /
|
||||
неделя). Настройки данных у проходов теперь **разные** — намеренно, они
|
||||
- **Дыры закрываются сами.** `major`, обратимо. Три прохода разной глубины
|
||||
(5 минут / сутки / неделя). Настройки данных у проходов теперь **разные** — намеренно, они
|
||||
наполняют разные слои; это безопасно ровно потому, что слой входит в ключ.
|
||||
- **Форма Apple не транслируется.** Значения отдаём как пришли, нормализовано
|
||||
только время (`ts_utc` + офсет исходной зоны). Единственное добавление —
|
||||
- **Форма Apple не транслируется.** `major`, обратимо пересборкой. Значения
|
||||
отдаём как пришли, нормализовано только время (`ts_utc` + офсет исходной зоны). Единственное добавление —
|
||||
стабильный код рядом с переведённой строкой: HAE отдаёт «БДГ» и «Сидячий
|
||||
образ жизни» на языке телефона, а родной экспорт — коды HealthKit, и без
|
||||
словаря эти два источника не сойтись.
|
||||
- **Своей агрегации в хранении нет — есть слои.** Метрика лежит в той
|
||||
подробности, в какой пришла (`sample`/`raw`/`minute`/`hour`); слой выводится
|
||||
- **Своей агрегации в хранении нет — есть слои.** `critical`, обратимо
|
||||
пересборкой. Метрика лежит в той подробности, в какой пришла (`sample`/`raw`/`minute`/`hour`); слой выводится
|
||||
из выравнивания меток, а не из заголовка HAE — тот врёт.
|
||||
- **Агрегация в ответе — только измеренная.** Род свёртки выводится сверкой
|
||||
слоёв между собой (часовое = сумма минутных → накопительная, = среднее →
|
||||
- **Агрегация в ответе — только измеренная.** `critical`, обратимо: ответ не
|
||||
хранится, но потребитель уже принял по нему решение. Род свёртки выводится
|
||||
сверкой слоёв между собой (часовое = сумма минутных → накопительная, = среднее →
|
||||
мгновенная), а не размечается руками. Род неизвестен — свёртки нет. И
|
||||
никогда не суммируем нижний слой HAE: это интерполяция, а не сэмплы.
|
||||
- **Секреты не в логах** — токены приёма и чтения. Данные о здоровье
|
||||
чувствительны: тела запросов только на `DEBUG` и с обрезкой.
|
||||
- **Секреты не в логах.** `critical`, необратимо: утечка не отзывается. Токены
|
||||
приёма и чтения. Данные о здоровье чувствительны: тела запросов только на
|
||||
`DEBUG` и с обрезкой. Периметр и модель угроз — [docs/security.md](docs/security.md).
|
||||
|
||||
## Команды
|
||||
|
||||
@@ -83,37 +94,83 @@ Module path — `git.vakhrushev.me/av/healthlog`.
|
||||
- `task tidy` — `go mod tidy`
|
||||
- `task setup` — установка golangci-lint
|
||||
|
||||
## Гейт
|
||||
|
||||
- **Команда:** `task gate` (`BASE=<rev>` — база диффа; без неё берётся
|
||||
`git merge-base HEAD master`). Шаги: сборка, `go vet`, `golangci-lint`,
|
||||
`gofmt`, тесты, флаки, гонки, покрытие изменённых строк, миграции против
|
||||
`docs/database.md`, образцы конфига, секреты и данные о здоровье в индексе,
|
||||
уязвимости, раскладка документов (`docs.py check`).
|
||||
- **Где логи шагов:** `tmp/gate/<шаг>.log` (каталог под `.gitignore`); сводка —
|
||||
в терминале.
|
||||
- **Исходы:** 0 — зелёный; ненулевой — красный, и до его починки опиниативные
|
||||
проходы ревью **не запускаются**.
|
||||
- **Что красит безусловно:** любой файл из `./data` в индексе, любой токен в
|
||||
индексе, непокрытая изменённая строка, миграция без правки `docs/database.md`.
|
||||
Причина одна на все: это ровно те отказы, которые не видны глазами и стоят
|
||||
необратимо.
|
||||
- **Чего в гейте намеренно нет и кто обязан это гонять:**
|
||||
`task verify:archive` (минута прогона, данные есть только на этой машине) и
|
||||
`task verify:busy` (25 секунд). Гоняет их **человек или оркестратор задачи**
|
||||
перед любым изменением правила разбора, идентичности или слияния — а не «когда
|
||||
вспомнит». Прецедент, когда молчащая краснота прожила две задачи, записан в
|
||||
[docs/review.md](docs/review.md).
|
||||
|
||||
## Запреты
|
||||
|
||||
- **Не запускать сервис против `./data`** мимо `task up` / `task run`: это
|
||||
рабочая база `./data/healthlog.db` и рабочий архив `./data/raw`, других копий
|
||||
нет ни на какой машине.
|
||||
- **Не удалять и не перезаписывать `./data`** — ни файл базы, ни каталог
|
||||
архива, ни отдельные тела. Подмена базы после пересборки — действие человека
|
||||
при остановленном сервисе.
|
||||
- **Ничего из `./data` не попадает** ни в git, ни в логи выше `DEBUG`, ни в
|
||||
вывод агента.
|
||||
- **Не ходить в rivendell** и вообще наружу: деплой и выкладка спрашиваются
|
||||
всегда.
|
||||
- `testdata` — `internal/hae/testdata`: реальные пакеты HAE с вычищенными
|
||||
токенами. Временное — в `./tmp` (под `.gitignore`).
|
||||
|
||||
## Работа
|
||||
|
||||
- **Основная ветка:** `master`. От неё считается база диффа
|
||||
(`git merge-base HEAD master`), в неё вливает батч, от неё ветвятся задачи.
|
||||
- **Необратимое** (спрашивается у человека всегда): деплой, выкладка наружу,
|
||||
удаление или перезапись чего-либо в `./data`, подмена файла базы результатом
|
||||
пересборки.
|
||||
- **Общий станок:** `task verify:archive`. Покраснев, он врывается в
|
||||
замороженный спринт: сходимость журнала — тот инвариант, ради которого
|
||||
существует архив, и жить с красным прогоном нельзя.
|
||||
- **Ориентир по размеру спринта:** 5–8 задач. Ориентир, а не закон.
|
||||
- **Что такое «сделана»:** пайплайн `av-dev-pipeline:task-pipeline` пройден
|
||||
целиком **и** критерии приёмки задачи проверены поимённо.
|
||||
|
||||
## Процесс
|
||||
|
||||
Задачи — в [docs/backlog](docs/backlog/README.md) (один файл на задачу, индекс
|
||||
производен). Порядок и его обоснование — в [docs/plan.md](docs/plan.md).
|
||||
Задачи — в [docs/tasks/BACKLOG.md](docs/tasks/BACKLOG.md) (один файл на запись,
|
||||
индексы производны), цели — в [docs/tasks/PLAN.md](docs/tasks/PLAN.md). Ведёт их
|
||||
скилл `av-dev-pm:tasks`, спринт и ритуал между спринтами — `av-dev-pm:session`.
|
||||
|
||||
Работа над задачей идёт скиллом `healthlog-task-pipeline`: беклог → `opsx:explore` →
|
||||
`opsx:propose` → ревью спек (профиль `design`) → `opsx:apply` → ревью кода →
|
||||
`opsx:archive` → чистка беклога → коммит. Ревью — скилл `healthlog-review-pipeline`,
|
||||
проходы — агенты `healthlog-review-*`.
|
||||
Работа над задачей идёт скиллом `av-dev-pipeline:task-pipeline`: задача →
|
||||
`opsx:explore` → `opsx:propose` → ревью спек (профиль `design`) → `opsx:apply` →
|
||||
ревью кода → `opsx:archive` → закрытие задачи → коммит. Ревью — скилл
|
||||
`av-dev-pipeline:review-pipeline`, проходы — агенты `av-dev-pipeline:review-*`,
|
||||
проектная настройка конвейера — [docs/review.md](docs/review.md).
|
||||
|
||||
**Действуем автономно.** Умолчание — делать, а не спрашивать. Вопрос, который
|
||||
решать не мне, **вынимается блокером** в секцию `блокеры` беклога, задача
|
||||
переформулируется на остаток, остаток доводится до коммита. Блокеры разбираются
|
||||
пачками; из чего состоит пункт блокера — в
|
||||
[индексе беклога](docs/backlog/README.md). Спрашиваем только про
|
||||
**необратимое**: деплой, выкладку наружу, удаление или перезапись данных в
|
||||
`./data`.
|
||||
решать не мне, **выносится в раздел «Вопросы»** файла задачи и помечается тегом
|
||||
`question`; задача с открытым вопросом в спринт не берётся, а сама работа
|
||||
переформулируется на остаток и доводится до коммита. Спрашиваем немедленно
|
||||
только про **необратимое** — список выше.
|
||||
|
||||
**Развилка или блокер — сперва prior art.** Проект не уникален: прежде чем
|
||||
**Развилка или вопрос — сперва prior art.** Проект не уникален: прежде чем
|
||||
проектировать своё, смотрим, как это решено в референсах
|
||||
[паспорта](docs/passport.md) и в интернете. Готовое решение либо берётся, либо
|
||||
отвергается с названной причиной — и причина идёт в `architecture.md`.
|
||||
|
||||
Гейт блокирует: пока `task gate` красный, опиниативные проходы ревью не
|
||||
запускаются.
|
||||
|
||||
**Поток не останавливается.** Телефон шлёт непрерывно и молча. Сломанный приём,
|
||||
оставленный работать, теряет данные необратимо: доставка, не попавшая в
|
||||
архив, в журнал не попадает вовсе — телефон её не перешлёт.
|
||||
Ничего из `./data` не попадает ни в git, ни в логи выше `DEBUG`, ни в вывод
|
||||
агента.
|
||||
|
||||
## Конвенции
|
||||
|
||||
@@ -123,7 +180,7 @@ Module path — `git.vakhrushev.me/av/healthlog`.
|
||||
(`depguard`). Пересказывать эти правила не нужно — линтер скажет точнее.
|
||||
|
||||
Прозой остаётся то, что правилом не выражается:
|
||||
[docs/conventions.md](docs/conventions.md) — уровень лога по адресату,
|
||||
[docs/conventions/README.md](docs/conventions/README.md) — уровень лога по адресату,
|
||||
единственный логирующий чекпоинт на доменной границе, трансляция ошибки на
|
||||
внешней границе, самодокументируемый `config.example.toml`, время в БД в UTC
|
||||
RFC 3339, ULID через `ident`.
|
||||
@@ -132,7 +189,7 @@ RFC 3339, ULID через `ident`.
|
||||
`testdata`. Документация формата тонкая и местами расходится с тем, что
|
||||
приложение реально шлёт, — источником истины служат живые данные.
|
||||
|
||||
Что показал реальный поток — [docs/local-research.md](docs/local-research.md).
|
||||
Что показал реальный поток — [docs/research/apple-health.md](docs/research/apple-health.md).
|
||||
Читать **до** работы над разбором: там же лежат находки, которых нет в
|
||||
документации HAE (поле `source` существует; порядок ключей в JSON нестабилен,
|
||||
поэтому хеш содержимого считается по канонической форме с рекурсивной
|
||||
|
||||
Reference in New Issue
Block a user