docs: audit project and reopen incomplete roadmap gates
This commit is contained in:
@@ -6,7 +6,7 @@ RF4 Spotter — неофициальный сервис свежих точек
|
||||
|
||||
## Статус разработки
|
||||
|
||||
**Проверка 20 сентября 2026: локальный кодовый контур проходит сборочные и тестовые gates, внешний запуск ждёт сервер и его настройки.** Пакет восстановления A01–A13 закрыт. Python: **191 passed, 1 skipped**; Astro check/build и web unit проходят. Граф миграций имеет единственную голову `0020`; OpenAPI artifact синхронизирован с FastAPI (`36 paths`). Изолированный production bootstrap с чистыми томами повторно пройден; живая БД, реальные секреты и импорт внешних источников по-прежнему требуют отдельной инфраструктурной/разрешённой приёмки.
|
||||
**Аудит 22 сентября 2026:** сборка проходит; Python — **228 passed, 1 failed, 1 skipped**, web unit — **14 passed, 1 failed**. Два теста отстали от изменённых контрактов. OpenAPI актуален (**45 paths**), локальная БД на **0022 (head)**, media audit чистый. Обнаружены проблемы сохранности media-решений, конкурентных операций и UX; часть преждевременно закрытых пунктов возвращена в работу. Полный отчёт: [PROJECT_AUDIT_2026-09-22.md](docs/PROJECT_AUDIT_2026-09-22.md). Production и свежая browser-приёмка этим аудитом не подтверждены.
|
||||
|
||||
Актуальные следующие задачи находятся только в [ROADMAP](docs/ROADMAP.md). Старые планы и аудиты сохранены как история и больше не задают порядок работ. До внешнего запуска нужны сервер, DNS/TLS, production-секреты, публичные контакты, внешний backup и канал уведомлений.
|
||||
|
||||
@@ -184,7 +184,7 @@ docker compose up --build
|
||||
## Что реализовано
|
||||
|
||||
- FastAPI и SQLAlchemy 2;
|
||||
- PostgreSQL 17 и линейные миграции Alembic до `0020`;
|
||||
- PostgreSQL 17 и линейные миграции Alembic до `0022`;
|
||||
- идемпотентный seed с двумя точками и свежими демо-уловами;
|
||||
- `GET /api/v1/activity` с фильтрами периода, водоёма, рыбы, способа и сортировки;
|
||||
- `GET /api/v1/spots/{id}` и `/catches`;
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
# Аудит RF4 Spotter — 22 сентября 2026
|
||||
|
||||
Проверенный исходный commit: `25b6f39`. Аудит охватывает API, Astro UI,
|
||||
исследовательские CLI, media, миграции, локальную БД, CI и эксплуатационные
|
||||
сценарии. Изменения этого прохода — отчёт и актуализация плана, не исправление
|
||||
продуктового кода. Внешние источники не запрашивались.
|
||||
|
||||
## Вывод
|
||||
|
||||
Основа приложения работоспособна: production build собирается, OpenAPI
|
||||
синхронизирован, media audit чистый, локальные контейнеры healthy. Однако
|
||||
состояние «все локальные задачи закончились» неверно. Есть проблемы сохранности
|
||||
media-решений, конкурентных операций, навигации и достоверности UI. Несколько
|
||||
пунктов закрыли по проверке наличия элементов или пустой страницы, хотя их
|
||||
критерии включают наполненные состояния и визуальную приёмку.
|
||||
|
||||
## Исполняемые проверки
|
||||
|
||||
| Проверка | Результат текущего прохода |
|
||||
|---|---|
|
||||
| `.venv/bin/pytest -q` | 228 passed, 1 failed, 1 skipped; 90 dependency deprecation warnings |
|
||||
| `npm --prefix apps/web run test:unit` | FAIL; прямой запуск файла уточнил: 14 passed, 1 failed |
|
||||
| `npm --prefix apps/web run build` | PASS; Astro: 87 файлов, 0 errors, 0 warnings, 2 hints по `execCommand` |
|
||||
| `PYTHONPATH=apps/api .venv/bin/python apps/api/export_openapi.py --check` | PASS, 45 paths |
|
||||
| `media_cli --audit` | 704 записи: 466 approved, 226 superseded, 1 duplicate, 11 invalid; issues/orphans пусты |
|
||||
| `media_cli --quality-report` | Среди 253 опубликованных fish-файлов один 48×48, известных альтернатив нет |
|
||||
| `docker compose ps` | API, web, PostgreSQL, MinIO healthy |
|
||||
| `docker compose exec -T api alembic current` | `0022 (head)` |
|
||||
| SQL, транзакция `BEGIN READ ONLY` | waterbody: 21, source_external_id: 19; tackle_item: 0; rig: 0 |
|
||||
|
||||
Python failure: `tests/test_community_cli.py:73` ожидает старый ответ reserve-only
|
||||
без `reserved_at`. Web failure: `apps/web/tests/unit/presentation.test.ts:99`
|
||||
ожидает «Схема монтажа», тогда как продукт правильно показывает «Схема сборки».
|
||||
Это рассогласование тестов с изменёнными контрактами, не основание откатывать
|
||||
терминологию или идентификатор резерва.
|
||||
|
||||
Live E2E, новый screenshot/axe-прогон, production bootstrap, restore drill,
|
||||
нагрузка и проверка CVE в этом проходе не выполнялись. Уже запущенные Docker
|
||||
образы не пересобирались; их health не доказывает соответствие текущему коду.
|
||||
Старые browser-отчёты — исторические доказательства с указанными в них границами.
|
||||
Выводы о гонках ниже основаны на коде; конкурентные production-запросы не выполнялись.
|
||||
|
||||
## Ошибки и недоработки
|
||||
|
||||
### R01 · P1 · Сохранность media-решений
|
||||
|
||||
`apps/api/Dockerfile:9` копирует `data/media` в образ, а сервис api в
|
||||
`compose.production.yaml` не имеет persistent media volume. Admin publish/rollback
|
||||
записывают manifest в этот каталог. Пересоздание контейнера вернёт состояние
|
||||
из образа. `deploy/backup.sh` сохраняет PostgreSQL и MinIO, но не этот manifest.
|
||||
Нужен единый долговременный writable store с bootstrap из Git, backup/restore и
|
||||
правилом обновления release. Критерий: решение переживает recreate, обновление
|
||||
образа и восстановление, не теряет provenance и fallback. Без cooldown.
|
||||
|
||||
### R02 · P1 · Область публикации и конкурентная запись manifest
|
||||
|
||||
Кнопка `apps/web/src/pages/admin/media.astro:57` появляется на отфильтрованной
|
||||
странице, но отправляет только note. Endpoint `routers/admin.py:64` вызывает
|
||||
`publish_quality_upgrades`, который выбирает ВСЕ `upgrade_stored`
|
||||
(`rf4_research/media_assets.py:205`). Проверенная видимая выборка и область
|
||||
действия не связаны. Использование общего `.tmp` и отсутствие lock/version
|
||||
дополнительно допускают конфликт publish/rollback/CLI: atomic replace не
|
||||
защищает read-modify-write от потери чужого изменения.
|
||||
Нужны выбранные asset IDs, preview набора, версия manifest, блокировка и
|
||||
проверка реальных файлов перед продвижением. Критерий: скрытый кандидат не
|
||||
публикуется, устаревшее решение возвращает конфликт, одновременные операции
|
||||
сохраняют корректный JSON и обе истории решений. Без cooldown.
|
||||
|
||||
### R03 · P1 · Идемпотентность отправки улова
|
||||
|
||||
`routers/submissions.py:77` после `IntegrityError` возвращает winner без
|
||||
повторной проверки payload hash и времени действия ключа. Уникальность ключа
|
||||
хранится дольше пяти минут, поэтому путь достижим и при повторе старого ключа.
|
||||
Проверка replay token в обычной ветке также перестаёт проходить после успешного
|
||||
upload, который очищает token hash. Нужен единый replay-контракт для всех веток,
|
||||
включая завершённый upload, и различение конфликтов idempotency/Spot/Bait.
|
||||
Критерий: одинаковый запрос возвращает тот же результат, другой payload — 409;
|
||||
конкурентное создание одной точки/приманки не превращается в необработанный 500.
|
||||
Без cooldown; конкурентная проверка в изолированном PostgreSQL.
|
||||
|
||||
### R04 · P1 · Скриншот: повтор, гонка и восстановление формы
|
||||
|
||||
`add_screenshot` читает report через `db.get`, без row lock: два запроса могут
|
||||
пройти проверку пустого screenshot_key и записать два объекта. После успешного
|
||||
upload повтор уже получает 401 из-за очищенного token hash. Если клиент потерял
|
||||
ответ, он не может отличить успех от неудачи. Кроме того,
|
||||
`pages/api/report-screenshot.ts:15` удаляет только upload-cookie, сохраняя
|
||||
старый `rf4-idempotency-key` после восстановления; следующий новый улов может
|
||||
конфликтовать с предыдущим. Нужны одноразовая атомарная операция, повторяемый
|
||||
ответ и завершение состояния формы. Критерий: lost response + retry успешно
|
||||
восстанавливаются, новый улов отправляется, лишние S3-объекты не остаются.
|
||||
|
||||
### R05 · P1 · Пагинация и семантика каталога снастей
|
||||
|
||||
`pages/tackle/index.astro:18` использует общий offset для предметов и сборок,
|
||||
а Pagination в конце зависит только от `result.total`. При 0 предметов и 49
|
||||
сборках вторую страницу сборок нельзя открыть через UI. Сбой предметов скрывает
|
||||
и успешно загруженные сборки. Неверная category или слишком длинный q дают API
|
||||
422, но UI называет это временной недоступностью 503. Empty search описан как
|
||||
незаполненный каталог. Нужны отдельные пагинации/состояния и валидация параметров.
|
||||
Критерий: доступны все 49+ сборок независимо от предметов, фильтры сохраняются,
|
||||
ошибка ввода отличается от сбоя API и отсутствия данных. Без cooldown.
|
||||
|
||||
Дополнительно карточки этого каталога обёрнуты в `<a>`, а вложенный
|
||||
`DataPassport` через `SourceBadge` создаёт ещё один `<a>` при source_url.
|
||||
Это недопустимое вложение интерактивных ссылок; браузер может перестроить DOM.
|
||||
Сделать отдельные ссылки названия и источника, проверить наполненную карточку
|
||||
клавиатурой и по итоговому DOM. На пустом каталоге дефект не виден.
|
||||
|
||||
### R06 · P1 · Импорт и срок годности сохранённого плана
|
||||
|
||||
`pages/plan.astro:45` автоматически заменяет localStorage при открытии share URL.
|
||||
Параметр остаётся в URL: удаление точки или очистка отменяются при reload.
|
||||
Свежесть показывается как «Свежесть сохранена» без пересчёта времени, а ссылки
|
||||
строятся из query JSON. Поэтому текст «Данные не отправляются на сервер»
|
||||
некорректен для открытия share URL: query приходит серверу.
|
||||
Нужны preview/merge/replace, однократный импорт с удалением параметра,
|
||||
восстановление предыдущего плана, обработка storage errors и вычисляемый возраст.
|
||||
Рассмотреть fragment для переносимого payload. Критерий: чужая ссылка не стирает
|
||||
план без выбора, reload не воскрешает удалённое, старые сведения явно устаревшие.
|
||||
|
||||
### R07 · P1 · Координатная схема
|
||||
|
||||
`components/ActivityMap.astro` рисует все переданные items; `index.astro:100`
|
||||
не ограничивает их одним водоёмом или точностью координат. На одной сетке
|
||||
смешиваются независимые системы координат. Несколько элементов одной grid cell
|
||||
накладываются, минимальная ширина маркера превышает ширину ячейки; overflow hidden
|
||||
может обрезать содержимое без document overflow. Нужны группировка по водоёму,
|
||||
явные approximate/area-состояния, обработка совпадений и доступный список.
|
||||
Критерий: все точки различимы и достижимы мышью/клавиатурой на 320 px,
|
||||
водоём виден в подписи, схемы разных водоёмов не смешаны. Без cooldown.
|
||||
|
||||
### R08 · P1 · Достоверность рекомендаций и стоимость аналитики
|
||||
|
||||
`routers/analytics.py:32` разрешает публичному клиенту снизить min_samples и
|
||||
min_players до 1 и получить `recommendation`, хотя UI обещает минимум 3/2.
|
||||
Группировка идёт по raw строке, а не canonical identity; один подтверждённый ID
|
||||
может дать ссылку всей группе, содержащей unresolved-строки. Данные выбираются
|
||||
по reported_at: старый улов, импортированный сегодня, выглядит свежим.
|
||||
Все отчёты и компоненты окна загружаются в Python без лимита ответа.
|
||||
Нужны неизменяемый публичный порог, явное время улова/получения, conservative
|
||||
identity-группировка и измерение фактического endpoint на больших fixtures.
|
||||
Критерий: 1/1 никогда не рекомендация, unresolved не наследует подтверждение,
|
||||
старый улов не представлен свежим; ограниченный ответ и документированный бюджет.
|
||||
|
||||
### R09 · P1 · Полнота удаления и retention
|
||||
|
||||
`tackle_components.py:35` копирует source_url/raw_payload в дочерние записи.
|
||||
`retention.py:94` и `routers/admin.py` при удалении очищают только родительский
|
||||
CatchReport. Дочерние копии provenance остаются. Нужно определить поля,
|
||||
содержащие личные сведения, и каскадно очищать их с сохранением разрешённых
|
||||
агрегатов. Удаление S3 до commit БД также требует повторяемой процедуры при
|
||||
отказе транзакции. Критерий: повторная очистка безопасна, чувствительные копии
|
||||
не остаются в дочерних payload, ошибки между S3/БД восстанавливаются. Без cooldown.
|
||||
|
||||
### R10 · P1 · Согласованная модель cooldown
|
||||
|
||||
`community_cli.py:385` освобождает резерв при любом URLError кроме HTTPError.
|
||||
После ответа-редиректа DNS-ошибка следующего hop тоже попадает сюда, хотя первый
|
||||
сервер уже получил запрос. Community CLI и media CLI используют общий helper
|
||||
и JSON-состояние при одинаковом state-file, scheduler — отдельную историю БД.
|
||||
Media CLI не применяет новую отмену резерва при локальном сбое. Единого
|
||||
координирующего хранилища и одинаковой классификации исходов у всех путей нет.
|
||||
Нужны признаки стадии запроса и единая координация запусков; отменять резерв
|
||||
только при доказанном отсутствии обращения. Критерий: DNS до первого запроса
|
||||
освобождает свой резерв, сбой после redirect — нет; CLI и scheduler не расходуют
|
||||
одно окно одновременно. Проверять offline, без запросов к RF4DB.
|
||||
|
||||
### R11 · P2 · Media: подписи, происхождение и размеры
|
||||
|
||||
`EntityMedia.astro:16` при любом размере <256 заявляет, что источник не содержит
|
||||
проверенной крупной версии; размеры сами этого не доказывают. Новая подпись не
|
||||
проверена в compact/light/dark после добавления. `media_catalog.py:40` считает
|
||||
всякий неизвестный URL официальным RF4 и распознаёт домен подстрокой.
|
||||
Нужны unknown-источник по умолчанию, точное сопоставление hostname, отдельное
|
||||
подтверждение отсутствия альтернатив, перенос длинных подписей и review низкого
|
||||
разрешения по всем типам assets. Для revoked media отдельно определить TTL:
|
||||
годовой immutable cache позволяет старому клиенту показывать ранее выданный файл.
|
||||
Критерий: неизвестный источник не «официальный», компактная подпись не обрезана,
|
||||
обещания отзыва соответствуют фактической cache-политике. Без загрузок.
|
||||
|
||||
### R12 · P2 · Честная визуальная и административная приёмка
|
||||
|
||||
`docs/ux-reference-review-2026-09-21.md` описывает representative light/mobile
|
||||
review и прямо оставляет production CLS/INP и хранение references открытыми.
|
||||
Поэтому фраза D07 «подтверждены WCAG AA, отсутствие CLS» не доказана. Последняя
|
||||
проверка G07 видела только empty-state (в БД нет снастей/сборок), U06 — DOM
|
||||
одной точки. Наличие кнопки не подтверждает её работу. Административная форма
|
||||
входа не заменяет authenticated review очереди и конфликтов.
|
||||
Критерий: привязанные к revision screenshots light/dark на mobile/desktop,
|
||||
наполненные, длинные, ошибочные состояния, keyboard, contrast, theme без потери
|
||||
ввода и реальные действия на изолированных fixtures. Production metrics остаются
|
||||
отдельным gate. Это локальная приёмка, cooldown не нужен.
|
||||
|
||||
### R13 · P2 · Вернуть зелёные проверки и устойчивые CI artifacts
|
||||
|
||||
Исправить две устаревшие проверки, описанные выше, добавить адресные regressions
|
||||
для R01–R12 после исправления функций. CI есть в `.gitea/workflows/`, а не в
|
||||
`.github`: Python/миграции, Astro, Compose E2E и pip-audit уже настроены.
|
||||
Нужны сохранение успешных visual reports, явный Node/Python baseline,
|
||||
проверка web dependency advisories и версий контейнеров. Текущий аудит не
|
||||
подтверждает отсутствие CVE; внешнюю проверку зависимостей выполнять отдельно.
|
||||
|
||||
## Улучшения продукта и эксплуатации
|
||||
|
||||
- **R14 · P2 · Разделение подтверждённого и демонстрационного каталога.**
|
||||
`seed.py` создаёт справочники, точки и описания до проверки `seed_demo_data`.
|
||||
При production false демо-уловы выключены, но редакционные описания точек
|
||||
остаются. Отделить обязательный справочник от demo seed, показывать полноту
|
||||
реального каталога/unknown отдельно от числа загруженных элементов. Счётчик
|
||||
media-файлов не означает наличие canonical карточек. Критерий: чистый production
|
||||
не создаёт неподтверждённых описаний, local fixtures явно обозначены.
|
||||
- **R15 · P2 · Восстановление пользовательских действий.** Сохранение формы при
|
||||
422/429/timeout, понятный конфликт повторной отправки, undo удаления из плана,
|
||||
общие utility для clipboard/storage. Критерий: пользователь не вводит весь
|
||||
улов заново и различает «улов сохранён, фото не загружено» от полного отказа.
|
||||
- **R16 · P2 · Измеряемая производительность.** Media manifest перечитывается и
|
||||
сканируется для каждого изображения (`published_file`); аналитика грузит ORM
|
||||
объекты целиком. Измерить реальный API, RSS, число SQL и количество reads,
|
||||
затем добавить индекс digest/mtime cache и bounded aggregation. SQL-only
|
||||
query-plan drill не подменяет endpoint measurement. Redis заранее не нужен.
|
||||
- **R17 · P2 · Публичная навигация и SEO снастей.** `sitemap.xml.ts` перечисляет
|
||||
рыбу/водоёмы/точки, но не каталог и карточки снастей. Добавить только реально
|
||||
подтверждённые индексируемые карточки; единый переход из каталога к снастям,
|
||||
объяснение справочника и аналитики, корректные пустые/недоступные состояния.
|
||||
- **R18 · P1 · Release gate и инфраструктурные границы.** Dev Compose публикует
|
||||
API, web и MinIO console на всех интерфейсах; defaults известны. Привязать
|
||||
dev-порты к loopback либо явно выделить opt-in LAN. Проверить production
|
||||
proxy/client-IP цепочку и rate-limit на двух клиентах. Секреты, DNS/TLS,
|
||||
внешние backup/alerts, права источников и пилот остаются отдельными блокерами.
|
||||
|
||||
## Состояние данных и корректировка плана
|
||||
|
||||
В `.cache/waterbodies/rf4db-level_019_american_pond-20260921.json` уже есть
|
||||
сохранённый snapshot: 19 видов, 2 URL изображений, description null, point_urls
|
||||
пустой. Старое утверждение ROADMAP о текущей Cloudflare-странице устарело.
|
||||
Это подтверждение локального файла, не нового сетевого запроса и не импорта.
|
||||
Подготовка его versioned provenance и offline import может выполняться без
|
||||
cooldown; полнота всех detail-страниц W02 всё ещё не достигнута.
|
||||
|
||||
Повторно открываются A04/B23 (media mutation), G06/G07, U04/U05,
|
||||
B25/D07/A06/U06. Остальные ранее реализованные функции не объявляются
|
||||
несуществующими. Новые критерии и порядок работ — в ROADMAP; полный пункт
|
||||
закрывается только после устранения дефекта и адресной приёмки.
|
||||
+53
-23
@@ -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 запускается одним общим прогоном для инфраструктурного пакета, а не после каждой правки.
|
||||
|
||||
Reference in New Issue
Block a user