Files
rf4-spotter/docs/REGRESSION_AUDIT_2026-09-09.md

24 KiB
Raw Permalink Blame History

Повторный аудит после исправлений — 9 сентября 2026

Вывод и границы проверки

Текущий 9ae05ef не готов к production. Проверены изменения c6fdc96..9ae05ef: три коммита, 19 файлов, 208 добавленных и 80 удалённых строк. Изменений тестов, README и ROADMAP в этом диапазоне нет. Поэтому по доступной ветке нельзя подтвердить выполнение всего предыдущего аудита: часть исправлений полезна, часть неполна, есть новые блокирующие регрессии.

Это проверка кода и локальных команд, а не повторная визуальная приёмка в браузере или проверка рабочего сервера. Парсеры в сеть не запускались: разрешённый интервал не менее 30 минут на площадку сохраняется. Production/bootstrap не запускался; для Caddy использован только одноразовый контейнер без сети. Неотслеживаемые .gigacode/, .gigaide/, .idea/ не изменялись.

Фактические проверки

Проверка Результат
.venv/bin/pytest -q --tb=short 10 failed, 86 passed, 1 skipped, 3,54 с
.venv/bin/pytest -q tests/test_community_cli.py --tb=short 2 failed, 1 passed
npm --prefix apps/web run check 40 файлов, 0 ошибок/предупреждений
npm --prefix apps/web run test:unit 1 успешный test-file subtest
npm --prefix apps/web run build Успех
Caddy 2.10.2, caddy adapt с текущим Caddyfile Ошибка: строка 8, unrecognized directive: body_limit

Из 10 падений Python: два относятся к research cooldown; четыре — к изменённому activity-контракту; два — к новой зависимости scheduler helpers от внешней БД; по одному — к изменению сигнатуры rate limit и состава readiness. Последние два сами по себе не доказывают поломку функциональности: старые проверки не обновлены под новый контракт. Но зелёной приёмки изменений нет. Успешная сборка Astro не проверяет соответствие JSON реальному типу api<T>.

Подтверждённые регрессии и недоработки

R01 · P0 · Production proxy не может запуститься

  • Доказательство: Caddyfile, строка 8, body_limit 10M; локальный caddy adapt завершается ошибкой неизвестной директивы.
  • Последствие: недоступны сайт, API и файловый домен при запуске с этим конфигом. Прежняя успешная проверка маршрутизации не подтверждает текущий HEAD.
  • Исправление: поддерживаемая конфигурация лимита тела, затем проверка конфигурации и маршрутов; проверить 413 для JSON и multipart. Не ограничиться удалением строки: требование защиты от больших запросов остаётся.

R02 · P0 · Activity API изменён без миграции всех страниц

  • main.py: /api/v1/activity теперь возвращает {items,total,limit,offset}. На новый формат переведена только главная.
  • fish/[slug].astro и waterbodies/[slug].astro вызывают items.map вне try у объекта, а не массива: путь рендера заканчивается TypeError.
  • waterbodies/[slug]/[fish].astro проверяет items.length у объекта и выбирает ложное пустое состояние.
  • spots/[id].astro вызывает activityRows.find у объекта; исключение перехватывается как 503, запрос timeline не выполняется.
  • Четыре существующих API-теста падают после смены контракта. Перечисленные последствия страниц установлены по коду; отдельный SSR-прогон ещё нужен.
  • Приёмка: один общий контракт и все потребители, SSR для четырёх маршрутов с данными и пустым items, полная пагинация без потерь; старый контракт либо явно мигрирован, либо сохранена совместимость.

R03 · P1 · Research CLI не читает и не создаёт cooldown state

  • community_cli.py: enforce_fetch_interval открывает отсутствующий файл в r, mark_fetch — в r+; оба падают при первом запуске. Подтверждено двумя тестами.
  • Для существующего файла f.read(encoding="utf-8") тоже ошибочен: encoding задаётся при открытии, а не в TextIOWrapper.read.
  • Раздельные shared/exclusive lock не делают проверку и резервирование атомарными. Разблокировка перед закрытием/flush записи оставляет дополнительное окно гонки; миграции старых ключей нет.
  • Приёмка: первый запуск, существующий и повреждённый state, миграция ключей, конкурентные процессы, неудачный HTTP расходует интервал. Production и исследовательский путь не должны обходить общий лимит.

R04 · P1 · Disabled-фильтрация может обойти общий интервал сайта

  • community_scheduler.py: configured_sources() теперь оставляет только enabled. Из этого списка строится и site_sources для поиска недавних попыток.
  • Сценарий по коду: RF4-STAT fishing только что запрошен → его отключают → posts больше не учитывает fishing в истории сайта и может запросить тот же сайт раньше 1800 секунд. Исключать отключённые endpoint нужно из кандидатов, не из истории площадки.
  • run_source(disabled) теперь может получить KeyError до прежнего безопасного return False; повторные снимки registry открывают гонку при переключении enabled.
  • Приёмка: выключение/включение endpoint не сбрасывает site cooldown; конкурентные запуски видят общую историю и только актуальных кандидатов.

R05 · P1 · Bootstrap перестал быть изолированным и безопасным для источников

  • test-production-bootstrap.sh добавляет production proxy community-scheduler, но compose.bootstrap.yaml переопределяет только API/web.
  • Наследуются host-порты 80/443, production домены/TLS и реальные URL парсеров. Свежая БД bootstrap не знает историю запросов другого стека; повторные проверки способны нарушать 30-минутное ограничение. Импорт также конфликтует с проверкой catch_report = 0.
  • Проверки curl/Playwright продолжают ходить напрямую на API/web, а не через новый proxy: запуск контейнера не равен проверке маршрутизации.
  • Приёмка: loopback high ports, локальные HTTP-домены, fixture HTTP-источник и запрещённый внешний egress; сценарии формы/модерации проходят через Caddy. Реальные источники — только отдельный согласованный smoke с общей историей интервалов.

R06 · P1 · Фильтры сигналов сравнивают slug с названием

  • index.astro отправляет выбранные slug fish/waterbody.
  • main.py, community_observations, сравнивает их с fish_name/waterbody_name (названия источника). Наблюдение «Щука» не совпадает с pike и исчезает при фильтрации.
  • Приёмка: единая семантика slug/ID с подтверждёнными mapping/alias; отдельно определить поведение несопоставленных наблюдений. Проверить источник, неполноту и выбранные фильтры вместе, не теряя атрибуцию.

R07 · P1 · Ошибка главной остаётся HTTP 200 и индексируемой

  • index.astro: if (unavailable) находится до запросов, сразу после присвоения false; в catch меняется только флаг. Ветка 503/Retry-After/noindex никогда не срабатывает при сбое API.
  • На detail-страницах noindex зависит от найденной сущности, не от unavailable: при успешном справочнике и сбое activity ошибочная страница не закрывается через этот флаг.
  • Приёмка: после завершения загрузки согласовать status, noindex, Cache-Control, Retry-After и JSON-LD для 200-empty/404/422/503; аварийная страница не изображает полноценный Dataset.

R08 · P1 · Фильтры UI не доведены до рабочего responsive-состояния

  • global.css: .filter-advanced-field{display:contents} применяется уже к label: текст подписи и select теряют общий grid-контейнер и становятся отдельными grid items.
  • На mobile поля скрыты CSS, но JS добавляет класс filter-compact-hidden, который задаёт противоположное — display:contents!important. Управления раскрытием больше нет. Без JS период и сортировка недоступны.
  • index.astro: только вариант 24 часов имеет selected; после запроса 6/12/72 форма показывает первый вариант вместо фактического фильтра и может менять период при повторной отправке.
  • Приёмка: связная label/select компоновка, сохранённые выбранные значения, доступные все фильтры с JS и без него; desktop/mobile, клавиатура и zoom. Это статические находки; визуальную приёмку нужно выполнить после исправления R02.

R09 · P1 · Публичной пагинации по-прежнему нет

  • index.astro запрашивает всегда limit=20&offset=0, добавлен total, но нет перехода к следующей странице активности.
  • Рекорды/каталоги также не получили полноценную публичную навигацию по страницам в проверенном diff. Рост лимита полевых сигналов до 48 не решает задачу остальных списков.
  • Приёмка: 21+ результатов достижимы, фильтры сохраняются, счётчик различает показанное/общее; определить SEO-политику пагинации.

R10 · P1 · Ошибки формы теряют черновик, timeout распознаётся неверно

  • report.astro восстанавливает черновик и фокус только при create_error, но не при новых rate_limited, server_error, timeout.
  • api/report.ts ищет timeout как TypeError с текстом abort; AbortSignal.timeout использует TimeoutError, поэтому новое состояние обычно недостижимо.
  • Отключение кнопки не даёт серверной идемпотентности: при таймауте создания неизвестно, сохранил ли API заявку; повтор может создать дубль. Обработка multipart retry-маршрута и единая обработка 413 также требуют завершения.
  • Приёмка: восстановление полей/фокуса при каждом отказе, безопасное поведение при недоступном sessionStorage, корректная классификация timeout и повтор без дублирования сохранённого улова.

R11 · P1 · Проверка URL происходит после сетевого обращения

  • community_cli.py, fetch_html: сначала urlopen, затем allowlist конечного response.url. Запрос к запрещённому initial URL или redirect уже произошёл до отказа.
  • Лимит чтения 5 МБ полезен, но не исправляет эту проблему. fetch_site_key не объединяет rf4db.com и download.rf4db.com в площадку.
  • Приёмка: validate scheme/host/port до I/O и на каждом redirect, общий ключ площадки, ограничение ответа; тесты только на mock/fixture transport.

R12 · P1 · Мониторинг не сигнализирует о зависшем импорте

  • readiness.py добавляет community-компонент, но его failed/stale/unknown не влияют на итог ready. Выбирается одна последняя попытка всех источников: успех одного маскирует проблемы других.
  • monitor.sh проверяет лишь running контейнер и наличие текста "status":"ready" где-либо в JSON. Он не проверяет component status/свежесть каждого источника.
  • Приёмка: разделить готовность API обслуживать данные и здоровье сбора, но обязательно выдавать мониторинговую тревогу по enabled source/site с учётом ротации и backoff. Проверить running без завершения, stale и failed при исправных DB/MinIO.

R13 · P1 · Доверие к IP клиента не ограничено proxy boundary

  • main.py, _check_rate_limit: первый X-Forwarded-For принимается от любого peer без проверки доверенного прокси и валидности IP.
  • api/report.ts при отсутствии заголовков передаёт общий unknown. Это вновь общий bucket для прямого доступа к Astro.
  • Публичный обход через штатный Caddy здесь не доказан: proxy может очищать входной XFF. Подтверждено отсутствие защиты на уровне API и зависимость от внешнего trust boundary.
  • Приёмка: определить допустимые peer/proxy, проверять IP/цепочку, доказать независимость двух клиентов через proxy и игнорирование подделанного XFF на недоверенном входе.

R14 · P2 · Registry источников стал зависеть от БД при разборе CLI

  • cli.py вызывает configured_sources() при построении argparse choices, до разбора команды, включая --help. Теперь это открывает внешнее соединение с БД.
  • Два прежних scheduler unit-теста падают с connection refused к localhost:5432: helpers перестали быть автономными.
  • Приёмка: отделить статический registry от выборки enabled в переданной сессии; --help работает без БД; unit-тесты не требуют поднятого PostgreSQL, отдельная интеграционная проверка покрывает конкуренцию.

R15 · P1 · Заявленные D04/D06/D07/D08 выполнены не полностью

  • D04: community_importer.py добавляет fallback только водоёма по Waterbody.name_ru; рыба по-прежнему требует external ID и alias. review_note говорит о проверенных алиасах даже при новом name fallback. Нужны единые правила и точное объяснение автоматического соответствия.
  • D06: на главной остаётся «Один игрок не может искусственно поднять уверенность». Формула activity.py при 10 отчётах одного игрока с source confidence 100 даёт 72%: 45 + 7 + 20. Ограничения вклада по-прежнему нет.
  • D07: замена last_confirmed_at на reported_at не разделяет все события. community_review.py всё ещё присваивает caught_at=published_at. Неизвестное время улова нельзя выдавать за известное.
  • D08: запрос точки теперь ограничен водоёмом, но остаётся top-100; поиск только по spot_id не различает виды рыбы. После R02 нужен адресный агрегат/явный список видов на точке, а не поиск внутри страницы рейтинга.
  • Приёмка: fixture-набор с отсутствующим ID, публикацией позже улова, неизвестным временем, одним игроком и несколькими рыбами одной точки; публичные подписи соответствуют фактическим данным.

Что полезного уже добавлено

  • Community scheduler подключён к backend и edge в production Compose (направление T03 верное; egress-проверка текущего стека ещё нужна).
  • Публичный /imports использует отдельный DTO без диагностических подробностей (T07; добавить regression-проверку полей).
  • Введены лимит ответа источника 5 МБ, таймауты POST, отдельные сообщения 429/5xx, счётчик activity total и 503/noindex для рекордов. Это частичные улучшения, не основание закрывать связанные задачи целиком.
  • Прежние исправления разделения Astro/API и Basic/Bearer сохранены в коде, но новый невалидный Caddyfile блокирует их применение.

План исправления и дальнейших работ

Рабочие чекбоксы находятся в ROADMAP. Порядок: R01 → R02 → R03/R04/R05/R11 → R06R10 → R12R15. Не нужно перезапускать Docker после каждой правки.

  1. Восстановить deploy и контракт страниц. Адресные offline-тесты + один Caddy adapt.
  2. Устранить риски интервала и произвольных HTTP-запросов. Bootstrap перевести на fixtures до его следующего запуска.
  3. Закрыть пользовательские/SEO-регрессии и правдивость данных. Затем единая SSR/браузерная матрица с наполненными страницами.
  4. Восстановить зелёный Python suite, расширить web tests проверками потребителей API и добавить их в CI. Не подменять исправление регрессий ослаблением assertions.
  5. Один изолированный production acceptance: Caddy → Astro → API, форма → модерация → публикация с обязательным источником, неполные сигналы и мониторинг. Без живого scraping.
  6. Продолжить незавершённый предыдущий аудит: D05/S02 (полный каталог, lookup по slug, очередь detail URL); V01V04/U05U07 (идентичность, токены, плотность карточек, координаты, иконки, визуальная приёмка); S03S06 (canonical, полезные постоянные страницы, sitemap, performance); T08T10/D09D10 (воспроизводимость, нагрузка, release/migration, ограниченные права хранения, история импорта/backoff).
  7. После сервера: внешний launch checklist, S07; после обратной связи — V05. Astro/FastAPI/PostgreSQL сохраняются: смена стека эти дефекты не исправит.

Каждый пункт закрывается только с доказательством своей приёмки, обновлением README при изменении поведения и отдельным коммитом. Старые отметки о завершении относятся к прежним ревизиям, а не автоматически к текущему HEAD.