docs: проект переведён на канон документов версии 4

- каталог задач: PLAN.md → ROADMAP.md с каноническими секциями, все 44
  записи получили тип, заголовки приведены к форме своего типа
- расхождения, найденные судьями канона: исключения инварианта «источник
  неприкосновенен», инвариант про один активный infohash, UTC в logging.md,
  поведение из architecture.md заменено ссылками на спеки
- триггеры профиля ревью переписаны под умолчание standard
This commit is contained in:
av
2026-08-06 13:32:06 +03:00
parent 905a1ee4d3
commit 66d39297c5
66 changed files with 334 additions and 204 deletions
+29 -7
View File
@@ -22,7 +22,9 @@ Driven Development** через OpenSpec — см. раздел ниже.
Go 1.26, один статический бинарь (`CGO_ENABLED=0`). Module path —
`git.vakhrushev.me/av/jellybit`. SQLite через `modernc.org/sqlite` + `sqlx`,
миграции `goose`, HTTP — `chi` + `html/template` + htmx, конфиг —
`pelletier/go-toml/v2`, логи — `log/slog` (структурированный JSON).
`pelletier/go-toml/v2`, разбор `.torrent` и инфохэшей — `anacrolix/torrent`,
пред-парс имени раздачи — `middelink/go-parse-torrent-name`, логи — `log/slog`
(структурированный JSON).
## Инварианты
@@ -32,6 +34,15 @@ Go 1.26, один статический бинарь (`CGO_ENABLED=0`). Module
- **Источник неприкосновенен** — под `paths.downloads` допустимы только чтение
и `link(2)`; никаких `unlink`, `rename`, записи. Нарушение уничтожает
невосстановимые данные пользователя. **Необратимо. `critical`.**
Исключения два, и оба — не наши операции с файловой системой, а вызов
`torrents/delete` qBittorrent с `deleteFiles=true`: (1) `Delete` из
`done`/`orphaned`/`target_missing` по явному подтверждению человека — гард
последней копии там выключен сознательно
([state-reconciliation](openspec/specs/state-reconciliation/spec.md));
(2) уборка воркером **собственного** торрента, добавленного этим же `add`
секундами ранее, когда задачу отменили в окне после `add`
([download-tracking](openspec/specs/download-tracking/spec.md)). Всё
остальное под `paths.downloads` — по-прежнему `critical`.
- **Последняя копия не снимается** — `Undo` отклоняется целиком, если у цели не
осталось других жёстких ссылок (`nlink <= 1`) или исходного файла уже нет.
Частичный откат тоже стёр бы часть данных. **Необратимо. `critical`.**
@@ -47,14 +58,24 @@ Go 1.26, один статический бинарь (`CGO_ENABLED=0`). Module
qBittorrent, ключи LLM и метабаз, токен Telegram, API-ключ Jellyfin.
Утёкший в лог секрет отзывается вручную. **`major`.**
- **Авто-раскладка только при подтверждённом матче в метабазе** — самооценка
LLM гейтом не является
([ADR](docs/adr/ADR-2026-06-13-auto-link-requires-db-match.md)). Обратимо
LLM **единственным** гейтом не является и матч не заменяет: порог
`[recognition].auto_confidence_threshold` стоит поверх матча дополнительным
условием
([ADR](docs/adr/ADR-2026-06-13-auto-link-requires-db-match.md),
[recognition](openspec/specs/recognition/spec.md)). Обратимо
через `Undo`. **`major`.**
- **Не более одной активной загрузки на infohash** — проверка отсутствия другой
активной загрузки и вставка идут одной write-транзакцией (`_txlock=immediate`,
guarded-методы `store`); обход даёт две задачи, претендующие на одну раздачу и
один целевой путь. Обратимо (лишняя закрывается), но состояние расходится.
**`major`.** Поведение — [ingest](openspec/specs/ingest/spec.md).
- **Переходы состояний — только через `worker` под per-download блокировкой**,
и только легальные по декларативному графу. Обход даёт гонку двух
транспортов. **`major`.**
- **Время — только `store.Now()` (UTC), идентификаторы — только `ident`**;
`ident.Parse` на каждой входной границе. Механизировано линтером. **`minor`.**
`ident.Parse` на каждой входной границе. Время механизировано линтером
(`forbidigo` на `time.Now`); правило про `ident` линтером не проверяется —
держится на ревью. **`minor`.**
## Команды
@@ -117,9 +138,10 @@ Go 1.26, один статический бинарь (`CGO_ENABLED=0`). Module
(`git merge-base HEAD master`), в неё вливает батч, от неё ветвятся задачи.
- **Необратимое** (спрашивается у человека всегда): всё, что пишет в
`paths.downloads` или удаляет оттуда; удаление раздачи из qBittorrent вместе
с файлами (`Delete`); снятие последней копии данных; правка уже применённой
миграции; `git push --force`; удаление или перезапись файла в библиотеке
Jellyfin, которого мы не создавали.
с файлами (`Delete`) — кроме уборки собственного, только что добавленного
торрента при отмене (см. исключения инварианта выше); снятие последней копии
данных; правка уже применённой миграции; `git push --force`; удаление или
перезапись файла в библиотеке Jellyfin, которого мы не создавали.
- **Общий станок** — покрасневший `task gate` на `master` врывается в
замороженный спринт: пока он красный, ни одна задача не считается сделанной.
- **Ориентир по размеру спринта:** 5–8 задач. Ориентир, а не закон.