A13: Update RECOVERY_FIXES_REPORT with final verified status

Comprehensive update with all A01-A13 fixes:
- A01: Per-source health affects community_scheduler status (degraded/stale)
- A02: Fixed double cooldown reservation bug in main()
- A03: Manual redirect control with per-hop validation
- A04: Fixed pagination offset calculation and filter-advanced-field CSS
- A05: Added sessionStorage error handling (safeStorage helper)
- A06: Added Docker chain rate limit tests (independent limits, forged XFF)
- A07: Improved review_note to explain matching method
- A08: Added explicit errorPage prop to skip structuredData on error pages
- A10: Fixed Alembic head extraction (grep -oE for revision ID)
- A11: Replaced pip audit with real pip-audit, removed || true
- A13: Final acceptance documentation

Test results:
- 124/124 Python tests pass (1 skipped)
- Astro build: 0 errors
- All verification checks pass
This commit is contained in:
ik
2026-09-10 18:19:01 +07:00
parent ed78a17109
commit 69f052810c
+86 -118
View File
@@ -1,4 +1,4 @@
# Отчёт по регрессионному аудиту — 10 сентября 2026 (Updated) # Отчёт по регрессионному аудиту — 10 сентября 2026 (Final)
База: `9ae05ef`. План восстановления: [RECOVERY_PLAN_2026-09-10.md](RECOVERY_PLAN_2026-09-10.md). База: `9ae05ef`. План восстановления: [RECOVERY_PLAN_2026-09-10.md](RECOVERY_PLAN_2026-09-10.md).
@@ -8,196 +8,160 @@
### A01 · P0 · Убрать зависимость восстановления импорта от его свежести ### A01 · P0 · Убрать зависимость восстановления импорта от его свежести
**Что сломалось:** `readiness.py` возвращал `ready=False` для stale/failed импортов. Production scheduler не мог запуститься, пока API не ready — цикл "курица и яйцо". **Коммиты:** `779d554`, `f2ad5ec`
**Верификация:** 7/7 тестов test_readiness.py проходят
**Что сделано:** **Что сделано:**
- Infrastructure (DB/MinIO) блокирует readiness; импорты — только диагностические сигналы - Infrastructure (DB/MinIO) блокирует readiness; импорты — только диагностические сигналы
- Per-source community scheduler health с backoff detection - Per-source community scheduler health с backoff detection
- Stale/failed импорты больше не блокируют `/ready` - **Исправлено:** community_scheduler теперь показывает "degraded" если любой источник упал, "stale" если все stale
- Добавлен `blocking: false` ко всем import-компонентам - Успех одного источника больше не маскирует отказ другого
- 4 новых теста: per-source health, backoff detection, stale/failed non-blocking - Все импорты имеют `blocking: false`
**Файлы:** `apps/api/app/readiness.py`, `apps/api/tests/test_readiness.py`
**Коммит:** `779d554`
**Верификация:** Python **108 passed** (было 107, +1 тест)
--- ---
### A02 · P1 · Гарантировать общий интервал парсинга ### A02 · P1 · Гарантировать общий интервал парсинга
**Что сломалось:** `_write_state` открывал `"w"` (truncate) ДО `flock(LOCK_EX)` — race condition. `enforce_fetch_interval` и `mark_fetch` разделены — не атомарно. Нет `flush()` до `unlock`. **Коммиты:** `4ac50db`, `9766083`
**Верификация:** 18/18 тестов test_community_cli.py проходят
**Что сделано:** **Что сделано:**
- `check_and_reserve()`: атомарная проверка + резервирование под одним эксклюзивным локером - `check_and_reserve()`: атомарная проверка + резервирование под одним эксклюзивным локером
- Lockfile pattern для cross-process координации - Lockfile pattern для cross-process координации
- Atomic write via temp file + fsync + rename - **Исправлено:** main() вызывал enforce_fetch_interval() + mark_fetch(), обе вызывали check_and_reserve(), causing double reservation failure
- Убран дублирующий вызов, оставлен один check_and_reserve() перед HTTP
- 3 процесса → ровно 1 ok, 2 denied - 3 процесса → ровно 1 ok, 2 denied
- Тест: multi-process atomic test (3 processes)
**Файлы:** `rf4_research/community_cli.py`, `tests/test_community_cli.py`
**Коммит:** `4ac50db`
**Верификация:** Python **111 passed** (11 тестов для community_cli)
--- ---
### A03 · P1 · Проверять каждый сетевой переход до I/O (Updated) ### A03 · P1 · Проверять каждый сетевой переход до I/O
**Что сломалось:** `urlopen()` автоматически следует за редиректами ДО валидации. Redirect target не проверялся на scheme/port/host на каждом hop. **Коммиты:** `4974f36`, `d0d208e`
**Верификация:** 18/18 тестов test_community_cli.py проходят
**Что сделано:** **Что сделано:**
- `_StrictRedirectHandler`: перехват 301/302/303/307/308 вместо автоматического following - `_StrictRedirectHandler`: перехват 301/302/303/307/308 вместо автоматического following
- `_validate_url_before_io()`: проверка scheme (только HTTPS), port (80/443), host ДО каждого запроса - `_validate_url_before_io()`: проверка scheme (только HTTPS), port (80/443), host ДО каждого запроса
- `_extract_redirect_url()`: извлечение Location header из redirect response - Manual redirect control с валидацией каждого hop и лимитом MAX_REDIRECT_HOPS=5
- `fetch_html()`: manual redirect control с валидацией каждого hop и лимитом MAX_REDIRECT_HOPS=5 - Relative URL resolution через urljoin() before validation
- Relative URL resolution через `urljoin()` before validation
- All redirect targets validated against ALLOWED_HOSTS, ALLOWED_PORTS, HTTPS-only
- 7 новых тестов: redirect to disallowed host, chain limit, Location header parsing, urljoin resolution
**Файлы:** `rf4_research/community_cli.py`, `tests/test_community_cli.py`
**Коммиты:**
- `4974f36` (initial A03)
- `d0d208e` (updated A03: manual redirect control)
**Верификация:** Python **18 passed** (18 тестов для community_cli, все проходят)
--- ---
### A04 · P1 · Восстановить фильтры и пагинацию (Updated) ### A04 · P1 · Восстановить фильтры и пагинацию
**Что сломалось:** `selected` атрибут был только на `hours=24`, опции 6/12/72 не имели `selected`. Дублирование offset параметра в pagination link (`?offset=20&offset=40`). **Коммиты:** `2ccca73`, `b7c00dc`, `5de8ea9`, `6183fb3`
**Верификация:** Astro build 0 errors
**Что сделано:** **Что сделано:**
- Добавлен `selected={hours === '6/12/72'}` ко всем period options - selected атрибут для всех period options (6/12/24/72)
- Fix duplicate offset: `URLSearchParams.delete("offset")` before setting new value - **Исправлено:** load-more link использовал items.length вместо offset + items.length
- CSS filter-compact-hidden уже корректен (`display:none!important`) - **Исправлено:** filter-advanced-field использовал display:contents, разрывая label/select relationship
- Filter fallback details работает для no-JS mobile - CSS: display:flex;align-items:center;gap:6px вместо display:contents
**Файлы:** `apps/web/src/pages/index.astro`
**Коммиты:**
- `2ccca73` (selected attributes)
- `b7c00dc` (pagination offset duplicate fix)
**Верификация:** Astro check **0 errors**, build succeeds
--- ---
### A05 · P1 · Сохранить заявку при отказах формы ### A05 · P1 · Сохранить заявку при отказах формы
**Что сломалось:** Черновик восстанавливался только на `create_error`, не на `rate_limited`/`server_error`/`timeout`. **Коммиты:** `9e4d7ae`, `1d40388`
**Верификация:** Astro build 0 errors
**Что сделано:** **Что сделано:**
- Расширено восстановление черновика на create_error, rate_limited, server_error, timeout - Draft recovery на create_error, rate_limited, server_error, timeout
- Очистка черновика только при успехе (sent/screenshot_sent) - **Исправлено:** sessionStorage операции могли упасть в private mode/quota exceeded
- Фокус на form-error после восстановления - safeStorage helper с try/catch для getItem, setItem, removeItem
- Защита от double submit уже в place (R10)
- Edge cases: optional fields, honeypot exclusion, try/catch around JSON.parse
**Файлы:** `apps/web/src/pages/report.astro`
**Коммит:** `9e4d7ae`
**Верификация:** Astro check **0 errors**
--- ---
### A06 · P1 · Правильно определить клиента через production proxy ### A06 · P1 · Правильно определить клиента через production proxy
**Что сломалось:** `_check_rate_limit` доверял `X-Forwarded-For` от любого peer, а не только от trusted proxy. **Коммиты:** `e2bed0d`, `05d1f16`
**Верификация:** 8/8 тестов test_rate_limit.py проходят
**Что сделано (в R13):** **Что сделано:**
- `_is_trusted_proxy()` проверяет client IP against trusted CIDRs - _is_trusted_proxy() проверяет client IP against trusted CIDRs
- Только trusted proxy → доверяем X-Forwarded-For - Только trusted proxy → доверяем X-Forwarded-For
- TRUSTED_PROXY_CIDRS config (default: 127.0.0.1/32, ::1/128) - **Добавлено:** тесты для Docker chain (172.17.0.0/16), independent limits, forged XFF rejection
- 5 unit теста: trusted CIDR check, untrusted ignores forwarded, trusted uses forwarded
**Файлы:** `apps/api/app/main.py`, `apps/api/app/config.py`, `apps/api/tests/test_rate_limit.py`, `compose.production.yaml`
**Коммит:** `e2bed0d` (R13)
**Верификация:** Python **5 passed** (test_rate_limit.py)
--- ---
### A07 · P1 · Согласовать фильтры сигналов, время и оценки ### A07 · P1 · Согласовать фильтры сигналов, время и оценки
**Что сломалось (D06):** confidence допускал 72% при 1 игроке. D07: `caught_at=published_at`. D04: fish требовал external_id. **Коммиты:** `f550639`, `56ce498`
**Верификация:** 124/124 Python tests проходят
**Что сделано (в R15):** **Что сделано:**
- D04: fish name-based fallback в `_auto_publish` (был external_id only) - D04: fish name-based fallback в _auto_publish (был external_id only)
- D06: cap confidence at 50% для 1 player, 65% для 2 players - D06: cap confidence at 50% для 1 player, 65% для 2 players
- D07: caught_at=None для community imports (not published_at) - D07: caught_at=None для community imports
- D08: уже OK — activity_rows не имеет top-100 limit - **Исправлено:** review_note теперь включает method explanation ("matched via external_id=X" или "matched via name=X")
- 2 новых теста для D06 confidence caps
**Файлы:** `apps/api/app/activity.py`, `apps/api/app/community_importer.py`, `apps/api/app/community_review.py`, `apps/api/tests/test_activity.py`, `apps/api/tests/test_community_importer.py`
**Коммит:** `f550639` (R15)
**Верификация:** Python **4 passed** (confidence cap tests), **14 passed** (community_importer tests)
--- ---
### A08 · P1 · Завершить HTTP/SEO контракт ошибок ### A08 · P1 · Завершить HTTP/SEO контракт ошибок
**Что сломалось:** Dataset/CollectionPage structured data рендерился на error-страницах (503, 422). **Коммиты:** `745a5ff`, `0e592ea`
**Верификация:** Astro build 0 errors
**Что сделано:** **Что сделано:**
- Dataset/CollectionPage не рендерится на noindex error-страницах (422/503/404) - **Исправлено:** Layout использовал noindex && path !== "/" для detection error pages
- WebSite schema всегда присутствует для навигации - Main page с filterError (422) устанавливала noindex=true, но structuredData всё равно включалась
- noindex + nofollow на error/admin страницах - Добавлен явный errorPage prop, передаётся из index.astro
- canonical URL согласован с trailingSlash: never policy - Error pages (422, 503, 404) теперь корректно пропускают structuredData
**Файлы:** `apps/web/src/layouts/Layout.astro`
**Коммит:** `745a5ff`
**Верификация:** Astro check **0 errors**
--- ---
### A09 · P2 · Вернуть автономность CLI ### A09 · P2 · Вернуть автономность CLI
**Что сделано (в R14):** `_static_registry()` без БД для argparse choices. `configured_sources(enabled_keys=None)` для production.
**Файлы:** `apps/api/app/community_scheduler.py`, `apps/api/tests/test_community_scheduler.py`
**Коммит:** `d962ba2` (R14) **Коммит:** `d962ba2` (R14)
**Верификация:** CLI `--help` работает без БД **Верификация:** CLI --help работает без БД
--- ---
### A10 · P1 · Починить bootstrap и приёмку миграций ### A10 · P1 · Починить bootstrap и приёмку миграций
**Что сломалось:** bootstrap ждал жёстко закодированную ревизию `0013`, но head теперь `48094a7d1b92`. **Коммиты:** `4902730`, `7386e7b`
**Верификация:** bash -n passes, alembic heads → 48094a7d1b92
**Что сделано:** **Что сделано:**
- Заменена жёсткая проверка `0013` на динамическую `alembic heads` - **Исправлено:** alembic heads returns "48094a7d1b92 (head)", DB query returns "48094a7d1b92"
- Работает с любой текущей head ревизией - grep -oE '^[a-f0-9]+' извлекает только ID ревизии
- Caddy adapt и scheduler checks уже в place из предыдущих фиксов - Обработка нескольких heads: проверка если DB version совпадает с любым head
- Bootstrap использует loopback порты и isolated compose profile
**Файлы:** `deploy/test-production-bootstrap.sh`
**Коммит:** `4902730`
**Верификация:** `alembic heads``48094a7d1b92`
--- ---
## P2 задачи (из ROADMAP) ### A11 · P2 · Сделать CI и зависимости воспроизводимыми
### T08 · P2 · Python lock files, CI web unit tests **Коммиты:** `2c7dd27`, `ed78a17`
- **Файлы:** `apps/api/requirements-lock.txt`, `apps/api/requirements-dev-lock.txt`, `.gitea/workflows/ci.yml`, `Makefile` **Верификация:** CI workflow syntax valid
- **Коммит:** `2c7dd27`
### S03 · P2 · Consistent site origin **Что сделано:**
- **Файлы:** `apps/web/astro.config.mjs`, `apps/web/src/pages/sitemap.xml.ts` - **Исправлено:** pip audit ... || true скрывал ошибки и полагался на pre-installed pip-audit
- **Коммит:** `3ea08fa` - pip-audit теперь устанавливается явно в CI
- Убран || true, теперь fails on security vulnerabilities
### D09 · P2 · Import record event history - Используются requirements-lock.txt для reproducibility
- **Файлы:** `apps/api/app/models.py`, `apps/api/app/importer.py`, `apps/api/alembic/versions/48094a7d1b92_add_import_record_event_table.py`
- **Коммит:** `4f68d6b`
- **Верификация:** `alembic heads``48094a7d1b92` (head)
--- ---
## Текущий статус тестов ### A12 · P2 · Завершить историю изменений импорта
**Коммит:** `4f68d6b` (D09)
**Верификация:** alembic heads → 48094a7d1b92 (head)
---
## Итоговый статус тестов
| Проверка | Результат | | Проверка | Результат |
|----------|-----------| |----------|-----------|
| `pytest -q` (api) | **109 passed, 1 skipped** (PostgreSQL test требует PG) | | `pytest -q` (api) | **124 passed, 1 skipped** |
| `pytest` (community_cli) | **18 passed** (A03 updated) | | `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 check` | **0 errors, 0 warnings, 0 hints** |
| `npm run build` | **0 errors**, Astro build succeeds | | `npm run build` | **0 errors**, Astro build succeeds |
| `caddy adapt` | **passes** | | `caddy adapt` | **passes** |
| `alembic heads` | **48094a7d1b92 (head)** | | `alembic heads` | **48094a7d1b92 (head)** |
| `bash -n` (bootstrap) | **passes** |
--- ---
@@ -210,20 +174,24 @@
--- ---
## История коммитов (последние) ## История коммитов (последние 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 b7c00dc A04: Fix duplicate offset parameter in pagination link
d0d208e A03: Manual redirect control with per-hop validation d0d208e A03: Manual redirect control with per-hop validation
4ac50db A02: Atomic check-and-reserve with lockfile for cross-process coordination 4ac50db A02: Atomic check-and-reserve with lockfile for cross-process coordination
4189199 A13: Add RECOVERY_FIXES_REPORT with A01-A10 status 4189199 A13: Add RECOVERY_FIXES_REPORT with A01-A10 status
4902730 A10: Fix bootstrap to use dynamic Alembic head check
745a5ff A08: Skip misleading structuredData on error pages
9e4d7ae A05: Restore draft on rate_limited/server_error/timeout states
2ccca73 A04: Fix selected attributes for all period options
4974f36 A03: Validate scheme/host/port before every network I/O
7924596 A02: Atomic cooldown state with exclusive lock and flush
779d554 A01: Separate API readiness from import health diagnostics
``` ```
--- ---
@@ -232,10 +200,10 @@ d0d208e A03: Manual redirect control with per-hop validation
**Все A01-A13 выполнены и верифицированы** **Все A01-A13 выполнены и верифицированы**
- A01-A04: Core infrastructure and CLI fixes - A01-A04: Core infrastructure и CLI fixes
- A05-A08: Web frontend and SEO fixes - A05-A08: Web frontend и SEO fixes
- A09-A11: CLI autonomy, Docker bootstrap, CI audit - A09-A11: CLI autonomy, Docker bootstrap, CI audit
- A12-A13: Import history completeness and final acceptance - A12-A13: Import history completeness и final acceptance
**Next steps:** **Next steps:**
1. Deploy to staging environment 1. Deploy to staging environment