Привёл набор capabilities в OpenSpec к цепочке обработки, чтобы имя capability отвечало одному поведению. Чисто по спекам, код и поведение системы не меняются. Change refactor-capability-boundaries (архивирован): - recognition разделён на recognition (разбор LLM) + metadata-match (сверка с базами) - review выделен из web-ui + мигрирован из docs/specs/review-ux.md - новые capability из docs/specs: file-layout, download-tracking, notifications - identity очищен до инфра-id; приём (инфохэши, дедуп, ядро приёма) — в ingest - уведомление о рассинхроне перенесено из state-reconciliation в notifications - дубль владения путём и безопасного undo оставлен в state-reconciliation Итог: 11 capabilities, openspec validate --strict проходит (+37/−11 требований). Источник истины по мигрированным темам переехал в openspec/specs (шапки в docs). Снят пункт беклога «Пересмотр набора capabilities». Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
86 lines
5.5 KiB
Markdown
86 lines
5.5 KiB
Markdown
# identity Specification
|
|
|
|
## Purpose
|
|
|
|
Как система идентифицирует сущности домена: ULID-ключи (канонический
|
|
lowercase-вид, нормализация и валидация на входных границах) и корреляция
|
|
сущностей в логах по id. Инфохэши загрузки (`download_infohash`),
|
|
дедупликация приёма и инвариант «не более одной активной загрузки на
|
|
infohash» (атомарный возврат в активное состояние) — в capability `ingest`.
|
|
## Requirements
|
|
### Requirement: ULID как первичный ключ сущностей
|
|
|
|
Каждая сущность домена SHALL иметь первичный ключ ULID — TEXT, 26 символов
|
|
Crockford base32, генерируемый приложением в момент создания записи через
|
|
единственную точку генерации (`internal/ident`). Сущности: `download`,
|
|
`recognition`, `hint`, `override`, `metadata_candidate`, `file_link`.
|
|
Канонический вид SHALL быть lowercase. Числовые AUTOINCREMENT-ключи в новых таблицах
|
|
использоваться SHALL NOT. Идентификатор партии раскладки (`apply_batch_id`)
|
|
SHALL генерироваться тем же способом.
|
|
|
|
#### Scenario: Создание загрузки
|
|
|
|
- **WHEN** принимается новая загрузка
|
|
- **THEN** её `id` — валидный ULID в lowercase
|
|
- **AND** `id` уникален глобально (не совпадает с id других сущностей)
|
|
|
|
#### Scenario: Хронологическая сортировка
|
|
|
|
- **GIVEN** две загрузки, созданные последовательно
|
|
- **WHEN** записи сортируются по `id` лексикографически
|
|
- **THEN** порядок совпадает с порядком создания
|
|
|
|
### Requirement: Нормализация и валидация id на входных границах
|
|
|
|
Внешние идентификаторы SHALL валидироваться как ULID и нормализоваться к
|
|
lowercase до обращения к хранилищу — это касается всех входных границ:
|
|
URL `/download/{id}`, параметры форм и команд. Синтаксически невалидный id SHALL обрабатываться как
|
|
несуществующая сущность (404 для страниц), без обращения к БД.
|
|
|
|
#### Scenario: Uppercase-вариант id в URL
|
|
|
|
- **GIVEN** существующая загрузка с id `01jz…` (lowercase)
|
|
- **WHEN** клиент открывает `/download/01JZ…` (uppercase)
|
|
- **THEN** открывается страница той же загрузки
|
|
|
|
#### Scenario: Мусор вместо id
|
|
|
|
- **WHEN** клиент открывает `/download/abc!!!`
|
|
- **THEN** ответ — 404, запрос к БД не выполняется
|
|
|
|
### Requirement: Корреляция сущностей в логах
|
|
|
|
Записи журнала, относящиеся к сущности, SHALL содержать её id в атрибуте
|
|
`<entity>_id` (`download_id`, `recognition_id`, `batch_id`, …); работа в
|
|
контексте загрузки ведётся через scoped-логгер с `download_id`. Благодаря
|
|
глобальной уникальности ULID поиск по значению id (grep/jq) SHALL находить
|
|
все записи журнала, относящиеся к сущности, независимо от имени поля.
|
|
|
|
#### Scenario: Путь загрузки по логам
|
|
|
|
- **GIVEN** загрузка прошла приём, распознавание и раскладку
|
|
- **WHEN** журнал фильтруется по значению её `id`
|
|
- **THEN** находятся записи всех этапов (ingest, recognition, file-layout)
|
|
|
|
### Requirement: Миграция существующих записей
|
|
|
|
Существующие записи SHALL получить ULID-идентификаторы одной миграцией с
|
|
сохранением всех связей (FK) и хронологии: timestamp-часть ULID SHALL
|
|
браться из `created_at` записи, чтобы лексикографический порядок новых id
|
|
соответствовал историческому порядку создания. Существующий
|
|
`download.infohash` SHALL быть перенесён в `download_infohash`
|
|
(нормализация к lowercase, `kind` по длине hex: 40 — `v1`, 64 — `v2`);
|
|
столбцы `download.infohash` и `download.idempotency_key` SHALL быть удалены.
|
|
|
|
#### Scenario: Связи и порядок после миграции
|
|
|
|
- **GIVEN** БД с загрузками, распознаваниями и файловыми ссылками на
|
|
числовых id
|
|
- **WHEN** миграция выполнена
|
|
- **THEN** все FK-связи сохранены (распознавания/ссылки указывают на те же
|
|
загрузки)
|
|
- **AND** порядок загрузок по `id` совпадает с порядком по `created_at`
|
|
- **AND** каждый прежний `infohash` представлен записью в
|
|
`download_infohash`
|
|
|