Files
jellybit/openspec/changes/archive/2026-08-06-cancel-dismiss-split-wording/review/triage.md
T
av 0b02a8c224 specs: в state-reconciliation разведены Cancel и Dismiss по состояниям
- требование «Ручное закрытие загрузки» приведено к коду: два пути закрытия,
  error_code на каждом, раскладка поверхностей — описательно, а не SHALL
- уборка своего торрента после отмены названа исключением по состоянию, а не
  по команде; убрана ложная гарантия «данных пользователя не касается»
- в docs/review.md записан проскочивший дефект гарда окна после add и новый
  вопрос проходу adversary про асимметрию признака владения
2026-08-06 15:50:41 +03:00

368 lines
34 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Триаж чекпоинта 2 — change `cancel-dismiss-split-wording`
Дата: 2026-08-06. Профиль: `standard`, режим: `по графу`.
База диффа: `01e64d60de7495900d9a4c8f5ce9bf811ee7b7e6`.
## Сводка
**Гейт:** зелёный. Кодовые шаги (build/vet/lint/gofmt/test/flaky/race/
diff-coverage/migrations/govulncheck) — `SKIP` с причиной «кода в диффе нет»;
`canon``OK`; `gitleaks``OK`.
**Проходы поимённо:**
| Проход | Исход |
|---|---|
| `review-gate` | зелёный (кодовые шаги SKIP — кода в диффе нет) |
| `review-specs` | отработал, 4 находки + 4 строки границ |
| `review-code` | отработал, 1 находка + подтверждение A3 + блок «нарушений нет» |
| `review-adversary` | отработал, 5 находок (4 воспроизводящих теста), 3 свойства без пути |
| `review-reimpl` | **не запускался** — стадия 3, только `deep`; вне профиля `standard` |
| `review-architecture` | **не запускался на этом чекпоинте** — стадия 4 (`wide`/`deep`); отработал на чекпоинте 1 в профиле `design`, находки закрыты правками предложения (design.md, D1D3, D6D8) |
| `review-ops` | отработал, 3 находки + ответы на обязательные вопросы |
Состав запущенного сверен с профилем `standard` — расхождений нет.
**Счёт находок:** на входе 13 (S1S4, C2, A1, A2, A3adv, A4, A5, O1O3; C1 —
подтверждение приёмки, не находка). После дедупа — 10 (S1=A3adv, C2=A4,
S4=A5). Разложено: 3 блокируют, 4 стоит исправить сейчас, 0 гипотез,
6 в урожай, 1 promote-кандидат.
**Состояние change:** файлы staged, но не закоммичены (`HEAD` == база; дифф
worktree против базы — 5 файлов, 514 вставок, все под
`openspec/changes/cancel-dismiss-split-wording/`). Перед архивацией нужен
коммит — это состояние прогона, не находка.
**Вердикт по архивации:** change **годится к архивации после инлайн-правок**
секций 1–2. Находки, требующей остановки или переработки предложения, нет:
все семь пунктов — правки текста дельты, локальные и однозначные, кроме одной
развилки (п. 7, выбор регистра нормы о подтверждении). Дефекты в коде,
вскрытые тестами adversary, — пред-существующие, изменением не введены и не
задеты (критерий A3 требует пустой кодовый дифф); они уходят урожаем.
## Приёмка A1–A3 (доклад приёмщику, не отметка «принято»)
- **A1** — требование различает `Cancel`/`Dismiss` по состояниям (не-терминальные
против «любое кроме `deleted`», конфликт против no-op) и называет `error_code`
обоих путей (`user_dismiss` против пустого). Оракул:
`openspec validate --strict cancel-dismiss-split-wording``Change … is valid`,
exit 0 (прогнано мной). **Выполнен**, с оговоркой: нормативная фраза «SHALL
называть путь закрытия» внутренне противоречит пустому коду `Cancel` — п. 4 ниже.
- **A2** — сценарий «Закрытие не-терминальной загрузки из веб-UI идёт отменой»
(дельта, строки 112–119): исход `cancelled`, `error_code` назван (пустой).
Оракул: тот же прогон + чтение сценария. **Выполнен.**
- **A3** — `git diff --stat <база> -- internal web cmd`**пусто** (прогнано
мной по worktree; полный дифф — 5 файлов только под `openspec/changes/`).
Гейт `Dismissable` и danger-zone шаблона не тронуты. **Выполнен.**
---
## Блокирует мердж (архивацию)
Все три — корзина **(а): дефект введён текстом этого изменения**. Все три —
правки одного-двух абзацев дельты
`openspec/changes/cancel-dismiss-split-wording/specs/state-reconciliation/spec.md`.
### 1. Дельта гарантирует «`Dismiss` SHALL NOT вызывать qBittorrent» — гарантия ложна: `Dismiss` из `catched` в окне после `add` даёт `torrents/delete` с `deleteFiles=true`
- Файл: дельта, строки 36–47; код `internal/worker/worker.go:520556` (уборка
по условию `after.State != store.StateCatched`, безразлична к команде),
`internal/httpapi/review.go:384391` (маршрут `dismiss` по состоянию не
гейтится — путь достижим сегодня прямым POST)
- Severity: major, Confidence: high. Корзина: **(а)** — сам механизм уборки
пред-существует и нормирован командо-нейтрально в `download-tracking`
(строки 216–234); ложна только атрибуция исключения одной команде `Cancel`.
- Оракул (прогнан мной): `go test ./tmp/adv/ -run TestDismissInAddWindow -v`
«ADV-3 воспроизведён: закрытие путём Dismiss (error_code="user_dismiss")
вызвало torrents/delete deleteFiles=[true]». Плюс
`internal/worker/catched_test.go:244268` — штатный тест уборки ставит
`cancelled` напрямую, не через `Cancel`.
- Дедуп: S1 (`specs`) + A3adv (`adversary`) — одна причина: исключение в коде
привязано к состоянию/окну, а не к команде.
- Действие: **инлайн**. Переформулировать исключение по условию, а не по
команде: «закрытие **любым путём**, уведшее задачу из `catched` в окне между
нашим `add` и записью перехода» — как это уже сформулировано в
`download-tracking`. Тем же правом убрать из абзаца атрибуцию «`Cancel` тем же
ограничен, с одним исключением» — исключение общее для обоих путей.
- **В ту же правку входит C2/A4** (дедуп: одна ссылка, одно предложение):
markdown-ссылка `../../../specs/download-tracking/spec.md` (строка 44) не
резолвится **ни из позиции дельты** (нормализуется в
`openspec/changes/specs/download-tracking/spec.md`, файла нет; верно было бы
4 уровня), **ни после архивации** (из `openspec/specs/state-reconciliation/`
нормализуется в `specs/download-tracking/spec.md`; верно — 1 уровень).
Единого относительного пути, верного в обеих позициях, не существует.
Проверено арифметикой пути (`os.path.normpath` + `exists`, прогнано мной).
Фикс: дать ссылку **бэктиком**`openspec/specs/download-tracking/spec.md`
как во всех соседних кросс-ссылках capability (прецедент:
`openspec/specs/state-reconciliation/spec.md:92`); markdown-ссылок в
`openspec/specs/` нет ни одной (grep пуст), и `docs.py check` этот каталог
не проверяет — битую ссылку не покрасил бы никто.
### 2. Дельта заявляет, что уборка в окне после `add` «данных пользователя не касается» — безусловное утверждение сильнее того, что гарантирует код: пред-существующие файлы под `paths.downloads` уничтожаются
- Файл: дельта, строки 45–46; код `internal/worker/worker.go:495556` (гард
«подтверждённое отсутствие» проверяет отсутствие **торрента**, не отсутствие
**данных**)
- Severity: major (для текста; кодовый дефект — кандидат `critical`, см. урожай
У-1), Confidence: high для текстовой части. Корзина: **(а)** для текста /
**(в)** для кода — дыра в гарде пред-существует, изменением не задета; этим
change введена только ложная гарантия.
- Оракул (прогнан мной): `go test ./tmp/adv/ -run TestCancelInAddWindow -v`
«ADV-1 воспроизведён: worker вызвал torrents/delete deleteFiles=[true];
данные …/downloads/Show/e1.mkv существовали под paths.downloads ДО add
(гард проверял отсутствие торрента, а не отсутствие данных)».
- Почему текст — major, а не перенос `critical` с кода: текст сам данных не
уничтожает; но, будучи влит в нормативный дом, он узаконивает дыру — следующий
аудит поверит «данных не касается» и закроет расследование. Severity кода на
severity текста автоматически не переносится; кодовый дефект едет урожаем со
своим весом.
- Действие: **инлайн**. Убрать безусловное «и данных пользователя не касается».
Right-size замена — отослать к дому нормы без собственной гарантии
(«нормирована в `download-tracking`»), не расширяя дельту признанием дыры:
дыра — предмет задачи У-1, а не этого требования.
### 3. Обещание «библиотечные ссылки без штатной команды снятия **до повторной привязки**» ложно: перепривязка их не снимает никогда — `Undo`/`Delete` работают только с последним батчем
- Файл: дельта, строки 62–66 и сценарий 93–101; код
`internal/worker/review.go:332` (каждый `Apply` — новый батч), `:557572`
(`Undo` — только `LatestBatchID`), `:625641` (`Delete` — только
`LatestBatchID`)
- Severity: major, Confidence: high. Корзина: **(а)** для формулировки /
**(в)** для кода (поведение батчей пред-существует, изменением не задето).
- Оракул (прогнан мной): `go test ./tmp/adv/ -run 'TestDismissRelink|TestStrandedLink' -v`
«ADV-2: в библиотеке осталась неуправляемая ссылка (батч 1), которую не
снимает ни Undo, ни Delete»; «ADV-2b: запись в deleted, раздача снесена с
файлами, но в библиотеке осталась неуправляемая ссылка с живыми данными —
место не освобождено».
- Последствие текста: единственный маршрут к брошенной ссылке — ровно та связка
`Dismiss` из `done` → relink, которую дельта нормирует; «до повторной
привязки» называет relink лекарством, которым он не является, а после него
«полное удаление» перестаёт освобождать место — danger-зона обещает обратное.
- Действие: **инлайн**. Заменить «до повторной привязки» честным: «…без штатной
команды снятия; последующая перепривязка их **не** снимает — `Undo` и
`Delete` работают только с последним батчем раскладки». Расширение
`Undo`/`Delete` на все батчи — урожай У-2.
## Стоит исправить сейчас
### 4. Нормативное «`error_code` SHALL называть путь закрытия» нарушено пустым кодом `Cancel` и опровергнуто тем же документом тремя абзацами ниже
- Файл: дельта, строка 49; код `internal/worker/worker.go:985`
(`SetDownloadState(ctx, id, StateCancelled, "", "")` — проверено чтением)
- Severity: major, Confidence: high. Корзина: **(а)** — фраза введена этим
изменением.
- Оракул: код (пустой код ничего не «называет») + сам документ: строки 56–58
(«пустота кода на пути `Cancel` … не закреплена нормативно») и 73–82
(«известное ограничение»). `SHALL`, который документ сам объявляет
неудовлетворённым, — приглашение следующему аудиту завести ту же задачу.
- Действие: **инлайн**. Перевести фразу в согласованный регистр: например,
«`error_code` перехода SHALL **различать** пути закрытия, а не унифицировать
их» (пустой у `Cancel` против `user_dismiss` у `Dismiss` — различает), либо
сузить нормативность до `Dismiss` (его `SHALL user_dismiss` уже стоит строкой
ниже). Остальной абзац и «известное ограничение» уже согласованы.
### 5. «Прежняя диагностика остаётся только в логе перехода» — фактически неверно: лог-строки перехода `Cancel`/`Dismiss` прежних `error_code`/`error_msg` не содержат
- Файл: дельта, строки 53–58 и сценарий 103–110; код
`internal/worker/worker.go:985991, 10161022` — проверено чтением: `Cancel`
логирует только `from`/`to`, `Dismiss``from`/`to`/`code` **нового**
перехода; контракт поля `code``docs/conventions/logging.md:2630`.
- Severity: major, Confidence: high. Корзина: **(а)** для формулировки /
**(б)** для наблюдаемости (лог и раньше не нёс прежней диагностики — урожай
У-3).
- Последствие: причина падения восстановима только по более ранней, никак не
связанной по смыслу лог-строке, чья доступность зависит от неописанной
ретенции логов (`docs/architecture.md` — только «логи в stdout контейнера»).
- Действие: **инлайн**. Поправить формулировку: прежние код и текст «в записи
не сохраняются; лог-строка перехода их не дублирует — они остаются лишь в
более ранних строках лога состояния». Добавление `prev_error_code`/`prev_error_msg`
в лог — кодовая правка, запрещённая критерием A3, → урожай У-3.
### 6. Описанная раскладка Telegram врёт в трёх клетках построенной по ней таблицы «состояние × поверхность»
- Файл: дельта, строки 30–34; код `internal/tgbot/bot.go:452461`,
`internal/tgbot/render.go:4450, 281290` — проверено чтением: уведомление
`EventDone` — текст без клавиатуры; `EventTargetMissing`/`EventOrphaned`
только ссылка в веб (`webOnly`, и та `nil` при пустом `web_base_url`);
`StateLinking` — клавиатуры нет вовсе (не «отсылает в веб»). «Закрыть»
появляется на **карточке** (`renderCard`/refresh), а не в уведомлении.
- Severity: minor, Confidence: high. Корзина: **(а)** — перечень введён этим
изменением (текст описателен: «сегодня раскладка такова», но неверен).
- Последствие: реальный пробел — уведомление о рассинхроне приходит без
стоп-крана — текстом замаскирован.
- Действие: **инлайн**. Либо уточнить: «Telegram даёт „Закрыть“ на **карточке**
загрузки в `failed`/`stuck`/`done`/`orphaned`/`target_missing`; уведомления
о `done` и рассинхроне кнопок закрытия не несут», либо снять перечень
Telegram целиком, оставив нормативную гарантию только за страницей загрузки
(раскладка по поверхностям всё равно объявлена ненормативной).
### 7. Новый `SHALL` об «отдельном подтверждающем шаге» въезжает уже нарушенным: без JS диалога нет, в Telegram нет ни danger-зоны, ни подтверждения
- Файл: дельта, строки 68–71; код
`web/templates/partials/download_main.html:9598` — проверено чтением,
комментарий дословно: «без JS гейт — только раскрытие details и явный submit
(диалога нет)»; `internal/tgbot/render.go:266290` — «Закрыть» в общем ряду
кнопок. `openspec/specs/web-ui/spec.md` нормирует «без JavaScript не
ломается».
- Severity: minor, Confidence: medium. Корзина: **(а)** — прежняя редакция
обязательства не содержала (уточнение adversary), норма введена этим
изменением; тот же изъян в пред-существующем требовании «Полное удаление
загрузки пользователем» — корзина (в), урожай У-6.
- Дедуп: S4 (`specs`) + A5 (`adversary`) — одна причина.
- Действие: **развилка** — выбор регистра нормы, а не правка формулировки:
1. **Сузить норму до фактического гейта**: «„Закрыть“ SHALL размещаться в
обособленной danger-зоне, скрытой по умолчанию (раскрытие — сознательный
шаг); с JS дополнительно SHALL показываться диалог подтверждения» + назвать
известным ограничением, что Telegram danger-зоны не даёт. Цена: слабее
звучит R7, зато спека не лжёт и код не требуется.
2. **Оставить `SHALL` как цель** и завести урожаем задачу: no-JS-подтверждение
(например, промежуточная страница-подтверждение) и danger-гейт в Telegram.
Цена: до выполнения задачи нормативный дом содержит невыполненный `SHALL`
(ровно то, за что бьёт п. 4).
Рекомендация триажа — вариант 1: он согласуется с R12 («спека фиксирует
наблюдаемое») и с решением п. 4.
## Гипотезы без доказательства
Пусто. Все выжившие находки имеют прогнанный оракул (тест, чтение названных
строк кода, арифметика пути, вывод команды). Понижений за отсутствие оракула
не было; `critical` кода из A1 не понижен, а отделён от текстовой находки и
уехал урожаем со своим весом (У-1) — его единственный непрогнанный шаг назван
там честно.
Отдельно: совпадение S1/A3adv и S4/A5 в разных проходах учтено как рост
приоритета, не как рост Confidence — под всеми проходами одна модель.
## Promote candidates
- **Проверка ссылок в `openspec/`** — шаг `canon` (`docs.py check`) обходит
только `docs/` и `CLAUDE.md`; битую markdown-ссылку в спеке не красит никто
(п. 1/C2 прожил бы до архивации молча). Кандидат: расширить проверку битых
ссылок на `openspec/specs/` и `openspec/changes/`, либо записать конвенцией
«кросс-ссылки в спеках — только бэктиком» (сегодня это фактическая, но
нигде не записанная практика: markdown-ссылок в `openspec/specs/` ноль).
Провенанс: `review-code` C2 + `review-adversary` A4.
## Урожай (пред-существующие дефекты — владельцу задач, не в этот change)
- **У-1 (кандидат `critical`, Confidence: medium).** Отмена/закрытие в окне
после `add` сносит **пред-существующие** файлы пользователя под
`paths.downloads`: гард «подтверждённое отсутствие» (`worker.go:495509`)
проверяет отсутствие торрента в qBittorrent, но не отсутствие данных на
save_path; `qbt.Add` подхватывает лежащие там файлы, уборка бьёт
`torrents/delete` с `deleteFiles=true`. Путь: пользователь снял раздачу,
файлы оставив (это предписывает сам `download-tracking:169174` как
восстановление) → подал тот же торрент → отменил в окне листинг+add (сотни
мс — секунды). Инвариант «источник неприкосновенен»: исключение (2) покрывает
«собственный торрент», но не пред-существующие данные; UI при этом обещает
«файлы и раздача останутся нетронутыми» (`download_main.html:100111`).
Оракул: `tmp/adv/adv_test.go`, `TestCancelInAddWindowDeletesPreExistingData`
прогнан, воспроизводит (каталог gitignored — при заведении задачи тест
перенести в её материалы). Единственный непрогнанный шаг — семантика боевого
qBittorrent `deleteFiles=true` над пред-существующими файлами: в бой ходить
запрещено, отсюда medium. Провенанс: `review-adversary` A1, чекпоинт 2
change `cancel-dismiss-split-wording`.
- **У-2 (major, Confidence: high).** `Undo`/`Delete` работают только с
последним батчем (`review.go:557572, 625641`), каждый `Apply` создаёт новый
(`review.go:332`): связка «закрыть `done` → relink» бросает ссылку батча 1
навсегда, а «полное удаление» после этого не освобождает место (инод жив за
счёт брошенной ссылки). Оракул: `TestDismissRelinkStrandsLibraryLinks`,
`TestStrandedLinkDefeatsDelete` — прогнаны, воспроизводят. Провенанс:
`review-adversary` A2.
- **У-3 (minor, наблюдаемость).** Лог-строки переходов `Cancel`/`Dismiss` не
несут прежних `error_code`/`error_msg`, хотя `d` их держит в момент записи
(`worker.go:985991, 10161022`): «почему задача упала» после закрытия не
восстановить одной строкой. Кандидат: `prev_error_code`/`prev_error_msg` в
лог перехода. Оракул: чтение кода + контракт `logging.md:2630`. Провенанс:
`review-ops` O1.
- **У-4 (minor).** «Per-download блокировка» из `CLAUDE.md` (инвариант
«переходы состояний») и `worker.go:11` — фактически один глобальный
`sync.Mutex` (`worker.go:239`), удерживаемый `Apply` на весь файловый
ввод-вывод: долгая раскладка блокирует команды по **другим** загрузкам и
поллинг. Минимум — поправить формулировку в двух местах; по существу —
per-id блокировка либо вынос I/O из-под замка. Оракул: чтение кода (одно
поле, не карта). Провенанс: `review-ops` O2.
- **У-5 (minor, эффект — гипотеза при сегодняшнем профиле «единицы/день»).**
Дефолтный список — `SCAN download` (негативный `state NOT IN` мимо
`idx_download_state`, измерено `EXPLAIN QUERY PLAN` на копии схемы), а
`cancelled` копятся без ретенции, и требование под ревью расширяет канал их
производства. Частичный индекс или позитивный список состояний; ретеншен —
задача в беклоге уже есть. Провенанс: `review-ops` O3.
- **У-6 (minor).** Пред-существующее требование «Полное удаление загрузки
пользователем» (`openspec/specs/state-reconciliation/spec.md:463465`) несёт
ту же формулировку о подтверждающем шаге с той же no-JS-дырой, что п. 7.
Судьба зависит от развилки п. 7 — править согласованно. Провенанс:
`review-specs` S4 (смягчающее наблюдение).
Уже назначено урожаем ранее (не дублирую задачей): R10 из рубрики чекпоинта 1 —
«допустимость команды переоценивается на момент исполнения, а не по снимку
рендера» (`tasks.md`); туда же примыкает граница `specs` про `/review/{id}`,
открытый для любого состояния.
## Границы покрытия
**Запускалось:** профиль `standard`, режим «по графу», чекпоинт 2 (после
apply). `review-gate` (кодовые шаги SKIP — кода в диффе нет; canon, gitleaks —
OK), `review-specs`, `review-code`, `review-adversary`, `review-ops`.
**Не запускалось:** `review-reimpl` — стадия 3, только в `deep`;
`review-architecture` — стадия 4 (`wide`/`deep`); оба вне профиля `standard`.
`review-architecture` отработал на чекпоинте 1 (профиль `design`) вместе с
`review-specs` и `review-rubric`; его находки закрыты правками предложения
(design.md, решения D1D3, D6–D8) и на этом чекпоинте не перепроверялись.
**Что запущенные проходы не могли проверить в принципе (из charter'ов):**
- `specs`: поведение живых qBittorrent/Telegram — клавиатуры Telegram не
покрыты тестами вовсе, сверка была чтением кода; плюс его заявленные границы:
владение целевым путём после закрытия `done` (ушло в У-2), `/review/{id}`
без гейта состояния (примыкает к R10), «активный список» как три разных
множества (`store/list.go`, `store/download.go`).
- `code`: только записанные конвенции; конвенции о форме OpenSpec-спек в
`docs/conventions/` нет — норму задавали `openspec/config.yaml` и `CLAUDE.md`.
- `adversary`: семантика **боевого** qBittorrent (`deleteFiles=true` над
пред-существующими файлами) — в бой ходить запрещено; это единственный
непрогнанный шаг У-1. Свойства без пути: снятие последней копии
последовательностью команд не строится; секретов в `error_msg`/логах не
найдено; негейченный маршрут `dismiss` в сегодняшнем периметре
(`docs/security.md`) — операторское действие, не дефект (совпадает с
«Типовым ложноположительным» про отсутствие авторизации — корректно не
выведен находкой).
- `ops`: `-race` на этом диффе не гонялся (кода нет — SKIP); гонки суждены
рассуждением. Ретенция логов stdout нигде не описана — доступность «более
ранней лог-строки» из У-3 неизвестна.
- триаж (я): нового не ищу по определению — работаю с чужими выводами; пропуск
любого прохода — мой пропуск тоже. Тесты `tmp/adv/` прогнаны и воспроизводят,
но их фикстуры — фейковый qbt и локальная ФС: они доказывают поведение
**нашего** кода, не связки с боем.
**Осталось целиком на человеке** (из `docs/review.md`, два списка раздельно):
*Не проверит ни один проход:*
- история инцидентов на umbar и что уже ломалось в проде;
- поведение SQLite под реальным объёмом и профилем нагрузки;
- завязка внешних потребителей (Jellyfin, закладки, чужие ссылки) на текущее
поведение;
- качество распознавания (корпус решено не собирать, REJECTED 2026-08-06);
- суждение «этой функциональности не должно существовать».
*Перестали проверять сознательно:*
- идиоматичность Go — с 2026-08-04, вместе с упразднением прохода `idiom` при
переезде на плагин; различение «идиоматично против распространено» не
спрашивает никто; пересмотр — задача `quality-review-agents`.
**Каких документов/фактов не хватило (поразрядно, с причиной):**
- `docs/conventions/` — конвенции о форме OpenSpec-спек нет: правило «ссылки
бэктиком, не markdown» пришлось выводить grep'ом по фактике (`review-code`);
- `docs/architecture.md` — ретенция логов не описана (только «stdout
контейнера»): судьба «прежней диагностики в логе» из У-3 неопределима
(`review-ops`);
- `docs/review.md`, журнал дефектов — пуст (заведён 2026-07-23): оракулов
«такое здесь уже воспроизводилось» не было ни для одной находки, все
подтверждения добывались тестами и чтением кода (триаж);
- `CLAUDE.md`, инварианты — присутствуют и использованы (ранжирование У-1 по
«необратимо» и границе исключения (2)); «Типовые ложноположительные» и оба
подраздела «Недоступно проверке» — присутствуют и использованы. Здесь
пробелов нет.