15 KiB
Отчёт по регрессионному аудиту — 9 сентября 2026
Архивный промежуточный отчёт. Его статусы и раздел «Следующие шаги» относятся только к базе
9ae05efи не являются текущим backlog. Регрессии закрыты последующим пакетом восстановления; актуальный итог находится в RECOVERY_FIXES_REPORT.md, текущие задачи — только в ROADMAP.md. Пагинация R09 дополнительно усилена 15 сентября полноценными серверными previous/next-страницами.
База: 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].astro—items.mapна объекте → TypeErrorwaterbodies/[slug].astro—items.mapна объекте → TypeErrorwaterbodies/[slug]/[fish].astro—items.lengthна объекте → ложное пустое состояниеspots/[id].astro—activityRows.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.
R08 · P1 · Фильтры UI не доведены до responsive-состояния
Что сломалось: .filter-compact-hidden использовал display:contents!important, который ломал label/select компоновку (текст и select становились отдельными grid items). Без JS период/сортировка были недоступны.
Почему так получилось: В коммите b310423 (U03) добавлен JS-класс filter-compact-hidden для скрытия period/sort на мобильных, но CSS использовал display:contents вместо display:none. Это ломало grid-компоновку.
Что сделано:
.filter-compact-hiddenизменён наdisplay:none!important.- Добавлен
<details class="filter-advanced-fallback">как fallback для period/sort на мобильных без JS. - CSS для
.filter-advanced-fallbackпоказывает<details>на мобильных, скрывает на desktop. - JS по-прежнему управляет скрытием через класс, но без JS
<details>раскрывается по клику.
Файлы: apps/web/src/pages/index.astro, apps/web/src/styles/global.css
Коммит: 38bb871
Верификация: Astro check 0 errors.
Текущий статус тестов
| Проверка | Результат |
|---|---|
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.py — 3 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/waterbodyslugs. - Статус: Не начато.
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.
- Статус: Не начато.
Исторические следующие шаги (закрыты последующим recovery-пакетом)
- R03 — Research CLI cooldown: исправить
fcntllocking иencoding. - R04 — Разделить
configured_sources()иsite_cooldown_sources(). - R05 — Перевести bootstrap на loopback-порты и локальные домены.
- R06 — Исправить
community_observationsна фильтрацию поfish_slug/waterbody_slug. - R07 — Переместить
unavailableпроверку после запросов. - R08 — Исправить CSS для
.filter-compact-hidden, добавить fallback без JS. - R09 — Полная пагинация с сохранением фильтров.
- R10 — Корректная обработка
TimeoutError. - R11 — Проверка allowlist до
urlopen. - R12 — Влияние
community_schedulerна readiness. - R13 — Proxy trust boundary для X-Forwarded-For.
- R15 — D04/D06/D07/D08 partial completion.