Files
avandClaude Fable 5 37f2f6481a Идентичность на ULID: download_infohash, guarded-дедуп, миграция (ulid-identity)
Все сущности переехали с INTEGER AUTOINCREMENT на TEXT ULID (lowercase,
internal/ident — единая точка генерации и разбора; oklog/ulid). Инфохэши
загрузки — множество (download_infohash, v1/v2 гибридных торрентов): дедуп
и сопоставление в поллинге по любому из хешей, magnet-парсер отдаёт оба
хеша гибридной ссылки, усечённый v2-хеш v2-only раздач не хранится.

Инвариант «не более одной активной загрузки на infohash» вместо снятого
unique-индекса держат guarded-методы store в одной write-транзакции
(_txlock=immediate): CreateDownloadIfNoActive (приём/adopt, с доносом
недостающих хешей), ActivateIfNoOtherActive (retry/recovery/relink, отказ
до побочных эффектов), guarded AddInfohashes; SetDownloadState отклоняет
терминал→активное как механический бэкстоп.

Миграция 0006 — первая Go-миграция goose: пересоздание таблиц при
включённых FK, backfill ULID с timestamp из created_at (хронология id
сохранена), разнос infohash, удаление idempotency_key. BREAKING: формат id
в URL/логах/Telegram, REST-поля id (string) и infohashes (список).

Новая конвенция docs/conventions/database.md (без числовых PK), корреляция
в логах grep'ом по голому ULID, ER-схема обновлена. Спеки: новая capability
identity, MODIFIED в state-reconciliation; change заархивирован. Пройдены
ревью дизайна и кода (по 8 углов), все находки исправлены с
регрессионными тестами.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 21:25:00 +03:00

70 lines
5.5 KiB
Markdown
Raw Permalink 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.
## Why
Идентичность в домене сегодня держится на двух хрупких вещах: загрузка
фактически идентифицируется инфохэшем (`idempotency_key`), хотя у одной
логической загрузки хешей несколько (v1/v2/гибрид, перезалив — другой хеш),
а первичные ключи всех таблиц — числовые автоинкременты, не уникальные между
таблицами и неудобные для корреляции в логах. Это фундамент (шаг 1 черновика
[logical-title-model](../../../docs/drafts/logical-title-model.md)) для
«второго сезона», «докачивания» и истории переходов; менять PK дешевле
сейчас, пока БД маленькая и ссылок на идентификатор немного.
## What Changes
- **ULID (канонически lowercase) как TEXT PK всех сущностей**: `download`,
`recognition`, `hint`, `override`, `metadata_candidate`, `file_link`.
Генерация — в приложении (`oklog/ulid`, monotonic entropy). **BREAKING**:
формат id меняется в URL (`/download/{id}`, `/review/{id}`), ссылках и
callback-data Telegram-бота, логах; в REST JSON поле `id` меняет тип
`number → string`, поле `infohash` заменяется списком `infohashes`.
- **Новая таблица `download_infohash`** (`download_id`, `infohash`,
`kind` v1|v2, составной PK `(infohash, download_id)` — один хеш легитимно
принадлежит нескольким загрузкам во времени) — множество хешей одной
загрузки; дедуп переезжает на проверку активности по этой таблице,
столбцы `download.idempotency_key` и `download.infohash` удаляются.
- **Поиск по любому из хешей** — при приёме (дедуп) и в поллинге qBittorrent.
- `apply_batch_id` генерируется как ULID (столбец уже TEXT).
- **Go-миграция goose**: backfill ULID существующим строкам с timestamp-частью
из `created_at` (сортировка id сохраняет хронологию), переписывание FK,
разнос текущего `infohash` в `download_infohash`.
- **Конвенция `docs/conventions/database.md`**: PK — TEXT ULID, генерится
приложением; числовой AUTOINCREMENT не используем; у деталей/связей
допустим естественный ключ.
- **Логи**: у каждой сущности поле `<entity>_id`; глобальная уникальность
ULID делает grep по голому id штатным способом корреляции; обновить
примеры в `docs/conventions/logging.md`.
- ER-схема `docs/specs/database.md` обновляется в этом же change.
## Capabilities
### New Capabilities
- `identity`: как система идентифицирует сущности домена — ULID-ключи и их
канонический вид (нормализация на входных границах), множество инфохэшей
загрузки, инвариант «одна активная загрузка на infohash» (дедуп при приёме),
сопоставление раздачи в поллинге по любому из хешей.
### Modified Capabilities
- `state-reconciliation`: требование «терминализация восстанавливает
`idempotency_key`» меняется — инвариант «одна активная задача на infohash»
обеспечивается проверкой активности по `download_infohash`, отдельный
снимаемый/восстанавливаемый ключ исчезает.
## Impact
- **Код**: `store` (типы id `int64 → string`, все запросы, миграция),
`ingest` (дедуп через `download_infohash`), `worker` (сопоставление в
поллинге по множеству хешей), `httpapi`/веб-UI (парсинг и валидация ULID в
`/download/{id}`, ссылки), Telegram-уведомления (ссылки на загрузку).
- **Зависимости**: + `github.com/oklog/ulid/v2` (чистый Go, CGO не нужен).
- **БД**: пересоздание всех шести таблиц (SQLite меняет PK только через
rebuild) одной миграцией; первая Go-миграция в проекте — goose до сих пор
использовался только с SQL-файлами, нужна регистрация Go-миграций.
- **Документация**: новая `docs/conventions/database.md`, правки
`docs/conventions/logging.md`, ER-схема `docs/specs/database.md`,
ссылка на новую конвенцию из `CLAUDE.md`/README конвенций.
- **Не меняется**: семантика дедупа (нашли активную загрузку по любому хешу →
та же загрузка), явные `ORDER BY created_at` в списках, инварианты
безопасности данных.