Рефакторинг границ capabilities: цепочка загрузка→матч→ревью→раскладка (openspec)
Привёл набор 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>
This commit is contained in:
@@ -3,13 +3,11 @@
|
||||
## Purpose
|
||||
|
||||
Как система идентифицирует сущности домена: ULID-ключи (канонический
|
||||
lowercase-вид, нормализация и валидация на входных границах), множество
|
||||
инфохэшей загрузки (`download_infohash`), инвариант «не более одной
|
||||
активной загрузки на infohash» (дедупликация приёма, атомарный возврат в
|
||||
активное состояние), корреляция сущностей в логах по id.
|
||||
|
||||
lowercase-вид, нормализация и валидация на входных границах) и корреляция
|
||||
сущностей в логах по id. Инфохэши загрузки (`download_infohash`),
|
||||
дедупликация приёма и инвариант «не более одной активной загрузки на
|
||||
infohash» (атомарный возврат в активное состояние) — в capability `ingest`.
|
||||
## Requirements
|
||||
|
||||
### Requirement: ULID как первичный ключ сущностей
|
||||
|
||||
Каждая сущность домена SHALL иметь первичный ключ ULID — TEXT, 26 символов
|
||||
@@ -50,81 +48,6 @@ URL `/download/{id}`, параметры форм и команд. Синтак
|
||||
- **WHEN** клиент открывает `/download/abc!!!`
|
||||
- **THEN** ответ — 404, запрос к БД не выполняется
|
||||
|
||||
### Requirement: Множество инфохэшей загрузки
|
||||
|
||||
Загрузка SHALL иметь одну или более записей инфохэша (`download_infohash`:
|
||||
`infohash` lowercase hex, `kind` ∈ `v1`|`v2`). При приёме magnet-ссылки
|
||||
SHALL записываться ВСЕ известные из неё хеши — гибридный magnet несёт и
|
||||
btih (v1), и btmh (v2); `kind` определяется по длине hex (40 — `v1`, 64 —
|
||||
`v2`). Когда qBittorrent сообщает для раздачи оба хеша (`infohash_v1`,
|
||||
`infohash_v2`), система SHALL дописывать недостающие записи загрузке;
|
||||
усечённый хеш v2-only раздачи (поле `hash` qBittorrent, 40 hex от v2)
|
||||
записываться SHALL NOT. Сопоставление раздачи qBittorrent с загрузкой
|
||||
(поллинг, discover) SHALL выполняться по любому из известных хешей. Один и
|
||||
тот же infohash MAY принадлежать нескольким загрузкам во времени (повторный
|
||||
приём после терминального состояния), но активной из них MUST быть не более
|
||||
одной.
|
||||
|
||||
#### Scenario: Гибридный торрент раскрывает оба хеша
|
||||
|
||||
- **GIVEN** загрузка принята по magnet с v1-хешем
|
||||
- **WHEN** qBittorrent отдаёт раздачу с заполненными `infohash_v1` и
|
||||
`infohash_v2`
|
||||
- **THEN** у загрузки появляются обе записи (`kind` = `v1` и `v2`)
|
||||
|
||||
#### Scenario: Сопоставление по v2-хешу
|
||||
|
||||
- **GIVEN** загрузка с записями v1- и v2-хешей
|
||||
- **WHEN** поллинг находит раздачу, совпавшую только по v2-хешу
|
||||
- **THEN** раздача сопоставляется с этой загрузкой
|
||||
|
||||
### Requirement: Дедупликация приёма по любому из хешей
|
||||
|
||||
При приёме система SHALL искать **активную** (нетерминальную) загрузку по
|
||||
любому из известных хешей и, найдя, SHALL возвращать её вместо создания
|
||||
новой. Проверка активности и вставка новой загрузки с её хешами SHALL
|
||||
выполняться атомарно (в одной write-транзакции), поддерживая инвариант «не
|
||||
более одной активной загрузки на infohash». Отдельного снимаемого/
|
||||
восстанавливаемого ключа идемпотентности в схеме быть SHALL NOT — активность
|
||||
выводится только из `state`.
|
||||
|
||||
#### Scenario: Повторный приём при активной загрузке
|
||||
|
||||
- **GIVEN** активная загрузка с infohash `h`
|
||||
- **WHEN** принимается magnet с тем же `h`
|
||||
- **THEN** новая загрузка не создаётся, возвращается существующая
|
||||
|
||||
#### Scenario: Повторный приём после завершения
|
||||
|
||||
- **GIVEN** загрузка с infohash `h` в терминальном состоянии (`done`)
|
||||
- **WHEN** принимается magnet с тем же `h`
|
||||
- **THEN** создаётся новая загрузка со своим ULID и записью `h`
|
||||
|
||||
### Requirement: Атомарность возврата загрузки в активное состояние
|
||||
|
||||
Система SHALL атомарно (в одной write-транзакции) проверять на каждом пути,
|
||||
возвращающем загрузку из терминального состояния в активное (ручной retry,
|
||||
воскрешение фоновой сверкой, повторная раскладка/relink) или создающем её
|
||||
(приём, adopt чужой раздачи), что никакая другая активная загрузка не
|
||||
владеет любым из хешей этой, и при владении SHALL отказывать в переходе,
|
||||
сохраняя инвариант «не более одной активной загрузки на infohash».
|
||||
Отказ SHALL происходить до побочных эффектов во внешних системах
|
||||
(повторного добавления торрента в qBittorrent).
|
||||
|
||||
Та же проверка SHALL применяться к дозаписи хешей загрузке (раскрытие
|
||||
гибридного торрента): хеш, которым владеет другая активная загрузка,
|
||||
дописан быть SHALL NOT. Прямой перевод терминальной загрузки в активное
|
||||
состояние в обход этой проверки SHALL отклоняться хранилищем (механический
|
||||
бэкстоп вместо удалённого unique-индекса).
|
||||
|
||||
#### Scenario: Retry при занятом хеше
|
||||
|
||||
- **GIVEN** загрузка #1 в `failed` с хешем `h`, и другая активная загрузка
|
||||
#2 с тем же `h`
|
||||
- **WHEN** пользователь вызывает retry для #1
|
||||
- **THEN** переход отклоняется с пояснением, #1 остаётся в `failed`
|
||||
- **AND** активной по `h` остаётся #2
|
||||
|
||||
### Requirement: Корреляция сущностей в логах
|
||||
|
||||
Записи журнала, относящиеся к сущности, SHALL содержать её id в атрибуте
|
||||
@@ -159,3 +82,4 @@ btih (v1), и btmh (v2); `kind` определяется по длине hex (40
|
||||
- **AND** порядок загрузок по `id` совпадает с порядком по `created_at`
|
||||
- **AND** каждый прежний `infohash` представлен записью в
|
||||
`download_infohash`
|
||||
|
||||
|
||||
Reference in New Issue
Block a user