download-tracking: требование о re-read source_type приведено к коду
- re-read `source_type` перечитывается под блокировкой после тик-снимка, а остаточное окно вывода имени названо известным ограничением с ценой и достоверным маршрутом восстановления (ручной шаг + `Retry`, не самоисцеление) - в `ingest` снята парная ложная гарантия «воркер добавит раздачу файлом», добавлены сценарии на оба окна апгрейда и на недоступные байты `.torrent` - заведён ADR о том, что при разрыве спека↔код двигается тот, чья формулировка сильнее рационали
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
# Спека следует за кодом, когда гарантия недостижима, а окно узкое
|
||||
|
||||
- **Дата:** 2026-08-06
|
||||
- **Источник:**
|
||||
[openspec/changes/archive/2026-08-06-catched-source-type-reread-wording/design.md](../../openspec/changes/archive/2026-08-06-catched-source-type-reread-wording/design.md)
|
||||
|
||||
## Решение
|
||||
|
||||
Требование `download-tracking` о re-read `source_type` приведено **к коду**, а
|
||||
не наоборот: перечитывать под блокировкой переходов **после тик-снимка**, а
|
||||
остаточное окно (апгрейд magnet → `.torrent`, легший в вызов namer'а) названо в
|
||||
спеке известным ограничением с ценой и маршрутом восстановления. Код не тронут.
|
||||
|
||||
Общее правило, которое отсюда следует для проекта: когда спека и код разошлись,
|
||||
**двигается тот, чья формулировка сильнее рационали**. Требование, обещавшее
|
||||
больше, чем нужно ради его собственной причины, чинится текстом; недостающая
|
||||
гарантия чинится кодом.
|
||||
|
||||
## Почему
|
||||
|
||||
Формулировка была сильнее своей же рационали:
|
||||
|
||||
> Рациональ исходного требования — «не полагаться на снимок, снятый ранее вне
|
||||
> блокировки» — выполнен первым re-read под замком. Формулировка «непосредственно
|
||||
> перед добавлением» была сильнее рационали и кодом не достигается: между
|
||||
> re-read и `qbt.Add` стоит namer, вынесенный из-под блокировки намеренно
|
||||
> (требование «Медленные вызовы SHALL выполняться вне блокировки»).
|
||||
|
||||
Цена починки кодом оказалась несоразмерной ущербу:
|
||||
|
||||
> Вариант A (пересобирать `addReq` из `before` под замком) отклонён: это не
|
||||
> однострочник — `sourceAddParts` читает байты `.torrent` и держать его под
|
||||
> блокировкой нельзя, а при апгрейде корректен был бы и повторный вызов namer'а
|
||||
> (подсказка имени берётся из другого источника).
|
||||
|
||||
Главное же — **молчащая ложная гарантия дороже названного ограничения**. Пока
|
||||
спека утверждала недостижимое, дефект был невидим ровно потому, что нормативный
|
||||
дом поведения его отрицал; аудит capability находил его заново.
|
||||
|
||||
## Рассмотренные варианты
|
||||
|
||||
- **A — починить код** (пересобирать запрос на добавление из свежей записи под
|
||||
блокировкой). Отвергнут по цене: чтение блоба `.torrent` под замком
|
||||
недопустимо, корректная версия тянет повторный вызов namer'а. Отвергнут
|
||||
**отложенно, а не окончательно**: спека поэтому не запрещает его нормативно
|
||||
(см. `design.md` D6).
|
||||
- **B — привести спеку к коду** (принято). Наблюдаемое поведение прежнее,
|
||||
меняется заявленное.
|
||||
- **C — оставить как есть.** Отвергнут: расхождение спека↔код воспроизводится
|
||||
каждым аудитом, а читатель спеки считает окно закрытым.
|
||||
|
||||
## Последствия
|
||||
|
||||
- `+` Нормативный дом поведения перестал утверждать недостижимое; ограничение
|
||||
видно и имеет названную цену вместо молчания.
|
||||
- `+` Парная ложная гарантия снята и в `ingest` («воркер добавит раздачу
|
||||
файлом») — иначе она бы просто переехала в соседнюю capability и всплыла
|
||||
следующим аудитом.
|
||||
- `+` Заодно назван исход ветки «сохранённые байты `.torrent` недоступны», не
|
||||
заказанной до этого ни одним сценарием.
|
||||
- `−` Цена окна выше, чем считала постановка задачи: не «подождать и нажать
|
||||
`Retry`», а ожидание `magnet_timeout` (дефолт `24h`) **плюс ручной шаг** —
|
||||
убрать зависшую раздачу из qBittorrent. `Retry` сам не добивает: `metaDL`
|
||||
считается живым и здоровым торрентом, и `Retry` к нему перецепляется без
|
||||
повторного `add` (`design.md` D2). На этой пересмотренной цене вопрос
|
||||
«чинить ли окно кодом» открыт заново.
|
||||
- `−` Новая нормативная ветка (недоступные байты `.torrent`) держится на чтении
|
||||
кода: теста-оракула у неё нет, её регрессия зелёный гейт не покрасит.
|
||||
- `−` Прецедент «спека следует за кодом» опасен буквальным применением. Он
|
||||
оправдан **только** когда формулировка сильнее рационали и ущерб от разрыва
|
||||
назван; «код так делает, значит так и запишем» этой записью не
|
||||
санкционируется.
|
||||
|
||||
## Триггер пересмотра
|
||||
|
||||
Записан отдельно, чтобы не гонять круг заново:
|
||||
|
||||
> Окно возвращается в работу вариантом A, когда апгрейд в вызове namer'а
|
||||
> случится в эксплуатации хотя бы раз — признак в логах — либо когда стоимость
|
||||
> ручного шага станет заметной. До того — принято и описано.
|
||||
@@ -42,6 +42,7 @@
|
||||
|
||||
| Дата | Запись | Статус |
|
||||
| --- | --- | --- |
|
||||
| 2026-08-06 | [Спека следует за кодом, когда гарантия недостижима, а окно узкое](ADR-2026-08-06-spec-follows-code-on-narrow-window.md) | — |
|
||||
| 2026-08-04 | [Конвейер ревью и пайплайн задачи переезжают в плагины](ADR-2026-08-04-review-pipeline-to-plugin.md) | — |
|
||||
| 2026-07-24 | [Локальная сборка образа + доставка docker save/load](ADR-2026-07-24-local-image-build.md) | — |
|
||||
| 2026-07-23 | [Конвейер ревью: гейт, generative-проходы и триаж](ADR-2026-07-23-review-pipeline-generative.md) | — |
|
||||
|
||||
Reference in New Issue
Block a user