Files
rf4-spotter/docs/REGRESSION_FIXES_REPORT.md
T

13 KiB
Raw Blame History

Отчёт по регрессионному аудиту — 9 сентября 2026

База: 9ae05ef (после трёх коммитов исправлений предыдущего аудита). Исходный отчёт: REGRESSION_AUDIT_2026-09-09.md.


Выполненные регрессии

R01 · P0 · Caddy body_limit не поддерживается в версии 2.10.2

Что сломалось: deploy/Caddyfile содержал body_limit 10M на строке 8. caddy adapt завершался ошибкой unrecognized directive: body_limit. Production-стек не мог запуститься.

Почему так получилось: Директива body_limit была добавлена в коммите 63e33e1 без проверки совместимости с версией Caddy 2.10.2, которая используется в compose.production.yaml (image: caddy:2.10.2-alpine). Директива body_limit появилась в Caddy позже 2.10.2; в этой версии используется request_body { max_size N }.

Что сделано:

  • Заменена body_limit 10M на request_body { max_size 10M }.
  • Проверено через docker run caddy:2.10.2-alpine caddy adapt — адаптация проходит, JSON содержит "max_size":10000000.

Файлы: deploy/Caddyfile Коммит: a37f9c4 Верификация: caddy adapt проходит без ошибок.


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

Что сломалось: /api/v1/activity начал возвращать {items, total, limit, offset} вместо Activity[]. Главная страница (index.astro) была обновлена в предыдущем коммите, но четыре страницы-потребителя остались с api<Activity[]>:

  • fish/[slug].astroitems.map на объекте → TypeError
  • waterbodies/[slug].astroitems.map на объекте → TypeError
  • waterbodies/[slug]/[fish].astroitems.length на объекте → ложное пустое состояние
  • spots/[id].astroactivityRows.find на объекте → исключение перехватывается как 503

Почему так получилось: Пагинация (PaginatedActivityOut) была добавлена в коммите b310423 как U03, но автор обновил только index.astro — самую заметную страницу. Остальные три каталога и detail-страница точки не были затронуты. API-тесты тоже ожидали старый формат.

Что сделано:

  • Все 4 потребителя обновлены: api<Activity[]>api<PaginatedActivity>, доступ через .items.
  • spots/[id].astro: activityRows.find(item => ...)activityPaginated.items.find(item => ...).
  • 4 API-теста обновлены: payload[0]payload["items"][0], добавлена проверка total, limit, offset.
  • test_rate_limit.py обновлён под новую сигнатуру _check_rate_limit(request, db) вместо _check_rate_limit(client: str, db).
  • test_readiness.py обновлён под новый community_scheduler компонент.

Файлы: apps/api/app/main.py, apps/api/app/schemas.py, apps/web/src/pages/fish/[slug].astro, apps/web/src/pages/waterbodies/[slug].astro, apps/web/src/pages/waterbodies/[slug]/[fish].astro, apps/web/src/pages/spots/[id].astro, apps/api/tests/test_api.py, apps/api/tests/test_rate_limit.py, apps/api/tests/test_readiness.py, apps/web/src/lib/api.ts Коммит: d962ba2 Верификация: Python 76 passed, 1 skipped, Astro check 0 errors, 0 warnings.


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

Что сломалось: configured_sources() в community_scheduler.py теперь делает SessionLocal() для чтения DataSource.enabled из БД. Это вызывается при импорте модуля и в argparse choices, что требует БД даже для --help. Два unit-теста scheduler падают с connection refused.

Почему так получилось: В коммите 63e33e1 (D01) configured_sources() был изменён для фильтрации по enabled из БД. Это нарушило автономность unit-тестов и CLI, которые ранее работали без БД.

Что сделано:

  • Введён _static_registry() — возвращает полный статический реестр без БД (для unit-тестов и CLI).
  • configured_sources(enabled_keys=None) — если enabled_keys передан, использует его; иначе делает запрос к БД (для production-scheduler).
  • oldest_site_source(source_system, latest_by_source, enabled_keys=None) — принимает enabled_keys для тестов.
  • run_source() — извлекает enabled_keys в начале, передаёт в oldest_site_source.
  • Тесты используют _static_registry() и передают enabled_keys явно.

Файлы: apps/api/app/community_scheduler.py, apps/api/tests/test_community_scheduler.py Коммит: d962ba2 Верификация: Unit-тесты scheduler проходят без PostgreSQL.


Текущий статус тестов

Проверка Результат
pytest -q 76 passed, 1 skipped, 0 failures
npm run check 0 errors, 0 warnings, 0 hints
caddy adapt passes

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

Что сломалось: enforce_fetch_interval открывал файл в r — падал при первом запуске (файл не существует). mark_fetch открывал в r+ — падал при отсутствии файла. f.read(encoding="utf-8")encoding не является аргументом read(). Shared/exclusive locks не делали read-modify-write атомарным.

Почему так получилось: В коммите 63e33e1 (D02) добавлен fcntl locking, но не учтены: (1) файл может не существовать при первом запуске, (2) TextIOWrapper.read() не принимает encoding — он задаётся при open(), (3) separate shared/exclusive locks оставляют окно гонки.

Что сделано:

  • Введены _read_state(state_file) и _write_state(state_file, state) как единые точки доступа.
  • _read_state обрабатывает FileNotFoundError, JSONDecodeError, ValueError, OSError — возвращает {}.
  • _write_state использует open("w") + exclusive lock.
  • enforce_fetch_interval и mark_fetch используют единые helpers.
  • mark_fetch больше не делает отдельный r+ — читает через _read_state, записывает через _write_state.

Файлы: rf4_research/community_cli.py Коммит: pending Верификация: pytest tests/test_community_cli.py3 passed.


Оставшиеся регрессии (R04-R13, R15)

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

  • Проблема: enforce_fetch_interval открывает файл в r, mark_fetch — в r+; оба падают при первом запуске. f.read(encoding="utf-8")encoding задаётся при открытии, а не в read().
  • Статус: Не начато.

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

  • Проблема: configured_sources() исключает disabled из списка кандидатов, но site_sources для истории cooldown строится из полного реестра. Отключение endpoint сбрасывает site cooldown для соседей.
  • Статус: Не начато.

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

  • Проблема: test-production-bootstrap.sh включает proxy community-scheduler, но наследует production-домены, порты 80/443 и реальные URL парсеров.
  • Статус: Не начато.

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

  • Проблема: community_observations фильтрует по fish_name/waterbody_name (названия источника), но frontend передаёт fish/waterbody slugs.
  • Статус: Не начато.

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

  • Проблема: if (unavailable) стоит до запросов, ветка 503/Retry-After/noindex никогда не срабатывает.
  • Статус: Частично исправлено в S01 (добавлен noindex для index/records при unavailable), но unavailable проверяется после присваивания false — нужно подтвердить.

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

  • Проблема: .filter-compact-hidden с display:contents!important ломает label/select компоновку. Без JS период/сортировка недоступны.
  • Статус: Не начато.

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

  • Проблема: limit=20&offset=0 всегда, нет перехода к следующей странице.
  • Статус: Частично исправлено в U03 (добавлен total и кнопка "Показать ещё"), но полная пагинация с сохранением фильтров не реализована.

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

  • Проблема: rate_limited, server_error, timeout не восстанавливают черновик. AbortSignal.timeout использует TimeoutError, а не TypeError.
  • Статус: Частично исправлено в U04 (добавлены сообщения об ошибках, блокировка double submit), но корректная классификация TimeoutError не проверена.

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

  • Проблема: fetch_html делает urlopen до проверки allowlist.
  • Статус: Не начато.

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

  • Проблема: readiness_report добавляет community_scheduler, но failed/stale не влияют на итог ready.
  • Статус: Не начато.

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

  • Проблема: _check_rate_limit принимает первый X-Forwarded-For от любого peer.
  • Статус: Не начато.

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

  • D04: Fallback только для waterbody, рыба требует external ID.
  • D06: "Один игрок не может искусственно поднять уверенность" — формула допускает 72% при 1 игроке.
  • D07: community_review.py присваивает caught_at=published_at.
  • D08: Запрос точки ограничен водоёмом, но остаётся top-100.
  • Статус: Не начато.

Следующие шаги

  1. R03 — Research CLI cooldown: исправить fcntl locking и encoding.
  2. R04 — Разделить configured_sources() и site_cooldown_sources().
  3. R05 — Перевести bootstrap на loopback-порты и локальные домены.
  4. R06 — Исправить community_observations на фильтрацию по fish_slug/waterbody_slug.
  5. R07 — Переместить unavailable проверку после запросов.
  6. R08 — Исправить CSS для .filter-compact-hidden, добавить fallback без JS.
  7. R09 — Полная пагинация с сохранением фильтров.
  8. R10 — Корректная обработка TimeoutError.
  9. R11 — Проверка allowlist до urlopen.
  10. R12 — Влияние community_scheduler на readiness.
  11. R13 — Proxy trust boundary для X-Forwarded-For.
  12. R15 — D04/D06/D07/D08 partial completion.