docs: audit project and reopen incomplete roadmap gates
CI / backend-and-migrations (push) Waiting to run
CI / astro-build (push) Waiting to run
CI / dependency-audit (push) Waiting to run
CI / compose-e2e (push) Waiting to run

This commit is contained in:
ik
2026-09-22 07:33:51 +07:00
parent 25b6f3945e
commit 104d5803fb
3 changed files with 299 additions and 25 deletions
+53 -23
View File
@@ -1,19 +1,49 @@
# План работ RF4 Spotter
Этот файл — единственный актуальный список задач. Актуальный технический срез и границы доказательств зафиксированы в [PROJECT_AUDIT_2026-09-20.md](PROJECT_AUDIT_2026-09-20.md). Завершённые аудиты сохранены как история в [PROJECT_AUDIT_2026-09-08.md](PROJECT_AUDIT_2026-09-08.md), [REGRESSION_AUDIT_2026-09-09.md](REGRESSION_AUDIT_2026-09-09.md) и [RECOVERY_PLAN_2026-09-10.md](RECOVERY_PLAN_2026-09-10.md); их старые чекбоксы не являются текущей очередью.
Этот файл — единственный актуальный список задач. Текущий аудит: [PROJECT_AUDIT_2026-09-22.md](PROJECT_AUDIT_2026-09-22.md), исходный commit 25b6f39. Предыдущие аудиты — история, а не подтверждение текущей готовности.
Последняя сверка: **20 сентября 2026**.
Последняя сверка: **22 сентября 2026**.
Подтверждено:
Текущее состояние (проверено в этом проходе):
- [x] пакет восстановления A01–A13 завершён; итог и доказательства собраны в [RECOVERY_FIXES_REPORT.md](RECOVERY_FIXES_REPORT.md);
- [x] полный Python suite: **196 passed, 1 skipped**; skip относится к интеграционной проверке PostgreSQL и требует отдельного Docker-прогона;
- [x] Astro check: 62 файла, **0 errors / 0 warnings / 0 hints**; production build проходит;
- [x] API contract suite: `apps/api/tests/test_api.py` содержит **20 passed**; live `/health` и `/ready` после миграции требуют отдельного Docker-прогона;
- [x] граф Alembic линеен и имеет единственную голову `0020`; CI применяет её на чистой PostgreSQL, полный production bootstrap запускается отдельным еженедельным drill;
- [x] локальный dev Compose gate 20.09: PostgreSQL, MinIO, API и web healthy; `/health`/`/ready` успешны, migration current — `0020 (head)`, public `/`, `/waterbodies/` и `/records/` отвечают `200` из контейнерной сети;
- [x] изолированный production bootstrap повторно прошёл 20.09: чистые PostgreSQL/MinIO volumes, MinIO init с отдельными app credentials, Alembic `0020`, Caddy adapt, scheduler validation и оба Playwright-сценария; drill не обращается к внешним источникам;
- [x] Astro + FastAPI + PostgreSQL остаются целевым стеком; Next.js и Vinext не используются.
- Python: **228 passed, 1 failed, 1 skipped**; reserve-only test не учитывает reserved_at.
- Web unit: **14 passed, 1 failed**; тест ожидает прежнее слово «монтаж», продукт использует «сборка».
- Astro check/build: **87 файлов, 0 errors / 0 warnings / 2 hints**, сборка успешна.
- OpenAPI: **45 paths**, artifact актуален.
- Локальные API/web/PostgreSQL/MinIO healthy; БД на **0022 (head)**. Образы не пересобирались в ходе аудита.
- В БД **21 водоём, 19 с внешним ID; 0 предметов и 0 сборок**. Пустой UI не подтверждает приёмку наполненного каталога.
- Media audit: **704 записи, 466 approved**, ошибок и orphaned files нет. Среди fish один fallback 48×48.
- Production, новый browser/axe/E2E-прогон, restore и CVE-проверка в этом аудите не выполнялись. Старые результаты доступны в исторических отчётах.
- Стек: Astro + FastAPI + PostgreSQL.
## Очередь после аудита — без cooldown
Сначала исправлять функции и сохранность данных, затем проводить адресную приёмку.
R-пункты уточняют критерии существующих B/G/U/A/W-пунктов, а не заменяют
их частичным выполнением. Коммит после каждого действительно закрытого пункта.
Доказательства и ограничения: [аудит](PROJECT_AUDIT_2026-09-22.md).
- [ ] **R01 · P1 · Долговременное хранение media-решений (A04/B23/A07).** Persistent store для manifest/assets, bootstrap из Git, release merge и backup/restore. Критерий: publish/rollback переживают recreate и восстановление вместе с provenance.
- [ ] **R02 · P1 · Публикация выбранных media и защита от гонок (A04/B23).** Явные asset IDs, preview, версия manifest, lock и проверка файлов. Критерий: скрытые кандидаты не публикуются, устаревшее решение — конфликт, параллельные CLI/API не теряют изменения.
- [ ] **R03 · P1 · Идемпотентная отправка улова.** Сверять payload/срок ключа во всех ветках, включая IntegrityError и завершённый upload; обработать конкурентное создание Spot/Bait. Критерий: одинаковый повтор возвращает тот же результат, другой payload — 409, ожидаемые гонки не дают 500.
- [ ] **R04 · P1 · Восстановление загрузки скриншота.** Атомарное одноразовое сохранение, повтор после потерянного ответа, очистка idempotency-cookie после восстановления. Критерий: нет лишних объектов, retry завершает форму и следующий новый улов отправляется.
- [ ] **R05 · P1 · Полноценный каталог снастей (G06/G09).** Отдельная пагинация сборок/предметов, независимые ошибки, валидация URL-фильтров, empty search отдельно от незаполненного каталога. Критерий: 49+ сборок достижимы при 0 предметов, 422 не маскируется под 503. Убрать вложенные ссылки: карточка оборачивает DataPassport с source-link; проверить итоговый DOM и keyboard наполненных карточек.
- [ ] **R06 · P1 · Надёжный перенос плана (U05).** Preview, объединение/замена с восстановлением, однократный импорт, storage errors, вычисляемая свежесть и точное описание передачи share-данных. Критерий: ссылка не стирает план без выбора, reload не возвращает удалённое, старые данные отмечены.
- [ ] **R07 · P1 · Схема координат по водоёмам (U04).** Разделить системы координат, показать точность и водоём, обработать совпадения/обрезку. Критерий: все точки достижимы на 320 px и с клавиатуры, включая одинаковые координаты и длинные названия.
- [ ] **R08 · P1 · Достоверная аналитика снастей (G07).** Зафиксировать публичный минимум 3 наблюдения/2 игрока, разделить время улова и импорта, conservative canonical grouping, ограничить ответ. Критерий: 1/1 не рекомендация, unresolved не получает ложную привязку, старый улов не выглядит свежим.
- [ ] **R09 · P1 · Полное удаление личных данных.** Retention/delete учитывают дочерние source_url/raw_payload и восстановление при сбоях S3/БД. Критерий: личные копии не остаются в компонентах, повторная очистка безопасна.
- [ ] **R10 · P1 · Единый cooldown и стадии запроса.** Координация CLI/media/scheduler; release только до доказанного обращения. Критерий: DNS до первого запроса освобождает резерв, ошибка после redirect — нет; конкурентные пути делят одно окно. Проверять offline.
- [ ] **R18 · P1 · Границы локального и production запуска.** Loopback для dev-портов с явным opt-in LAN; проверить доверенные proxy/client-IP и rate-limit на двух клиентах. Критерий: default dev не открыт в LAN, production различает клиентов. Реальные серверные gates — A07.
- [ ] **R11 · P2 · Честное качество и происхождение media (B25).** Unknown вместо ложного official, hostname allowlist, размеры отдельно от наличия альтернатив, compact подписи и cache/revocation-политика. Критерий: длинные подписи читаемы, неизвестный источник не становится официальным.
- [ ] **R14 · P2 · Подтверждённый каталог и demo seed.** Отделить справочник от неподтверждённых описаний/точек, показывать loaded count и внешнюю полноту/unknown раздельно. Критерий: production без demo не создаёт вымышленных описаний, media count не подменяет canonical count.
- [ ] **R15 · P2 · Восстановление пользовательских действий.** Сохранять draft при 422/429/timeout, объяснять конфликт повторов, undo плана и общий storage/clipboard helper. Критерий: повтор не требует заполнить весь улов заново.
- [ ] **R16 · P2 · Производительность реальных endpoints.** Измерить ORM/RSS/SQL и чтение manifest, затем bounded aggregation и digest/mtime cache по результатам. Критерий: воспроизводимый endpoint budget на крупных fixtures; Redis заранее не добавлять.
- [ ] **R17 · P2 · Навигация и SEO снастей.** Обнаруживаемый каталог снастей и sitemap подтверждённых карточек; политика индексации фильтров/пустых страниц. Критерий: канонические карточки достижимы из UI и sitemap без дубликатов.
- [ ] **R12 · P2 · Визуальная приёмка наполненных состояний (D07/A06/U06/G09).** Изолированные данные: длинные карточки, компоненты, конфликты, ошибки, authenticated admin; light/dark mobile/desktop, keyboard/contrast и screenshots с revision. Критерий: подтверждены действия и читаемость, а не только DOM/overflow.
- [ ] **R13 · P2 · Зелёные проверки и CI artifacts.** Обновить два устаревших контракта, адресные regressions исправленных дефектов, успешные visual artifacts, Node/Python baseline и отдельный dependency audit. Критерий: suites зелёные, skip объяснён, отчёт связан с commit. Известные падения учитывать при каждом feature-коммите.
W03 также можно продвинуть offline: сохранённый snapshot Лосиного уже есть.
Полный W02 и реальные crosswalk/media-наборы продолжают зависеть от источников.
## Ближайший пакет — без сервера
@@ -43,14 +73,14 @@
- [x] **B20 · Аудит качества рыбных изображений.** Offline `media_cli --quality-report` проверяет фактические dimensions опубликованных файлов, отдельно считает неизбежный upscale в карточках и известные альтернативы. После quality-upgrade: 253 опубликованных fish-файла, 252 имеют 1024×1024 WebP, один fallback (`Ёрш`) остаётся 48×48 и увеличивается до 180 px; новых известных альтернатив для него нет. Совпадение сущности больше нельзя считать достаточным основанием пропустить потенциально более качественный файл.
- [x] **B21 · Очередь quality-upgrade.** Текущие approved-файлы не менялись до готовности замены. Очередь из 226 прямых RF4DB-альтернатив обработана с сохранением `duplicate_of`: все 226 кандидатов прошли `upgrade_stored` и затем были атомарно продвинуты в `approved`, старые версии сохранены как `superseded`. Один provenance-only `duplicate`, не связанный с низкоразрешённым published-файлом, намеренно не переводился. Ошибка останавливает домен по существующим правилам; публикация старой версии при этом не прерывается.
- [x] **B22 · Сравнение вариантов разных источников.** Offline-команда `--compare-quality-upgrades` сопоставила сохранённые кандидаты с опубликованными fallback по `duplicate_of` и проверила dimensions, размер файла, MIME, прозрачность и aspect ratio. Все 226 кандидатов прошли технические пороги и offline contact-sheet review; источник RF4DB/RF4MAP/официальный RF4 не получал автоматического приоритета. После публикации в manifest не осталось `upgrade_stored`, поэтому текущий повторный запуск команды корректно сообщает `compared: 0`.
- [x] **B23 · Безопасное продвижение и provenance.** Связи `supersedes`/`replaced_by`, отдельное решение review, атомарное переключение публичного `entity_key` и явный CLI rollback сохраняют обе исходные ссылки и возможность отката. Публичная карточка показывает источник выбранного изображения, а manifest сохраняет проверенные варианты. Старый Git-файл не удаляется при включении нового.
- [ ] **B23 · Безопасное продвижение и provenance.** Связи `supersedes`/`replaced_by`, отдельное решение review, атомарное переключение публичного `entity_key` и явный CLI rollback сохраняют обе исходные ссылки и возможность отката. Публичная карточка показывает источник выбранного изображения, а manifest сохраняет проверенные варианты. Старый Git-файл не удаляется при включении нового. **Повторно открыт аудитом 22.09: Atomic replace не обеспечивает сериализацию решений; см. R01/R02.**
- [x] **B24 · Производные размеры.** После выбора оригиналов генерируются детерминированные WebP/AVIF thumbnails для каталога и отдельный крупный вариант для detail, хэши производных фиксируются в manifest и отдаются через каталог. Маленькие карточки больше не обязаны загружать 1024×1024 оригинал.
- [x] **B25 · Визуальная приёмка media.** Contact sheets для всех 226 замен собраны и просмотрены offline: явных ложных соответствий, обрезки и искажённых пропорций не обнаружено; одно отличие подписи (`Ерш-носарь`/`Ёрш-носарь`) орфографическое. In-app Chromium проверил 12 комбинаций `/media` (320/390/768/1280 px × light/dark/system): горизонтального overflow нет, у 253 изображений есть `alt`, broken loaded images — `0`, переключение темы корректно меняет `data-theme`; automated media acceptance теперь отдельно проверяет loaded assets и разделы `waterbody/tackle/reference`, keyboard/focus и print-regression проходят, standalone axe — `12/12` маршрутов. Launcher `scripts/capture-visual-matrix.mjs` синхронизирован с regression matrix и захватывает `252` PNG; полный capture текущего прохода завершён без document overflow, representative manual review зафиксирован в `docs/ux-reference-review-2026-09-21.md`. Единственный опубликованный fallback ниже 256 px теперь явно помечается в UI как «Низкое разрешение · 48×48» и сохраняется с объяснением отсутствия проверенной крупной версии. Gate: нет битых файлов, искажённых пропорций, ложных соответствий или обрезанного объекта; низкое разрешение явно обозначено.
- [ ] **B25 · Визуальная приёмка media.** Contact sheets для всех 226 замен собраны и просмотрены offline: явных ложных соответствий, обрезки и искажённых пропорций не обнаружено; одно отличие подписи (`Ерш-носарь`/`Ёрш-носарь`) орфографическое. In-app Chromium проверил 12 комбинаций `/media` (320/390/768/1280 px × light/dark/system): горизонтального overflow нет, у 253 изображений есть `alt`, broken loaded images — `0`, переключение темы корректно меняет `data-theme`; automated media acceptance теперь отдельно проверяет loaded assets и разделы `waterbody/tackle/reference`, keyboard/focus и print-regression проходят, standalone axe — `12/12` маршрутов. Launcher `scripts/capture-visual-matrix.mjs` синхронизирован с regression matrix и захватывает `252` PNG; полный capture текущего прохода завершён без document overflow, representative manual review зафиксирован в `docs/ux-reference-review-2026-09-21.md`. Единственный опубликованный fallback ниже 256 px теперь явно помечается в UI как «Низкое разрешение · 48×48» и сохраняется с объяснением отсутствия проверенной крупной версии. Gate: нет битых файлов, искажённых пропорций, ложных соответствий или обрезанного объекта; низкое разрешение явно обозначено. **Повторно открыт аудитом 22.09: Новая подпись требует compact/light/dark review; отсутствие альтернатив не подтверждается размерами. См. R11/R12. «Один fallback» выше относится только к fish.**
### Каталог водоёмов, карты и координаты
- [x] **W01 · Canonical-каталог RF4DB.** 16.09 через браузерный контекст получен и проверен индекс `/ru/maps`: 19/19 карточек, source slug/ID, русское название, уровень открытия, число видов рыб и URL изображения сохранены в [`data/waterbodies/rf4db-catalog-2026-09-16.json`](../data/waterbodies/rf4db-catalog-2026-09-16.json). Добавлена команда `python -m app.cli import-waterbody-catalog --input ...` для применения снимка в БД. Изображения остаются кандидатами без автоматической роли `map` или `cover`; detail-данные выполняются отдельно по W02.
- [ ] **W02 · Карточки водоёмов RF4DB.** Строгий fixture-based parser `rf4db-waterbody` расширен под реальный DOM `/fishes/` и `/positions/`, regression-тесты, безопасный `update_waterbody_detail` и атомарный batch `update_waterbody_details`/`import-waterbody-details` готовы. Snapshots `level_000_home`, `level_001_lake` и `level_002_kirzah_river` сохранены. Добавлены guarded launcher `scripts/fetch-waterbodies-chromium.mjs` для обычной Chromium-сессии и offline-режим `scripts/fetch-waterbodies.py --html`; оба сохраняют snapshots атомарно, не импортируют их автоматически и используют общий cooldown. Резерв имеет идентификатор попытки: локальный сбой до открытия страницы (включая падение Chromium, DNS или отказ соединения) освобождает только собственный резерв, а ответ источника сохраняет cooldown. Chromium-launcher сначала делает отдельную reserve-only операцию. Повторный разрешённый запрос `level_002_kirzah_river` 20.09 вернул совпадающие с canonical-каталогом имя, `29/29` видов и два URL изображений; snapshot импортирован в Compose PostgreSQL, `/health` и `/ready` успешны, публичная `/waterbodies/р-вьюнок` отвечает `200`. Описание и позиции источник не вернул, поэтому они оставлены `null`/пустыми без домыслов; изображения остаются только provenance-кандидатами и не публиковались. Три разрешённых запроса `level_019_american_pond` 20.09 завершились `403 Forbidden`, а текущий Chromium показывает защитную страницу Cloudflare: это блокировка HTTP-клиента источником, не ошибка атомарного сохранения launcher-а; snapshot и импорт не создавались, прежние подтверждённые данные и media не менялись. Одно промежуточное разрешённое окно 20.09 было зарезервировано CLI, но завершилось локальной DNS-ошибкой до ответа источника; зависший резерв от последнего локального падения снят без запроса к источнику. Следующий сетевой запуск допускается только после общего cooldown; полнота ответа остаётся неподтверждённой. CAPTCHA/challenge не автоматизируются. Осталось последовательно разобрать 16 detail-страниц.
- [ ] **W02 · Карточки водоёмов RF4DB.** Строгий fixture-based parser `rf4db-waterbody` расширен под реальный DOM `/fishes/` и `/positions/`, regression-тесты, безопасный `update_waterbody_detail` и атомарный batch `update_waterbody_details`/`import-waterbody-details` готовы. Snapshots `level_000_home`, `level_001_lake` и `level_002_kirzah_river` сохранены. Добавлены guarded launcher `scripts/fetch-waterbodies-chromium.mjs` для обычной Chromium-сессии и offline-режим `scripts/fetch-waterbodies.py --html`; оба сохраняют snapshots атомарно, не импортируют их автоматически и используют общий cooldown. Резерв имеет идентификатор попытки: локальный сбой до открытия страницы (включая падение Chromium, DNS или отказ соединения) освобождает только собственный резерв, а ответ источника сохраняет cooldown. Chromium-launcher сначала делает отдельную reserve-only операцию. Повторный разрешённый запрос `level_002_kirzah_river` 20.09 вернул совпадающие с canonical-каталогом имя, `29/29` видов и два URL изображений; snapshot импортирован в Compose PostgreSQL, `/health` и `/ready` успешны, публичная `/waterbodies/р-вьюнок` отвечает `200`. Описание и позиции источник не вернул, поэтому они оставлены `null`/пустыми без домыслов; изображения остаются только provenance-кандидатами и не публиковались. Три разрешённых запроса `level_019_american_pond` 20.09 завершились `403 Forbidden`, а текущий Chromium показывает защитную страницу Cloudflare: это блокировка HTTP-клиента источником, не ошибка атомарного сохранения launcher-а; snapshot и импорт не создавались, прежние подтверждённые данные и media не менялись. Одно промежуточное разрешённое окно 20.09 было зарезервировано CLI, но завершилось локальной DNS-ошибкой до ответа источника; зависший резерв от последнего локального падения снят без запроса к источнику. Следующий сетевой запуск допускается только после общего cooldown; полнота ответа остаётся неподтверждённой. CAPTCHA/challenge не автоматизируются. Осталось последовательно разобрать 16 detail-страниц. Сверка 22.09: после этих ошибок сохранён `.cache/waterbodies/rf4db-level_019_american_pond-20260921.json` (19 видов, 2 URL изображений, без description/point_urls). Утверждение выше о текущей Cloudflare-странице историческое; snapshot требует versioned provenance и проверки импорта. Остаток detail-очереди пересчитать по сохранённым и импортированным IDs.
- [ ] **W03 · Модель и provenance.** В модель `Waterbody`, API-каталог и миграции `0017`/`0019`/`0020` добавлены nullable-поля provenance, счётчик видов и detail-факты; идемпотентный upsert применён к canonical snapshot: `19/19`, без missing/duplicate/provenance issues. Осталось применить подтверждённые detail snapshots и отдельно разделить игровые и редакционные тексты при подключении detail-данных.
- [ ] **W04 · Классификация изображений.** Добавлены допустимые роли `waterbody_cover`, `waterbody_map`, `waterbody_depth_map`, `waterbody_screenshot` и проверка их назначения только через review для canonical waterbody. Кандидаты по-прежнему не получают роль автоматически. Осталось наполнить очередь detail-изображениями и провести contact-sheet review с проверкой dimensions, MIME, SHA-256, соответствия названию и источника.
- [ ] **W05 · Crosswalk источников.** Добавлен offline-конструктор консервативных предложений: нормализуются только точные имена/алиасы, неоднозначные и unmatched строки не получают canonical key; отсутствие ID выдаётся лишь диагностикой и не считается удалением. RF4MAP/RF4-STAT directory parsers теперь преобразуются в crosswalk identities без переноса метрик или автоматической привязки. Осталось подать полный набор реальных RF4DB/RF4MAP/RF4 Posts identities и вручную подтвердить результаты, включая три ранее отмеченных отсутствующих RF4MAP объекта.
@@ -71,8 +101,8 @@
- [ ] **G03 · Модель и provenance.** Добавлены отдельные `tackle_item`, `rig` и `rig_component` с миграцией `0021`; legacy `bait` и `catch_report.bait_id` не изменялись. `tackle_item` хранит `category`, `subcategory`, `brand`, `family`, `unlock_level`, source identity, timestamp и `raw_payload`; `rig_component` хранит роль, порядок, исходное значение и optional canonical item. Связи с catch-потоком добавлены в G05; безопасный backfill остаётся только после реального crosswalk.
- [ ] **G04 · Crosswalk и нормализация.** Добавлен offline `gear_crosswalk`: нормализация регистра/пробелов/`е/ё`, точное имя или alias плюс совместимая категория, консервативная проверка brand/family. Неоднозначные, несовместимые, брендовые и unmatched-строки получают review-статус без canonical key; исходное значение сохраняется. Остаётся подать реальные RF4DB/RF4MAP/RF4 Posts identities и вручную подтвердить результаты.
- [ ] **G05 · Связи с уловами и источниками.** Добавлены `catch_tackle_component` и offline `gear_components`: можно сохранять несколько unresolved/canonical компонентов с ролью, порядком, исходным значением, source identity и `raw_payload`; legacy `bait_id` не меняется. Parser сохраняет порядок оборудования из detail и разделяет bait/rig в catch-полях. Запись компонентов подключена к community import, официальному импорту и пользовательской форме; повторная обработка идемпотентна. Canonical-привязка и безопасный backfill остаются только после подтверждённого crosswalk.
- [x] **G06 · API и публичный каталог.** Добавлены пагинированные `/api/v1/tackle/items` и `/api/v1/tackle/rigs` с фильтрами предметов и сборок по названию, а также предметов по категории, бренду, семейству и уровню; detail endpoints для предмета и сборки, а также ordered `tackle_components` в ответе уловов точки. Ответы показывают только канонические характеристики, provenance, timestamp проверки и `missing_fields`; рейтинг эффективности не добавляется. Публичный каталог теперь показывает обнаруживаемые карточки снастей и сборок, имеет detail-routes `/tackle/items/:id` и `/tackle/rigs/:id` с 404/unavailable-различением, паспортом данных и явным списком недостающих полей; CatchList показывает ordered-компоненты и ведёт в canonical detail снасти или сборки только при наличии соответствующего ID, а unresolved raw value оставляет текстом. UI каталога теперь использует фильтр уровня открытия, сохраняет его в URL и пагинации и показывает подтверждённый уровень на карточке.
- [x] **G07 · Аналитика сочетаний и рекомендации.** Добавлен `/api/v1/analytics/tackle`: approved-наблюдения группируются по роли и исходному компоненту с фильтрами водоёма, рыбы, метода, роли и окна; дубликаты одного улова не увеличивают счётчик, а минимум наблюдений и независимых игроков отделяет факт использования от рекомендации. Публичный `/tackle/analytics` показывает и сохраняет фильтры, имеет clean reset-link, выборку, независимых игроков и раздельные `recommendation`/`insufficient_data` states без рейтинга эффективности. Добавлен объяснимый `freshness_score` с half-life `12.5` часа и контрактом `0..100`: он не меняет raw-счётчики и пороги, но стабильно ставит более свежую равную выборку выше старой и виден на карточке. При однозначной canonical-привязке аналитика ведёт в карточку снасти или сборки, а при конфликте идентификаторов сохраняет raw-значение без ложной ссылки. Chromium подтвердил `/tackle/analytics`: основной landmark, заголовок, четыре фильтра, честный empty-state и отсутствие горизонтального overflow; визуальная матрица и accessibility evidence зафиксированы отдельно.
- [ ] **G06 · API и публичный каталог.** Добавлены пагинированные `/api/v1/tackle/items` и `/api/v1/tackle/rigs` с фильтрами предметов и сборок по названию, а также предметов по категории, бренду, семейству и уровню; detail endpoints для предмета и сборки, а также ordered `tackle_components` в ответе уловов точки. Ответы показывают только канонические характеристики, provenance, timestamp проверки и `missing_fields`; рейтинг эффективности не добавляется. Публичный каталог теперь показывает обнаруживаемые карточки снастей и сборок, имеет detail-routes `/tackle/items/:id` и `/tackle/rigs/:id` с 404/unavailable-различением, паспортом данных и явным списком недостающих полей; CatchList показывает ordered-компоненты и ведёт в canonical detail снасти или сборки только при наличии соответствующего ID, а unresolved raw value оставляет текстом. UI каталога теперь использует фильтр уровня открытия, сохраняет его в URL и пагинации и показывает подтверждённый уровень на карточке. **Повторно открыт аудитом 22.09: Общая пагинация скрывает часть сборок, неполная изоляция ошибок; см. R05.**
- [ ] **G07 · Аналитика сочетаний и рекомендации.** Добавлен `/api/v1/analytics/tackle`: approved-наблюдения группируются по роли и исходному компоненту с фильтрами водоёма, рыбы, метода, роли и окна; дубликаты одного улова не увеличивают счётчик, а минимум наблюдений и независимых игроков отделяет факт использования от рекомендации. Публичный `/tackle/analytics` показывает и сохраняет фильтры, имеет clean reset-link, выборку, независимых игроков и раздельные `recommendation`/`insufficient_data` states без рейтинга эффективности. Добавлен объяснимый `freshness_score` с half-life `12.5` часа и контрактом `0..100`: он не меняет raw-счётчики и пороги, но стабильно ставит более свежую равную выборку выше старой и виден на карточке. При однозначной canonical-привязке аналитика ведёт в карточку снасти или сборки, а при конфликте идентификаторов сохраняет raw-значение без ложной ссылки. Chromium подтвердил `/tackle/analytics`: основной landmark, заголовок, четыре фильтра, честный empty-state и отсутствие горизонтального overflow; визуальная матрица и accessibility evidence зафиксированы отдельно. **Повторно открыт аудитом 22.09: Empty-state не подтверждает наполненную аналитику; пороги, время и привязки требуют R08/R12.**
- [ ] **G08 · Медиа и качество.** Добавлены отдельные reviewed-роли `tackle_card`, `tackle_detail`, `rig_diagram`, `tackle_screenshot` для `tackle`, а также CLI-параметр `--media-role`; offline audit отклоняет неизвестную роль и несовпадение роли с entity type. Существующие dimensions, MIME, SHA-256, прозрачность, aspect ratio, provenance и атомарное продвижение сохраняются. Публичный и admin UI используют единый словарь человекочитаемых role labels с безопасным fallback для будущих ролей; публичная и административная медиатека фильтруют разрешённые роли через URL/API, ищут по подтверждённой подписи и сохраняют type/role-контекст, detail-страницы снастей и сборок показывают только совпавшие reviewed media с прямой атрибуцией, admin-карточки дополнительно получают `data-media-role` для проверяемой инспекции, unit-регрессия словаря проходит. Административный поиск по подписи выполняется до server-side pagination, а несовместимые role/entity фильтры возвращают `422`. Остаётся провести реальный contact-sheet review для будущих tackle-кандидатов без автоматической публикации.
- [ ] **G09 · Приёмка и эксплуатация.** Добавлены fixture/regression tests для crosswalk, media roles, идемпотентных компонентов, пустых результатов и многокомпонентных наблюдений; каталог `/tackle` включён в visual-matrix, narrow smoke и accessibility routes. Offline catalog/media audits и сохранение старых данных при сбое импорта проходят. TEMP-only query-plan gate для фильтра каталога и группировки сочетаний использует индексы и укладывается в 250 мс. Chromium подтвердил empty-state публичного каталога и отсутствие overflow на 320 px; обычный E2E пропускает bootstrap без явных переменных, отдельный bootstrap с токеном проходит. Остаются HTTP/browser acceptance для неоднозначных и многокомпонентных комплектов, ручной visual review и проверяемый счётчик по категориям либо явный `unknown`. Сетевые тесты не выполнять; импорт оставить opt-in, последовательным и под общим cooldown/backoff.
@@ -86,7 +116,7 @@
- [x] **D04 · Сохранение и SSR-согласование.** Выбор хранится год в allowlist-cookie `rf4-theme` с `SameSite=Lax` и `Secure` на HTTPS; Astro SSR выставляет `data-theme` до отрисовки, а системный режим оставляет выбор браузеру. Собранный Astro client script переключает тему без inline-кода, localStorage и ослабления CSP.
- [x] **D05 · Темизация компонентов и графики.** На семантические light/dark-токены переведены базовые и admin-поверхности, header, каталог, breadcrumb-леска, ссылки сущностей, таблицы, формы, moderation-карточки, provenance/quality/status badges, паспорта данных, pagination, empty/loading-состояния, радар, timeline, силуэты и глифы; добавлен forced-colors layer. In-app Chromium подтвердил переключение light/dark/system на медиатеке во всех четырёх ширинах и сохранение явной темы после reload с синхронными `theme-color`; automated media acceptance отдельно подтверждает light/dark на 320 px, standalone axe проходит `12/12` маршрутов плюс 6 доменных маршрутов, forced-colors и print regression подтверждены. Focused `theme-acceptance` regression фиксирует SSR/client-согласование cookie и мета-тегов, а также отключение декоративных animation loops при `prefers-reduced-motion`. Launcher рассчитан на полную screenshot-матрицу `252` PNG без document overflow; representative review тени/градиентов выполнен, а reference-набор остаётся внешним артефактом. Источники и статусы сохраняют текстовые подписи и не различаются только цветом.
- [x] **D06 · Метаданные браузера и CSP.** Добавлены парные `theme-color` для системной light/dark схемы; явный cookie-выбор согласуется с SSR и мгновенно переключает активный meta-тег. PWA manifest использует устойчивый тёмный brand chrome и splash background. Production build сохраняет `inlinedScripts: []`; новые inline script/style/attributes не появились, ослабление CSP не потребовалось.
- [x] **D07 · Визуальная и accessibility-приёмка.** In-app Chromium подтвердил отсутствие горизонтального scroll на публичных `/`, `/waterbodies`, `/records`, `/report`, `/status`, `/media`, `/rules`, waterbody detail `/waterbodies/р-вьюнок` и fish detail `/fish/pike`, а также admin `/admin`, `/admin/moderation`, `/admin/external-sources`, `/admin/media` в 320/390/768/1280 px; исправлены tablet header и media-grid overflow. Автоматизированный visual-matrix regression и `scripts/capture-visual-matrix.mjs` теперь покрывают 21 маршрут × 4 ширины × 3 режима (`252` комбинации), включая valid/error spot, waterbody/fish detail, plan и tackle analytics: document overflow отсутствует, везде есть `main`/`h1`, SSR-тема совпадает; launcher настроен на полный внешний capture. Для media и двух detail-страниц light/dark/system корректно переключаются, broken images — `0`; axe `12/12`, отдельная domain accessibility-проверка охватывает 6 public/domain routes, keyboard/focus, forced-colors и print-regression проходят. Representative manual review и ограничения reference-хранения зафиксированы в `docs/ux-reference-review-2026-09-21.md`. Для обеих тем подтверждены WCAG AA, отсутствие горизонтального scroll и CLS, reduced motion и переключение без потери введённых данных; эталонные screenshots заменены воспроизводимым capture и кратким отчётом.
- [ ] **D07 · Визуальная и accessibility-приёмка.** In-app Chromium подтвердил отсутствие горизонтального scroll на публичных `/`, `/waterbodies`, `/records`, `/report`, `/status`, `/media`, `/rules`, waterbody detail `/waterbodies/р-вьюнок` и fish detail `/fish/pike`, а также admin `/admin`, `/admin/moderation`, `/admin/external-sources`, `/admin/media` в 320/390/768/1280 px; исправлены tablet header и media-grid overflow. Автоматизированный visual-matrix regression и `scripts/capture-visual-matrix.mjs` теперь покрывают 21 маршрут × 4 ширины × 3 режима (`252` комбинации), включая valid/error spot, waterbody/fish detail, plan и tackle analytics: document overflow отсутствует, везде есть `main`/`h1`, SSR-тема совпадает; launcher настроен на полный внешний capture. Для media и двух detail-страниц light/dark/system корректно переключаются, broken images — `0`; axe `12/12`, отдельная domain accessibility-проверка охватывает 6 public/domain routes, keyboard/focus, forced-colors и print-regression проходят. Representative manual review и ограничения reference-хранения зафиксированы в `docs/ux-reference-review-2026-09-21.md`. Полная визуальная и accessibility-приёмка не подтверждена. **Повторно открыт аудитом 22.09: WCAG AA и отсутствие CLS не подтверждены representative review. Требуются R12 и эталоны; production-метрики — U07.**
### UX/UI и конкурентное преимущество
@@ -95,9 +125,9 @@
- [ ] **U01 · UX-контракт и scorecard.** Контракт маршрутов, смысловой порядок ответа, словарь статусов и целевые метрики зафиксированы в [ux-contract.md](ux-contract.md); для home, spot, waterbody, plan и tackle описаны первый ответ и обязательное объяснение. Сквозной Playwright task journey проверяет home → spot → «Что взять» → plan и возврат query-контекста, включая source/evidence-поля первой карточки; ручной сценарий и критерии зафиксированы в [ux-task-review-2026-09-21.md](ux-task-review-2026-09-21.md). Осталось провести review с участниками и собрать completion rate на пилоте.
- [x] **U02 · Главный сценарий «рыба → водоём → точка → снасть».** Главная сохраняет рыбу, водоём, период и сортировку в shareable URL, явно показывает контекст запроса и даёт текстовый CTA «Открыть точку» на каждой карточке, включая mobile; первый экран ограничен пятью вариантами, а остальные доступны через сохраняющую query-контекст серверную пагинацию; empty-state предлагает вернуться к полному набору данных. Каталоги рыбы и водоёмов имеют поиск по названию, сохраняют `q` в URL и пересчитывают пагинацию по отфильтрованному набору. Добавлен URL-сохраняющий режим `view=map` с отдельным mobile-friendly действием: он показывает только подтверждённые `x:y` на честной координатной схеме и ведёт в detail точки, без имитации геометрии водоёма. Detail-страницы рыбы, водоёма и пары теперь позволяют выбрать окно 24/72 часа/7 дней, передают его по связанным ссылкам и используют серверную пагинацию результатов с общим счётчиком; stale-offset автоматически возвращается на последнюю доступную страницу. Acceptance: первый полезный вариант виден без регистрации, back/refresh сохраняют контекст, mobile не теряет фильтры.
- [x] **U03 · Evidence/trust card.** Общий evidence-контракт используется на activity-карточках, detail точки, водоёма и карточках снастей; detail-страницы рыбы, водоёма и пары дополнительно показывают единый сводный паспорт активности с периодом, общим числом точек, наблюдениями текущей выборки, средним доверием, свежестью, источниками и конфликтами. Freshness имеет текстовый `Свежо`/`Устарело`, completeness, confidence/статус и доступные доменные поля; при выборке меньше 3 явно показано отдельное состояние `Недостаточно данных`, ограниченный источник не смешивается с неполными полями, а явно переданные provenance-конфликты видны текстом. Источники координат доступны в раскрываемом блоке паспорта, тогда как точность остаётся видимой в основном ответе. Targeted E2E и API-тест проверяют публичные пути, период, 48-часовой порог и конфликт источников; единый `passportDisplayStatus` дополнительно закрепляет приоритет `conflict``stale` → исходный статус unit-регрессией. Все публичные provenance links проходят credential-free HTTP(S) allowlist.
- [x] **U04 · List/map и progressive disclosure.** List остаётся честным базовым режимом: фильтры, сортировка, URL-состояние и evidence-карточки уже работают без имитации координатной карты. Источники координат в activity/spot evidence-паспорте и многокомпонентный комплект снастей в списке уловов раскрываются по запросу, не скрывая основную точность, статус и источник; canonical-компоненты сохраняют переходы в каталог. Режим `view=map` использует единый URL-контракт, на mobile открывается отдельным действием и прямо маркирован как координатная схема, пока геометрия водоёма не подтверждена. Вторичные raw/provenance-поля не исчезают и раскрываются по запросу.
- [x] **U05 · Mobile-first и сохранённый план рыбалки.** `/plan` поддерживает список до 5 уникальных локальных вариантов, удаление, очистку, переход к точке, print/PDF и восстановление из shareable URL; кнопка «Поделиться планом» использует native share или clipboard fallback, отдельное копирование ссылки и явно сообщает об успехе, отмене, пустом плане или повреждённой share-ссылке. Detail-кнопка сохраняет данные с `aria-pressed` и восстанавливается после reload. Print/mobile-контракт проверяет 320/390 px, лимит импорта и отсутствие горизонтального overflow; импорт дедуплицирует точки, нормализует длины локальных текстовых полей, принимает confidence только в диапазоне `0..100` и не теряет реальное значение `0`, а карточка плана сравнивает подтверждённые метод/проводку и явно показывает риск по выборке/уверенности.
- [x] **U06 · Контентная и визуальная иерархия.** Для detail точки действие «Что взять» выделено отдельным заголовком, provenance и качество собраны в общем паспорте данных, а подтверждённые метод/проводка теперь вынесены в отдельный блок рядом с последними уловами; статусы дополнительно передаются текстом, малая выборка не маркируется как готовая рекомендация, а дублирующие catches/players/confidence убраны из вторичной колонки activity-карточки. Копирование координат имеет Clipboard API fallback. Mobile route-contract проверяет первый ответ и действие на home, spot, waterbody, plan и tackle, а task regression требует source/evidence-поля; ручной review точки в Chromium подтвердил точность координат, действие «Сохранить в план», «Что взять», состояние «Недостаточно данных» и отсутствие overflow.
- [ ] **U04 · List/map и progressive disclosure.** List остаётся честным базовым режимом: фильтры, сортировка, URL-состояние и evidence-карточки уже работают без имитации координатной карты. Источники координат в activity/spot evidence-паспорте и многокомпонентный комплект снастей в списке уловов раскрываются по запросу, не скрывая основную точность, статус и источник; canonical-компоненты сохраняют переходы в каталог. Режим `view=map` использует единый URL-контракт, на mobile открывается отдельным действием и прямо маркирован как координатная схема, пока геометрия водоёма не подтверждена. Вторичные raw/provenance-поля не исчезают и раскрываются по запросу. **Повторно открыт аудитом 22.09: Смешение водоёмов и наложение маркеров требуют R07.**
- [ ] **U05 · Mobile-first и сохранённый план рыбалки.** `/plan` поддерживает список до 5 уникальных локальных вариантов, удаление, очистку, переход к точке, print/PDF и восстановление из shareable URL; кнопка «Поделиться планом» использует native share или clipboard fallback, отдельное копирование ссылки и явно сообщает об успехе, отмене, пустом плане или повреждённой share-ссылке. Detail-кнопка сохраняет данные с `aria-pressed` и восстанавливается после reload. Print/mobile-контракт проверяет 320/390 px, лимит импорта и отсутствие горизонтального overflow; импорт дедуплицирует точки, нормализует длины локальных текстовых полей, принимает confidence только в диапазоне `0..100` и не теряет реальное значение `0`, а карточка плана сравнивает подтверждённые метод/проводку и явно показывает риск по выборке/уверенности. **Повторно открыт аудитом 22.09: Share URL перезаписывает план и повторяет импорт при reload; см. R06.**
- [ ] **U06 · Контентная и визуальная иерархия.** Для detail точки действие «Что взять» выделено отдельным заголовком, provenance и качество собраны в общем паспорте данных, а подтверждённые метод/проводка теперь вынесены в отдельный блок рядом с последними уловами; статусы дополнительно передаются текстом, малая выборка не маркируется как готовая рекомендация, а дублирующие catches/players/confidence убраны из вторичной колонки activity-карточки. Копирование координат имеет Clipboard API fallback. Mobile route-contract проверяет первый ответ и действие на home, spot, waterbody, plan и tackle, а task regression требует source/evidence-поля; ручной review точки в Chromium подтвердил точность координат, действие «Сохранить в план», «Что взять», состояние «Недостаточно данных» и отсутствие overflow. **Повторно открыт аудитом 22.09: DOM одной точки не завершает визуальную приёмку наполненных/длинных состояний; см. R12.**
- [ ] **U07 · UX-приёмка и измерения.** Playwright-контракты уже покрывают query journey, сквозной task journey, evidence states, saved plan, share/print, accessibility и visual matrix; unit-контракт добавил детерминированные stale- и blocked-подписи, а insufficient-data теперь отделён от incomplete в публичном паспорте. `/status` явно сообщает, что при ограничении источника опубликованные данные сохранены. Visual-matrix regression теперь проверяет `252` комбинации, включая valid/error spot, plan и tackle analytics; representative review зафиксирован в `docs/ux-reference-review-2026-09-21.md`. Локальный Lighthouse baseline обновлён: performance 98 и accessibility 100. Остаётся получить production Lighthouse/CLS/INP/time-to-first-useful-answer после пилота и принять решение о долговременном хранении screenshots.
- [ ] **Q01 · Документы источников — реестр готов, нужны первичные подтверждения.** Создан единый production-gate с атрибуцией, общим лимитом 30 минут, хранением и процедурой отзыва для RF4DB, RF4-STAT, RF4MAP, RF4 Posts и официального RF4. До открытой публикации приложить устойчивые ссылки/копии первичных разрешений, контакты, даты и отдельно подтвердить право на изображения; пустое поле блокирует соответствующий источник.
@@ -140,9 +170,9 @@
- [x] **A01 · Защита маршрутов и границы сессии.** Покрыть точный `/admin` и `/admin/*` единым Caddy Basic Auth, выставлять `noindex` и `no-store` для всех административных ответов, проверить отсутствие обхода через API и корректные `401/429`. Критерий: автоматический proxy-smoke для `/admin`, страниц и `/api/v1/admin/*` с отсутствующим, неверным и валидным доступом.
- [x] **A02 · Единая auth/error UX.** Привести dashboard, moderation и external sources к одинаковому поведению при `401`, `409`, `429`, `5xx`, loading/empty-состояниях: понятное сообщение, блокировка повторной отправки, возврат к входу только при истёкшей авторизации. Критерий: regression-тесты на каждый ответ и сохранение введённой причины.
- [x] **A03 · Полный single-owner workflow.** Добавить пагинацию очереди уловов, кнопку запуска официального импорта и отображение результата/истории, безопасные статусы источников с возрастом данных, cooldown/backoff и ручное обновление очередей. Критерий: владелец может пройти путь «импорт → проверка → решение → история» без API/CLI; старые данные сохраняются при сбое импорта.
- [x] **A04 · Контур медиа-проверки.** Admin-экран показывает approved/upgrade_queued/upgrade_stored медиа, источник, размеры, производные и provenance. Защищённые действия требуют непустую причину: publish атомарно продвигает только проверенные `upgrade_stored` и сохраняет fallback, rollback принимает только связанную пару `approved`/`superseded`; небезопасные состояния возвращают `409`. API-тесты проверяют auth, валидацию причины и вызов безопасных операций; browser acceptance остаётся в A06.
- [ ] **A04 · Контур медиа-проверки.** Admin-экран показывает approved/upgrade_queued/upgrade_stored медиа, источник, размеры, производные и provenance. Защищённые действия требуют непустую причину: publish атомарно продвигает только проверенные `upgrade_stored` и сохраняет fallback, rollback принимает только связанную пару `approved`/`superseded`; небезопасные состояния возвращают `409`. API-тесты проверяют auth, валидацию причины и вызов безопасных операций; browser acceptance остаётся в A06. **Повторно открыт аудитом 22.09: R01/R02 — сохранность, область решения и конкурентная запись.**
- [ ] **A05 · Многопользовательский доступ.** После пилота заменить общий Bearer-токен персональными аккаунтами и короткими серверными сессиями с отзывом, ролями read-only/moderator/importer/owner, operator ID в аудите и журналом входов. Критерий: минимальные права реально ограничивают действия, logout/revoke инвалидируют сессию на сервере.
- [x] **A06 · Browser/accessibility acceptance.** In-app Chromium подтвердил `/admin`, moderation, external sources и media в 320/390/768/1280 px без горизонтального overflow; повторная focused-проверка всех четырёх admin-маршрутов на 320/1280 px также подтвердила `main`/`h1` и overflow `0`. Четыре admin-маршрута имеют landmarks, подписанные поля, именованные кнопки и `alt` у изображений. Visual-matrix regression покрывает 21 маршрут × 4 ширины × 3 режима (`252` комбинации), включая valid/error spot, plan и tackle analytics, без document overflow и с совпадающей SSR-темой. Standalone axe проходит `12/12` маршрутов без serious/critical violations, keyboard/focus regression не оставляет фокус на `BODY`, forced-colors сохраняет видимую границу theme controls, print-regression сохраняет светлую схему и убирает фильтр hero image. Representative screenshots и ручная визуальная проверка shadows/gradients описаны в `docs/ux-reference-review-2026-09-21.md`; headless Playwright остаётся зависящим от sandbox-политики и не считается подтверждённым этим локальным прогоном. Локальная browser/accessibility-приёмка завершена; production gate остаётся отдельным пунктом A07.
- [ ] **A06 · Browser/accessibility acceptance.** In-app Chromium подтвердил `/admin`, moderation, external sources и media в 320/390/768/1280 px без горизонтального overflow; повторная focused-проверка всех четырёх admin-маршрутов на 320/1280 px также подтвердила `main`/`h1` и overflow `0`. Четыре admin-маршрута имеют landmarks, подписанные поля, именованные кнопки и `alt` у изображений. Visual-matrix regression покрывает 21 маршрут × 4 ширины × 3 режима (`252` комбинации), включая valid/error spot, plan и tackle analytics, без document overflow и с совпадающей SSR-темой. Standalone axe проходит `12/12` маршрутов без serious/critical violations, keyboard/focus regression не оставляет фокус на `BODY`, forced-colors сохраняет видимую границу theme controls, print-regression сохраняет светлую схему и убирает фильтр hero image. Representative screenshots и ручная визуальная проверка shadows/gradients описаны в `docs/ux-reference-review-2026-09-21.md`; headless Playwright остаётся зависящим от sandbox-политики и не считается подтверждённым этим локальным прогоном. Production gate остаётся отдельным пунктом A07. **Повторно открыт аудитом 22.09: Нужна приёмка наполненной authenticated admin после последних изменений, включая media-действия и конфликты; см. R12.**
- [ ] **A07 · Production gate.** Выполнить preflight с реальными секретами, проверить Caddy Basic + API Bearer, закрытые внутренние порты, backup/restore PostgreSQL и MinIO, readiness, no-store и внешний smoke после деплоя. Локальный чистый-volume bootstrap и оба Playwright-сценария подтверждены; критерий A07 остаётся открытым до acceptance-runbook с реальными секретами, backup/restore, rollback и процедурой отзыва доступа.
## Готовность открытой альфы — требуется сервер или внешний сервис
@@ -167,7 +197,7 @@
## Правила выполнения
- Сетевой парсинг одной площадки — не чаще одного раза за 30 минут, включая ошибки и разные endpoint.
- Сетевой парсинг одной площадки — не чаще одного раза за 30 минут для всех endpoint. Ответ источника (включая 403/429) расходует окно; доказанный локальный сбой до обращения освобождает только собственный резерв. Неопределённый сетевой исход не доказывает отсутствия запроса. Согласование всех путей — R10.
- Каждая публичная запись обязана показывать источник; неполные данные — отдельную пометку и список отсутствующих полей.
- Реальные источники не используются в тестах: только fixtures и изолированные Docker-сценарии.
- Docker запускается одним общим прогоном для инфраструктурного пакета, а не после каждой правки.