27 KiB
Аудит RF4 Spotter — 8 сентября 2026
Обновление 9 сентября: T01 исправлен (bb61d21) и проверен скриптом deploy/test-proxy-routing.sh. Ниже сохранены исходные находки аудита; актуальные отметки выполнения — в ROADMAP. T02/T03 остаются блокерами production.
База проверки: commit 486b4e9. Аудит охватывает production-конфигурацию, API, импорт и публикацию данных, Astro, пользовательские сценарии, визуальную идентичность и SEO. Это отчёт о текущем состоянии, а не подтверждение готовности к запуску. Исправления приложения в этот пакет не входят.
Вывод
Стек соответствует продукту: Astro SSR + FastAPI + PostgreSQL + MinIO + Caddy. Перенос на Next.js/Vinext, добавление SPA-фреймворка, Redis или микросервисов сейчас не обоснованы. Есть полезная основа: миграции, модерация, provenance, проверка изображений, Docker, backup/restore, SEO-разметка и выразительный рыболовный визуальный язык.
Открывать production пока рано. Подтверждены три блокирующих противоречия конфигурации: маршрутизация формы, несовместимые схемы admin-аутентификации и отсутствие внешней сети у community scheduler. Успешная локальная проверка напрямую через Astro/API эти дефекты не обнаруживает. Главный продуктовый риск — фактическое наполнение и достоверность времени/оценок, а не недостаток декоративных элементов.
Метод и границы
- Прочитаны конфигурации Compose/Caddy/Docker/CI, ключевые обработчики API и Astro, код агрегации, scheduler, research CLI, staging/review/retention, seed, компоненты и стили, текущие планы и ограничения.
- Встроенный браузер: главная текущего локального стенда, desktop 1280×1000 и mobile 390×844; оценены композиция, меню и фильтры. Предыдущий прогон 320/390/768/1280 проверял прежде всего переполнение. Его нельзя считать полноценной визуальной приёмкой всех состояний.
astro check: 40 файлов, 0 ошибок, 0 предупреждений. Целевые тесты CLI/scheduler/activity: 16 passed. Общий pytest был начат, но завершённый результат в этом прогоне не получен; прежние общие результаты не выдаются за новый полный прогон.- Внешние парсеры не запускались, 30-минутный лимит не расходован. Актуальность HTML источников, CVE всех зависимостей, нагрузка, TLS/DNS, реальный backup на сервере и поисковая индексация повторно не проверялись.
- Проверены официальные документы Docker, Caddy и Google. Подтверждение по коду отделено ниже от дизайнерских предложений и будущих проверок.
Приоритеты: P0 — блокирует production; P1 — нужно до открытой альфы; P2 — улучшение качества/масштабирования; P3 — после обратной связи пилота.
Техническая часть и эксплуатация
| ID | Приоритет | Подтверждение и последствие | Критерий исправления |
|---|---|---|---|
| T01 | P0 | deploy/Caddyfile: /api/* уходит в FastAPI, но /api/report и /api/report-screenshot реализованы в Astro. Production-форма попадает в отсутствующий маршрут. |
Через Caddy отправка и повтор загрузки достигают Astro и возвращают ожидаемый 303; /api/v1/* достигает FastAPI. |
| T02 | P0 | Caddy basic_auth на /api/v1/admin/* требует Basic в Authorization; admin JS посылает Bearer, _admin требует именно Bearer. Два слоя используют один несовместимый контракт. |
Выбрать согласованную схему; доказать успешные чтение/модерацию через proxy и отказ неавторизованному клиенту. |
| T03 | P0 | compose.production.yaml: community-scheduler только в backend, сеть internal: true. Нет внешней сети для HTTPS источников. |
Разрешён исходящий доступ scheduler; БД/MinIO остаются без внешних портов. Проверить локальным контролируемым HTTP-источником. |
| T04 | P1 | Astro POST не передаёт идентичность клиента, FastAPI rate limit использует request.client.host. Заявки через web объединяются под его адресом. Доверенные proxy явно не настроены. |
Два клиента имеют независимый лимит; поддельный forwarded header его не обходит; проверка через всю цепочку Caddy → Astro → API. |
| T05 | P1 | В Astro request.formData() выполняется до try, POST fetch без timeout; Caddy не задаёт лимит тела. Лимит 8 МБ в Python применяется после приёма multipart. |
Ограничить входной body до буферизации, обработать повреждённый multipart, задать время ожидания и понятные 413/429/ошибки формы. |
| T06 | P1 | Bootstrap запускает db/minio/api/web, без proxy и community-scheduler; CI проверяет локальный Compose. monitor.sh не включает community-scheduler, /ready знает лишь официальный импорт. |
Один production-contract прогон через proxy, проверка scheduler heartbeat/последнего успеха и уведомление о зависании/остановке. |
| T07 | P1 | GET /api/v1/imports публично возвращает ImportRunOut, включая error_summary и source_url. |
Публичный DTO содержит только необходимые безопасные статусы; детали ошибок доступны администратору. |
| T08 | P2 | Python фиксирует прямые зависимости, но не транзитивные; Docker base tags изменяемы. В CI нет web unit job (test:unit) и отдельного dependency audit. |
Воспроизводимый Python lock/constraints, поддерживаемые runtime-версии и регулярная проверка зависимостей; подключить существующие unit-тесты. CVE здесь не утверждаются. |
| T09 | P2 | activity_rows загружает все уловы окна и агрегирует Python-списками до пагинации; на карточке точки также читается история. Public cache локален процессу. |
Измерить память/p95 на представительном объёме; оптимизировать запросы по результату. Сохранить документированный TTL 20 с до необходимости общей инвалидации. |
| T10 | P2 | API/web стартуют с миграциями/seed; MinIO app policy readwrite шире одного bucket, общие admin credentials. |
Оформить отдельный release/migration шаг, bucket policy и план персонального доступа при росте команды; не добавлять identity provider до потребности. |
Семантика изоляции подтверждена Docker networks. Конфликт заголовков T02 следует из кода и контракта Caddy Basic Auth.
Парсеры и достоверность данных
| ID | Приоритет | Подтверждение и последствие | Критерий исправления |
|---|---|---|---|
| D01 | P1 | oldest_site_source включает выключенные адаптеры. Если самым старым RF4-STAT endpoint окажется disabled, включённый сосед будет постоянно пропускаться. |
Ротация только enabled-кандидатов; тест выключенного соседа, включения обратно, ошибки и двух конкурирующих запусков. |
| D02 | P1 | Research CLI делает check/write раздельно без межпроцессного lock, повреждённый state трактует как пустой; старые source-key записи после перехода к доменам не учитываются. Автономный state не связан с PostgreSQL. | Атомарное резервирование с fail-closed, миграция состояния; production сбор через единый механизм, offline parsing отделён. Нельзя заявлять общую гарантию только на основании предупреждения README. |
| D03 | P1 | fetch_html допускает произвольный override URL, автоматические redirects, неограниченный read(). fetch_site_key различает download.rf4db.com и rf4db.com. |
Единый реестр разрешённых площадок/хостов, проверка URL и redirects до HTTP, лимит ответа, корректные 429/Retry-After в рамках ≥30 минут. |
| D04 | P1 | RF4-STAT fishing выдаёт waterbody_external_id=None. Mapping сохраняет fallback alias по имени, но _auto_publish требует оба external ID. Ранее подтверждённое имя не включает автопубликацию следующей полной записи. |
Использовать единый безопасный контракт алиасов для suggestion/manual/auto; не разрешать неподтверждённое fuzzy-сопоставление. |
| D05 | P1 | Scheduler RF4MAP/RF4 Posts опрашивает по одному фиксированному detail URL. Seed даёт только 2 рыбы/2 водоёма; на локальной главной также только они и 0 активных точек. «Все парсеры включены» не означает полный охват. | Реестр задач/очередь разрешённых URL и пополнение канонического каталога; видимые счётчики fetched/staged/mapped/published/age; приёмочный набор из каждого адаптера. |
| D06 | P1 | Score зависит от количества сообщений одного ника; текст главной «Один игрок не может искусственно поднять уверенность» неверен. Имена без аккаунтов не доказывают независимость. | Исправить обещание, определить ограничения вклада одного игрока и неизвестных авторов; воспроизводимый пример спама не изображает независимые подтверждения. |
| D07 | P1 | В activity окно/затухание по reported_at, подпись свежести по caught_at or reported_at; community publisher записывает published_at также в caught_at. В feed «Получено» — last_seen_at, который обновляется каждым опросом. |
Разделить время улова, публикации, первого получения и последнего обнаружения; старый повторно найденный материал не выглядит новым уловом. |
| D08 | P1 | На /spots/[id] score берётся из первых 100 глобальных activity-строк и только по spot_id, хотя индекс рассчитан на spot+fish. Возможны ложное «данных нет» и оценка одной рыбы под видом всей точки. |
Прямой запрос оценки точки с явным контекстом рыбы; тест >100 комбинаций и нескольких рыб на точке. |
| D09 | P2 | Staging и autopublish коммитятся отдельными операциями; ошибка поздней записи может оставить часть публикаций при failed journal. Изменение сравнивает весь payload; полная история редакций отсутствует. | Явная семантика partial success, идемпотентный retry, сравнение значимых полей и история редакций/удалений; отсутствие записи в очередном списке само по себе не означает удаление. |
| D10 | P2 | source_status анализирует последние 20 запусков, running может считаться healthy; backoff смешан для домена, успешный сосед может сбросить ошибки endpoint. |
Раздельные domain cooldown и endpoint backoff, stalled/running статус, последний успех вне ограничения 20 строк. |
Обнаруженная особенность sitemap не является ошибкой пагинации: API public-spot-pages выдаёт два пути на строку БД, поэтому limit=500 и остановка при <1000 путях согласованы. Нужен контрактный тест и явная документация, а не механическая замена на <500.
UI/UX
| ID | Приоритет | Наблюдение | Критерий/решение |
|---|---|---|---|
| U01 | P1 | На screenshot 1280×1000 период и сортировка стоят вертикально; фильтры занимают около 196 px. Используется details/display:contents, результат не соответствует пятиколоночной задумке. |
Устойчивая desktop-сетка и мобильное раскрытие, проверка реального layout браузеров; отсутствие overflow недостаточно. |
| U02 | P1 | Главная передаёт waterbody/fish/hours только в activity; feed получает лишь limit. После выбора водоёма снизу остаются чужие сигналы. | Фильтровать оба блока согласованно либо явно подписать независимый общий feed. URL и сброс сохраняют понятную область действия. |
| U03 | P1 | Главная показывает первые 20 activity, records — 50, каталоги — 100 activity; навигации по остальным нет. Счётчик items.length выглядит общим числом. |
«Показано N из M», пагинация/следующая страница с сохранением фильтров; отдельно считать уникальные точки и комбинации точка+рыба. |
| U04 | P1 | Ошибки 422/429/5xx формы сводятся к «Проверьте поля»; нет ожидания/защиты от повторной отправки. Сохранение текста через sessionStorage не гарантировано при его недоступности; файл не восстанавливается. | Разные полезные сообщения, Retry-After для 429, блокировка повторного submit, честная подсказка о файле, server fallback. |
| U05 | P2 | В 390×844 результаты начинаются около y=700, mobile меню скрывает последний пункт; при 0 точек зелёный «пульс» всё ещё изображает живую активность. | Компактный первый экран, заметный доступ к скрытой навигации, отдельные empty/stale/error состояния, прямые действия «72 часа», «Сбросить», «Добавить улов». |
| U06 | P2 | Карточки многократно повторяют качество/доверие; «Не рассчитана», «сырые данные», «сюжеты» описывают реализацию. Текстовые badges часто 9–11 px. | Иерархия рыба → точка → приманка → возраст → источник; понятные статусы без выдуманных процентов, крупнее значимые подписи. |
| U07 | P2 | Предыдущая визуальная приёмка недостаточна: главная была пустой. | Матрица empty/1/many/long names/error; desktop/mobile, клавиатура, 200% zoom, раскрытые фильтры и форма; проверить также каталоги, detail и admin. Отдельно повторить axe, не приравнивая его к UX-аудиту. |
Визуальная идентичность
Сильная основа: тёмный хвойный фон, лаймовый акцент, спокойная бумажная подложка, контрастная антиква, озеро, крючок, поплавок, рябь и координатный радар. Это стоит сохранить. Следующие пункты — дизайнерские предложения, не программные дефекты.
| ID | Приоритет | Предложение | Проверяемый результат |
|---|---|---|---|
| V01 | P2 | Утвердить RF4 Spotter как основное имя, «Ни хвоста, ни чешуи» как слоган либо явно выбрать обратную иерархию. | Одинаковое узнаваемое имя в шапке, favicon/manifest, title, OG и footer. |
| V02 | P2 | Свести размеры текста, отступы, радиусы, тени, focus и семантические цвета в небольшой набор CSS-токенов. | Компонентные состояния document/demo; источник и качество различаются текстом/формой, не только цветом. Разделить монолитный минифицированный global.css по компонентам. |
| V03 | P2 | Сделать паспорт точки компактным «полевым журналом»: координатная метка с копированием, линия времени, SVG рыбы, легенда шкалы. | Один основной визуальный акцент на карточку; индекс не выглядит вероятностью поимки, радар не выдаётся за настоящую карту. |
| V04 | P2 | Проверить маленькие знаки 16/32 px, sharpness app icons и maskable safe area; точность OG alt. | Чёткие отдельные SVG/PNG варианты; OG обещает реальный продукт и имеет осмысленную подпись изображения. |
| V05 | P3 | Отдельные обложки водоёмов и тематические OG для рыбы/точки. | Только после согласования прав и измерения веса; не препятствуют чтению карточек и загрузке. |
SEO и производительность
SSR, русский lang, canonical, OG/Twitter, JSON-LD, sitemap, slug URL и 301 со старых UUID уже есть. Недостающие метатеги не являются главным препятствием: сейчас важнее корректные ответы, полнота каталога и полезный постоянный контент.
| ID | Приоритет | Подтверждение/риск | Критерий исправления |
|---|---|---|---|
| S01 | P1 | index/records/report/status при сбое API отображают ошибку с HTTP 200; в detail возможен частично заполненный spot и noindex={!spot}. |
Единая таблица 200/404/422/503, no-store/Retry-After и политика индексации ошибок. Не выдавать пустую аварийную страницу за нормальный результат. |
| S02 | P1 | Sitemap собирает полный справочник, но detail ищет slug только в первых 500 элементах; формы загружают первые 200. При росте появятся sitemap URL с ложным 404. | Прямые slug endpoints и пагинация каталогов; все sitemap URL разрешаются независимо от позиции в справочнике. |
| S03 | P2 | canonical убирает все query; structured data содержит жёсткий домен, PUBLIC_SITE_URL не передаётся в Docker build. Отдельная политика trailing slash/пагинации не оформлена. |
Единый origin, политика вариантов URL и страниц пагинации; внутренние ссылки используют конечные canonical URL (в sidebar ещё UUID redirect). |
| S04 | P2 | Постоянные страницы рыба+водоём живут в sitemap, но контент ограничен 72 часами и быстро превращается в шаблон «данных нет». | Полезный подтверждённый справочный контент, дата обновления/источники, архив последних известных наблюдений с явной давностью, видимые breadcrumbs. Решение об индексации пустых комбинаций. |
| S05 | P2 | Sitemap lastGood хранится в памяти без максимального срока fallback; >49000 путей вызывает ошибку вместо index. | Документированный срок fallback, sitemap index при необходимости, корректный lastmod только по реальным изменениям. |
| S06 | P2 | Hero уже WebP ~71 КБ, но не включён в Caddy matcher brandAssets; старый LCP 9,3 с не является текущим измерением. | Проверить фактический Cache-Control hero; свежий mobile/desktop performance baseline после P0/U01, затем production Core Web Vitals. Не обещать ускорение в секундах без измерения. |
| S07 | P2 | robots содержит обычные правила и не закрывает весь staging; поисковые сервисы/превью ещё не проверены на рабочем домене. | Закрытие тестового окружения, проверка Google Search Console и Яндекс Вебмастера после DNS/TLS, sitemap/JSON-LD/OG validation и наблюдение за индексацией. |
Политика HTTP основана на Google: HTTP status codes, а согласование URL — на Google: canonical URLs. Наличие schema/сайткарты само по себе не обещает индексацию или позиции.
Порядок реализации
- T01–T06: production маршруты, auth, сеть, клиентский лимит и реальная приёмка через Caddy.
- D01–D08, T07: лимиты/публикация, наполнение, честное время и оценка конкретной точки.
- U01–U04, S01–S02: основные сценарии, пагинация, ошибки и доступность индексируемых страниц.
- V01–V04, U05–U07, S03–S06: визуальная система, понятность и поисковое качество.
- T08–T10, D09–D10 и внешний launch checklist; S07 на рабочем домене. V05 после пилота.
Тестировать по риску: адресные unit/contract проверки, один общий proxy/Compose-прогон для инфраструктурного пакета, браузер для визуального пакета. Документация и небольшие стилевые правки не требуют пересборки всех контейнеров. Статус выполнения ведётся в ROADMAP по ID этого отчёта.