24 KiB
Повторный аудит после исправлений — 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()при построении argparsechoices, до разбора команды, включая--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 → R06–R10 → R12–R15. Не нужно перезапускать Docker после каждой правки.
- Восстановить deploy и контракт страниц. Адресные offline-тесты + один Caddy adapt.
- Устранить риски интервала и произвольных HTTP-запросов. Bootstrap перевести на fixtures до его следующего запуска.
- Закрыть пользовательские/SEO-регрессии и правдивость данных. Затем единая SSR/браузерная матрица с наполненными страницами.
- Восстановить зелёный Python suite, расширить web tests проверками потребителей API и добавить их в CI. Не подменять исправление регрессий ослаблением assertions.
- Один изолированный production acceptance: Caddy → Astro → API, форма → модерация → публикация с обязательным источником, неполные сигналы и мониторинг. Без живого scraping.
- Продолжить незавершённый предыдущий аудит: D05/S02 (полный каталог, lookup по slug, очередь detail URL); V01–V04/U05–U07 (идентичность, токены, плотность карточек, координаты, иконки, визуальная приёмка); S03–S06 (canonical, полезные постоянные страницы, sitemap, performance); T08–T10/D09–D10 (воспроизводимость, нагрузка, release/migration, ограниченные права хранения, история импорта/backoff).
- После сервера: внешний launch checklist, S07; после обратной связи — V05. Astro/FastAPI/PostgreSQL сохраняются: смена стека эти дефекты не исправит.
Каждый пункт закрывается только с доказательством своей приёмки, обновлением README при изменении поведения и отдельным коммитом. Старые отметки о завершении относятся к прежним ревизиям, а не автоматически к текущему HEAD.