# Триаж ревью: change `catched-source-type-reread-wording`

## Сводка

- **Профиль:** `quick`. **Режим прогона:** по графу. Второй чекпоинт (после
  apply, до archive). Изменение — только спеки и артефакты change, ни строки
  кода: критерий 4.3 подтверждён гейтом (`git diff --stat master` пусто по
  `internal/`).
- **Гейт:** зелёный. Кодовые шаги — `SKIP` по составу диффа (нет `.go`),
  `canon` и `gitleaks` — `OK`. `openspec validate --strict` valid. 12 тестов
  `internal/worker` PASS независимым прогоном.
- **Проходы поимённо:**
  1. `review-gate` — отработал, находок 0 (зелёный, три позитивных оракула).
  2. `review-specs` — отработал, 4 находки (все `minor`).
  3. `review-code` — отработал, 1 находка (`minor`).
  4. `review-triage` — этот отчёт.
  - Состав сверен с профилем `quick`: расхождений нет, все проходы профиля
    отработали. Непущенные стадии — см. «Границы покрытия».
- **Баланс находок:** 5 на входе → 4 пункта на выходе (находки 2 и 4 слиты по
  одной причине — обе суть неполное проведение решений D5/D6 в тексте дельты).
  Молчком не выброшено ничего.
- **Независимая перепроверка триажа** (запрошена явно): полнота копий обеих
  `MODIFIED`-дельт сверена своим `diff -u` тел требований база↔дельта
  (`download-tracking` «Добавление пойманной загрузки в qBittorrent», `ingest`
  «Приём источника из `.torrent`-файла»). Все ханки — добавления либо
  заявленные переформулировки; **ни один сценарий и ни одна нормативная клауза
  базы не потеряны**. Заявление проходов о полноте подтверждено.
- **Ложноположительные:** все 4 пункта сверены со списком «Типовые
  ложноположительные» `docs/review.md` — совпадений нет.
- **Согласие проходов:** пересекающихся находок нет, каждый проход нашёл своё;
  приоритет по совпадению не поднимался.

## Блокирует мердж

Пусто. Инварианты `CLAUDE.md` диффом не затрагиваются (дифф не содержит кода и
не ослабляет ни одно `SHALL` про источник, последнюю копию, песочницу
библиотеки, перезапись). Кандидатов в `critical`/`major` не было ни на входе,
ни после добычи оракулов.

## Стоит исправить сейчас

### 1. Архивируемый `proposal.md` и файл задачи сохраняют опровергнутый маршрут «самоисцеления» — следующий читатель повторит ровно тот дефект, ради которого change заведён

- Файл: `openspec/changes/catched-source-type-reread-wording/proposal.md:13-14,24-26`; `docs/tasks/items/catched-source-type-refresh.md:5,31-32,42-43`
- Severity: minor
- Confidence: high
- Оракул: добыт грепом при триаже. `proposal.md:13` — «остаточное namer-окно
  **самоисцеляется** (`magnet_timeout` → `failed` → ручной `Retry` перечитает
  актуальный `source_type`)»; `:26` — «ручной `Retry` **добивает** уже с
  актуальным `source_type`». Собственная дельта того же change
  (`specs/download-tracking/spec.md:73-82`) и `design.md` D2 утверждают
  обратное: «Сама собой задача из этого состояния не восстанавливается…
  `Retry` при живом торренте перецепляется… Восстановление сегодня требует
  ручного шага». Код: `worker.go:1043-1059` (`reAdd = !alive`),
  `classify:1130-1142` (`metaDL` → жив), тест `TestRetryReattachesNoReadd`.
  Тот же устаревший текст — в файле задачи.
- Последствие: после archive `proposal.md` остаётся постоянным документом о
  намерении и прямо противоречит своей же дельте. Читатель возьмёт дешёвый
  маршрут «просто Retry», не сделает ручной шаг (убрать зависшую раздачу из
  qBittorrent), задача останется в `failed`, читатель решит, что «спека врёт»,
  и заведёт находку заново — зеркальная копия дефекта, который change
  закрывает.
- Предложение: привести `## Why` и второй буллет `## What Changes` в
  `proposal.md` к формулировке дельты (ссылкой на `design.md` D2); тем же
  движением поправить «Насколько больно»/«Решение» в
  `docs/tasks/items/catched-source-type-refresh.md`. Заметка-отклонение в
  `tasks.md:60-65` уже честная — её не трогать.
- Найдено проходом: `review-specs`
- Действие: инлайн

### 2. Новая нормативная ветка «байты `.torrent` недоступны» не имеет оракула: её регрессия пройдёт зелёный гейт и молча сделает свежезаписанное требование ложным

- Файл: `openspec/changes/catched-source-type-reread-wording/specs/download-tracking/spec.md:55-57,198-205`; код — `internal/worker/worker.go:455-461`
- Severity: minor
- Confidence: high
- Оракул: перепроверен триажем. `grep "^func Test" internal/worker/catched_test.go`
  — 12 тестов, ветки `prepErr != nil` (worker.go:456-459: `add` не зовётся,
  `WARN`, задача остаётся в `catched`) нет ни в одном. Ближайший
  `TestRetryTorrentMissingBytesRollsBack` покрывает другой путь (`retryDownload`,
  `:1074-1082` — откат в прежнее состояние, не удержание в `catched`).
- Последствие: дельта впервые делает исход ветки нормативным (`add` SHALL NOT,
  SHALL оставаться в `catched`), но исход держится только на чтении кода.
  Будущая правка, уводящая недоступность байтов сразу в `failed`, не уронит ни
  один тест — класс «молчание»: требование станет ложным незаметно.
- Предложение: развилка, готовый вопрос владельцу: «Ветке "байты недоступны"
  нужен оракул, но тест — правка под `internal/`, запрещённая критерием 4.3.
  Варианты: (а) завести follow-up задачу на
  `TestProcessCatchedMissingTorrentBytesKeepsCatched` после archive — цена:
  одна маленькая задача, ветка получает оракул; (б) принять отсутствие оракула
  и записать его явно в границы дельты/журнал — цена: регрессия ветки молчалива
  до ручного разбора; (в) расширить критерий 4.3 этого change и добавить тест
  сейчас — цена: пересмотр критерия приёмки человеком.»
- Найдено проходом: `review-specs`
- Действие: развилка

### 3. Оговорки об окне читаются нормативно вопреки D5/D6: `ingest`-сценарий нормирует поведение воркера, а обоснование в `download-tracking` подводит чтение блоба под запрет медленных вызовов

- Файл: `openspec/changes/catched-source-type-reread-wording/specs/ingest/spec.md:104-112`; `specs/download-tracking/spec.md:63-65`
- Severity: minor
- Confidence: medium
- Оракул: `design.md` дословно. D5: «Нормативный дом ограничения при этом один —
  `download-tracking`; `ingest` только не противоречит ему» — но `THEN`/`AND`
  нового `ingest`-сценария утверждают исход добавления позитивно, а сценарий и
  есть проверяемая единица требования. D6: «обоснование запрета — деталь текущей
  реализации, а не свойство поведения. Вариант A в форме "перечитать только
  `source_type` под замком…" медленных вызовов под блокировкой не требует
  вовсе» — но дельта (`:63-65`) связывает «не пересобирается» причинной связкой
  с правилом о медленных вызовах, хотя сама же перечисляет медленные вызовы
  поимённо («вывод имени через LLM, `qbt.Add`», `:89`), и локальные чтения БД
  под `w.mu` штатны (`worker.go:448,485,533`).
- Последствие: два симметричных исхода одного корня. (а) В день реализации
  варианта A (назван отложенным, не отменённым) `download-tracking` поправят
  гарантированно, `ingest`-сценарий вспомнят отдельно — забытый, он станет
  требованием, прямо противоречащим коду: зеркальная копия исходного дефекта.
  (б) Автор, взявшийся закрывать окно, прочтёт причинную связку как нормативное
  препятствие дешёвому варианту — ровно исход, который D6 хотел исключить.
- Предложение: в `ingest`-сценарии свести `THEN`/`AND` к тому, что нормирует
  приём (байты сохранены, `source_type` стал `torrent`), исход добавления
  оставить ссылкой на `download-tracking`; в `download-tracking:63-65` убрать
  причинную связку с правилом о медленных вызовах, оставив факт («сегодня
  запрос под блокировкой не пересобирается; подробности решения — вне этого
  требования»).
- Найдено проходом: `review-specs` (две находки, слиты триажем: одна причина —
  неполное проведение D5/D6 в тексте)
- Действие: инлайн

### 4. Цитата требования `state-reconciliation` усечена — при сверке спека↔спека, которую change сам называет своим механизмом защиты, ссылка не найдётся текстовым поиском

- Файл: `openspec/changes/catched-source-type-reread-wording/specs/download-tracking/spec.md:77-78`; `design.md:175-176`
- Severity: minor
- Confidence: high
- Оракул: перепроверен триажем грепом. Фактический заголовок —
  `openspec/specs/state-reconciliation/spec.md:136`: «Ручной повтор
  зависшей/упавшей загрузки **из транспортов**». Оба места change цитируют без
  «из транспортов». Все пять прежних ссылок в архиве
  (`openspec/changes/archive/…`) приводят заголовок целиком — усечение появилось
  только здесь.
- Последствие: `design.md` Risks прямо полагается на «сверка спека↔спека это
  поймает» при изменении `Retry` — а неточная цитата дословным поиском не
  находится, и защита, на которую change ссылается, для этой пары не работает.
- Предложение: дописать «из транспортов» в обеих цитатах.
- Найдено проходом: `review-code`
- Действие: инлайн

## Гипотезы без доказательства

Пусто. Понижений не было: `critical`/`major` на входе не было, все четыре
оставшихся пункта имеют добытый оракул (греп/дифф/дословный текст design), у
двух `Confidence: medium` — они и так `minor`.

## Promote candidates

1. **Дословность цитат заголовков требований при кросс-ссылках между
   capability — механизируемо.** Из находки 4: проверка «каждая цитата
   `<capability> «Заголовок»` существует дословно в
   `openspec/specs/<capability>/spec.md`» — кандидат в шаг `canon`
   (`docs.py check`) или отдельный шаг гейта. Правило текстовое, оракул
   объективный, спорить не о чем.
2. **Термин «тик-снимок».** Дельта вводит компактную форму, которой нет в
   базовых спеках («снимок списка `catched`»). Находкой не поднято: единого
   глоссария нет, долг признан задачей
   `docs/tasks/items/ubiquitous-language-glossary.md` — приписать термин туда.
   (Провенанс: `review-code`.)

## Урожай

Отложенные находки — владельцу задач (`docs/tasks/`, скилл `av-dev-pm:tasks`),
списком:

1. **Диагностика ветки недоступных байтов уводит не туда**: устойчиво
   недоступные байты `.torrent` доводят задачу до `catch_timeout`, и она падает
   с `error_code = qbit_add`, хотя qBittorrent ни при чём. Оракул: `design.md`
   D7 (сознательно отдано урожаем, дельта этого не узаконивает). Провенанс:
   ревью предложения (профиль `design`) → `design.md` D7, подтверждено
   `review-specs`.
2. **Закрывать ли окно namer'а кодом (вариант A)** — открытый вопрос задачи с
   пересмотренной ценой: не «подождать и Retry», а `magnet_timeout` (дефолт
   `24h`) плюс ручной шаг в qBittorrent. Цена, на которой B выбирался против A,
   изменилась — вопрос владельцу. Оракул: `design.md` D2/D3. Провенанс:
   `design.md` D3, `review-specs` находка об окне.
3. **`catch_timeout = 0` не даёт отсечки**: спека обещает предохранитель
   безусловно, код проверяет только при `w.cfg.CatchTimeout > 0`
   (`worker.go:427`), дефолт `10m`. Пред-существующее, вне scope этого change.
   Провенанс: `review-specs`, границы спеки.
4. **Отмена `context` посреди `processCatched`** — пред-существующая
   недоговорённость спеки (что остаётся при обрыве между шагами). Провенанс:
   `review-specs`, границы спеки.
5. **Связка «failed + повторный приём `.torrent` → усыновление зависшей
   раздачи» нормирована прозой, сквозного оракула нет** (проверка размазана по
   двум capability, теста на цепочку целиком нет). Провенанс: `review-specs`,
   границы спеки.
6. **Верхняя граница окна в тексте — момент `qbt.Add`, а сценарий называет
   только «окно вывода имени»**: интервал `worker.go:484-511` (state re-read →
   листинг → `add`, `addReq` не пересобирается) в тексте дельты не разделён.
   Кандидат на уточнение формулировки при следующей правке требования.
   Провенанс: `review-specs`, границы спеки.

## Границы покрытия

**Запущено** (профиль `quick`, режим «по графу»):

- `review-gate` — отработал, зелёный. Кодовые шаги (build, vet, lint, gofmt,
  test, flaky, race, govulncheck, diff-coverage, migrations) — `SKIP` по
  причине «нет изменений в `.go`/`go.mod`»: корректный пропуск по составу
  диффа, не дыра инструментария. Race-детектор не запускался вовсе —
  конкурентность диффом не тронута. Вне чартера гейта — смысловая
  согласованность формулировок дельта-спек.
- `review-specs` — отработал, 4 находки. Не мог проверить в принципе: поведение
  под реальным потоком, живой qBittorrent (в т.ч. фактическую реакцию его
  версий на дубль `add`), качество распознавания. Свои границы спеки назвал —
  разнесены в «Урожай» (пп. 3-6).
- `review-code` — отработал, 1 находка. `docs/conventions/*` построчно не
  читались за пределами двух вопросов проекта: их предмет (код) в диффе
  отсутствует буквально — честная пустота по объекту, не пробел. Оба вопроса
  проекта к `code` — неприменимы (кода и полей конфига в диффе нет).
- `review-triage` — этот отчёт. Ничего нового не находит по определению:
  работает с чужими выводами; пропуск любого прохода — его пропуск тоже.
  Дополнительно выполнено: независимый `diff -u` полноты `MODIFIED` (потерь
  нет), грепы-оракулы находок 1, 2, 4.

**Не запускались** (все — по причине «профиль `quick`: дифф не содержит кода,
поведение снаружи не меняется, новых понятий и правил
идентичности/слияния/разбора не вводится»):

- `review-adversary` (стадия 2) — не запускался. Значит, вопросы проекта к
  `adversary` (пути вне библиотеки, снятие последней копии, крафт-магнет) в
  этом прогоне никто не задавал — к диффу из одних спек они и не применимы.
- `review-ops` (стадия 2) — не запускался. Гонки рассуждением не проверялись —
  и не требовались: конкурентный код не тронут.
- `review-reimpl` (стадия 3) — не запускался.
- `review-architecture` (стадия 4) — не запускался. Вопрос «не появился ли
  второй способ» в этом прогоне не задан.
- `review-rubric` — не запускался.

**Осталось целиком на человеке** — `docs/review.md`, «Недоступно проверке»,
двумя отдельными списками.

*Не проверит ни один проход* (принципиальная граница):

- история инцидентов на umbar и то, что уже ломалось в проде;
- поведение SQLite под реальным объёмом и профилем нагрузки;
- завязка внешних потребителей (Jellyfin, закладки, чужие ссылки) на текущее
  поведение;
- качество распознавания как таковое (размеченный корпус решено не собирать —
  `docs/tasks/REJECTED.md`, 2026-08-06);
- суждение «этой функциональности не должно существовать».

*Перестали проверять сознательно* (пересматривается первым при промахе):

- **идиоматичность Go — с 2026-08-04**: проход `idiom` упразднён при переезде
  на плагин `av-dev-pipeline`; различение «идиоматично против распространено»
  не спрашивает никто; пересмотр — задача `quality-review-agents`. Для этого
  диффа предмета нет (кода нет), но класс остаётся неснятым для проекта.

Плюс общее: поведение под реальным потоком, поведение внешних систем в их
боевых версиях (qBittorrent, трекеры, DHT — весь маршрут восстановления из D2
проверен чтением кода, не живым прогоном: запрет «не ходить в боевой
qBittorrent» соблюдён), вопрос нужности функциональности.

**Документы проекта:**

- `CLAUDE.md` (инварианты с severity) — есть, прочитан; ни одна находка
  инвариантов не трогает, основание `critical` не понадобилось.
- `docs/review.md` — есть; «Типовые ложноположительные» сверены (совпадений
  нет), «Недоступно проверке» перенесено выше двумя списками. **Журнал
  дефектов пуст** (заведён 2026-07-23) — оракулов вида «этот класс здесь уже
  воспроизводился» не существует, подтверждение по журналу было недоступно
  всем проходам.
- `docs/security.md`, `docs/architecture.md` — есть; находкам не потребовались.
- Единого глоссария терминов в проекте нет (долг признан задачей
  `ubiquitous-language-glossary`) — из-за этого «тик-снимок» не поднят
  находкой, а отдан в Promote candidates.
- Недостающих документов ни один проход не заявил.

**Потолок:** не потребовал жертв — 5 находок на входе, 4 пункта на выходе
(слияние по причине, не отсев); молчком не выброшено ничего.
