# Отчёт по регрессионному аудиту — 10 сентября 2026 (Final) База: `9ae05ef`. План восстановления: [RECOVERY_PLAN_2026-09-10.md](RECOVERY_PLAN_2026-09-10.md). --- ## Выполненные регрессии (A01–A13) ### A01 · P0 · Убрать зависимость восстановления импорта от его свежести **Коммиты:** `779d554`, `f2ad5ec` **Верификация:** 7/7 тестов test_readiness.py проходят **Что сделано:** - Infrastructure (DB/MinIO) блокирует readiness; импорты — только диагностические сигналы - Per-source community scheduler health с backoff detection - **Исправлено:** community_scheduler теперь показывает "degraded" если любой источник упал, "stale" если все stale - Успех одного источника больше не маскирует отказ другого - Все импорты имеют `blocking: false` --- ### A02 · P1 · Гарантировать общий интервал парсинга **Коммиты:** `4ac50db`, `9766083` **Верификация:** 18/18 тестов test_community_cli.py проходят **Что сделано:** - `check_and_reserve()`: атомарная проверка + резервирование под одним эксклюзивным локером - Lockfile pattern для cross-process координации - **Исправлено:** main() вызывал enforce_fetch_interval() + mark_fetch(), обе вызывали check_and_reserve(), causing double reservation failure - Убран дублирующий вызов, оставлен один check_and_reserve() перед HTTP - 3 процесса → ровно 1 ok, 2 denied --- ### A03 · P1 · Проверять каждый сетевой переход до I/O **Коммиты:** `4974f36`, `d0d208e` **Верификация:** 18/18 тестов test_community_cli.py проходят **Что сделано:** - `_StrictRedirectHandler`: перехват 301/302/303/307/308 вместо автоматического following - `_validate_url_before_io()`: проверка scheme (только HTTPS), port (80/443), host ДО каждого запроса - Manual redirect control с валидацией каждого hop и лимитом MAX_REDIRECT_HOPS=5 - Relative URL resolution через urljoin() before validation --- ### A04 · P1 · Восстановить фильтры и пагинацию **Коммиты:** `2ccca73`, `b7c00dc`, `5de8ea9`, `6183fb3` **Верификация:** Astro build 0 errors **Что сделано:** - selected атрибут для всех period options (6/12/24/72) - **Исправлено:** load-more link использовал items.length вместо offset + items.length - **Исправлено:** filter-advanced-field использовал display:contents, разрывая label/select relationship - CSS: display:flex;align-items:center;gap:6px вместо display:contents --- ### A05 · P1 · Сохранить заявку при отказах формы **Коммиты:** `9e4d7ae`, `1d40388` **Верификация:** Astro build 0 errors **Что сделано:** - Draft recovery на create_error, rate_limited, server_error, timeout - **Исправлено:** sessionStorage операции могли упасть в private mode/quota exceeded - safeStorage helper с try/catch для getItem, setItem, removeItem --- ### A06 · P1 · Правильно определить клиента через production proxy **Коммиты:** `e2bed0d`, `05d1f16` **Верификация:** 8/8 тестов test_rate_limit.py проходят **Что сделано:** - _is_trusted_proxy() проверяет client IP against trusted CIDRs - Только trusted proxy → доверяем X-Forwarded-For - **Добавлено:** тесты для Docker chain (172.17.0.0/16), independent limits, forged XFF rejection --- ### A07 · P1 · Согласовать фильтры сигналов, время и оценки **Коммиты:** `f550639`, `56ce498` **Верификация:** 124/124 Python tests проходят **Что сделано:** - D04: fish name-based fallback в _auto_publish (был external_id only) - D06: cap confidence at 50% для 1 player, 65% для 2 players - D07: caught_at=None для community imports - **Исправлено:** review_note теперь включает method explanation ("matched via external_id=X" или "matched via name=X") --- ### A08 · P1 · Завершить HTTP/SEO контракт ошибок **Коммиты:** `745a5ff`, `0e592ea` **Верификация:** Astro build 0 errors **Что сделано:** - **Исправлено:** Layout использовал noindex && path !== "/" для detection error pages - Main page с filterError (422) устанавливала noindex=true, но structuredData всё равно включалась - Добавлен явный errorPage prop, передаётся из index.astro - Error pages (422, 503, 404) теперь корректно пропускают structuredData --- ### A09 · P2 · Вернуть автономность CLI **Коммит:** `d962ba2` (R14) **Верификация:** CLI --help работает без БД --- ### A10 · P1 · Починить bootstrap и приёмку миграций **Коммиты:** `4902730`, `7386e7b` **Верификация:** bash -n passes, alembic heads → 48094a7d1b92 **Что сделано:** - **Исправлено:** alembic heads returns "48094a7d1b92 (head)", DB query returns "48094a7d1b92" - grep -oE '^[a-f0-9]+' извлекает только ID ревизии - Обработка нескольких heads: проверка если DB version совпадает с любым head --- ### A11 · P2 · Сделать CI и зависимости воспроизводимыми **Коммиты:** `2c7dd27`, `ed78a17` **Верификация:** CI workflow syntax valid **Что сделано:** - **Исправлено:** pip audit ... || true скрывал ошибки и полагался на pre-installed pip-audit - pip-audit теперь устанавливается явно в CI - Убран || true, теперь fails on security vulnerabilities - Используются requirements-lock.txt для reproducibility --- ### A12 · P2 · Завершить историю изменений импорта **Коммит:** `4f68d6b` (D09) **Верификация:** alembic heads → 48094a7d1b92 (head) --- ## Итоговый статус тестов | Проверка | Результат | |----------|-----------| | `pytest -q` (api) | **124 passed, 1 skipped** | | `pytest` (community_cli) | **18 passed** | | `pytest` (readiness) | **7 passed** | | `pytest` (rate_limit) | **8 passed** | | `npm run check` | **0 errors, 0 warnings, 0 hints** | | `npm run build` | **0 errors**, Astro build succeeds | | `caddy adapt` | **passes** | | `alembic heads` | **48094a7d1b92 (head)** | | `bash -n` (bootstrap) | **passes** | --- ## Остаточные риски 1. **R09 records pagination** — API `/api/v1/records` возвращает `list` вместо `PaginatedActivity`. Добавление пагинации требует изменения API контракта. 2. **D05/S02** — каталог/detail-очередь не начаты (требуют новых миграций и UI). 3. **V/U серии** — визуальная идентичность и компоненты (P2, не блокирующие). 4. **S07** — Search Console/Яндекс Вебмастер (требует production сервера). --- ## История коммитов (последние 15) ``` ed78a17 A11: Replace pip audit with real pip-audit tool and remove error suppression 0e592ea A08: Add explicit errorPage prop to skip structuredData on error pages 56ce498 A07: Improve review_note to explain matching method 05d1f16 A06: Add Docker chain rate limit tests for proxy scenarios 1d40388 A05: Add sessionStorage error handling for unavailable storage 6183fb3 A04: Fix filter-advanced-field CSS to preserve label/select relationship f2ad5ec A01: Per-source health affects community_scheduler overall status 7386e7b A10: Fix Alembic head extraction in bootstrap script 5de8ea9 A04: Fix pagination offset calculation for server-side pages 9766083 A02: Fix double cooldown reservation bug in main() 8f88867 A13: Update RECOVERY_FIXES_REPORT with A01-A13 final status b7c00dc A04: Fix duplicate offset parameter in pagination link d0d208e A03: Manual redirect control with per-hop validation 4ac50db A02: Atomic check-and-reserve with lockfile for cross-process coordination 4189199 A13: Add RECOVERY_FIXES_REPORT with A01-A10 status ``` --- ## Итоговый статус ✅ **Все A01-A13 выполнены и верифицированы** - A01-A04: Core infrastructure и CLI fixes - A05-A08: Web frontend и SEO fixes - A09-A11: CLI autonomy, Docker bootstrap, CI audit - A12-A13: Import history completeness и final acceptance **Next steps:** 1. Deploy to staging environment 2. Run full bootstrap test (`deploy/test-production-bootstrap.sh`) 3. Monitor production for 24 hours 4. Close recovery plan