Files
jellybit/docs/adr/ADR-2026-08-06-spec-follows-code-on-narrow-window.md
T
av 30ee598547 download-tracking: требование о re-read source_type приведено к коду
- re-read `source_type` перечитывается под блокировкой после тик-снимка, а
  остаточное окно вывода имени названо известным ограничением с ценой и
  достоверным маршрутом восстановления (ручной шаг + `Retry`, не самоисцеление)
- в `ingest` снята парная ложная гарантия «воркер добавит раздачу файлом»,
  добавлены сценарии на оба окна апгрейда и на недоступные байты `.torrent`
- заведён ADR о том, что при разрыве спека↔код двигается тот, чья формулировка
  сильнее рационали
2026-08-06 14:44:21 +03:00

6.5 KiB
Raw Blame History

Спека следует за кодом, когда гарантия недостижима, а окно узкое

Решение

Требование 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'а случится в эксплуатации хотя бы раз — признак в логах — либо когда стоимость ручного шага станет заметной. До того — принято и описано.