Files
rf4-spotter/docs/RECOVERY_FIXES_REPORT.md
ik 45b73ec54a
CI / backend-and-migrations (push) Canceled after 0s
CI / astro-build (push) Canceled after 0s
CI / dependency-audit (push) Canceled after 0s
CI / compose-e2e (push) Canceled after 0s
docs: refresh roadmap after recovery acceptance
2026-09-11 08:06:40 +07:00

250 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Отчёт по регрессионному аудиту — 10 сентября 2026 (Final Verified)
> **Закрыто повторной приёмкой 11 сентября 2026.** После исправлений, перечисленных ниже, дополнительно проверены: полный Python suite — `130 passed, 1 skipped`; Astro check — `0 errors / 0 warnings / 0 hints`; API suite — `20 passed`; чистый production bootstrap — успешно, включая Caddy, scheduler validation, Alembic `20260910_recovery` и Playwright-сценарий. Актуальная очередь перенесена в [ROADMAP.md](ROADMAP.md). Более низкие числа тестов, старый head и перечни «остатков» ниже описывают промежуточный снимок, а не текущее состояние.
База: `9ae05ef`. План восстановления: [RECOVERY_PLAN_2026-09-10.md](RECOVERY_PLAN_2026-09-10.md).
---
## Выполненные регрессии (A01–A13)
### A01 · P0 · Убрать зависимость восстановления импорта от его свежести
**Коммиты:** `779d554`, `f2ad5ec`, `883e63a`
**Верификация:** 7/7 тестов test_readiness.py проходят
**Что сделано:**
- Infrastructure (DB/MinIO) блокирует readiness; импорты — только диагностические сигналы
- Per-source community scheduler health с backoff detection
- **Исправлено:** has_any_success позволял одному здоровому источнику дать общий "ready" при другом stale — masking failures
- **Исправлено:** Added has_any_stale and has_any_running tracking
- Overall status is "degraded" if ANY source is failed/stale/running
- Overall status is "ready" ONLY when ALL enabled sources are healthy
- readiness (ready flag) still NOT blocked by import health (A01 requirement)
---
### 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`, `2a0c7b2`
**Верификация:** 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
- **Исправлено:** pagination condition items.length < totalItems always true for partial last page
- CSS: display:flex;align-items:center;gap:6px вместо display:contents
- Pagination: offset + items.length < totalItems для корректного определения последней страницы
---
### 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`, `e2223c6`
**Верификация:** 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")
- **Исправлено:** _auto_publish early return observation.fish_external_id is None блокировал name fallback
- Auto-matched review_note теперь описывает реальный метод: "Auto-matched: fish via external_id/name, waterbody via external_id/name"
---
### A08 · P1 · Завершить HTTP/SEO контракт ошибок
**Коммиты:** `745a5ff`, `0e592ea`, `e996c6d`
**Верификация:** Astro build 0 errors
**Что сделано:**
- **Исправлено:** Layout использовал noindex && path !== "/" для detection error pages
- Main page с filterError (422) устанавливала noindex=true, но structuredData всё равно включалась
- Добавлен явный errorPage prop, передаётся из страниц
- **Исправлено:** index.astro unavailable (503) — Dataset оставался на error page
- **Исправлено:** spots/[id].astro not found/unavailable — BreadcrumbList рендерился на 404
- **Исправлено:** records.astro unavailable (503) — no structuredData but should be explicit
- Error pages (422, 503, 404) теперь корректно пропускают structuredData
---
### A09 · P2 · Вернуть автономность CLI
**Коммиты:** `d962ba2`, `ec3a1ca`
**Верификация:** CLI --help работает без БД
**Что сделано:**
- **Исправлено:** CLI choices=configured_sources() открывал БД при импорте
- Добавлен STATIC_SOURCE_CHOICES список с известными ключами источников
- argparse использует static choices — no DB required for --help
- fetch-community command now checks enabled status at runtime
- Disabled sources return error: 'source X is disabled or not configured'
---
### 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** |
| CLI --help | **works without DB** |
---
## Исторический список рисков до повторных исправлений
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 сервера).
5. **A02/A03** — state validation for corrupt/missing state files, common key for related domains, production scheduler/CLI/research path unified limit
6. **A05** — server-side idempotency for double submit protection
7. **A10** — bootstrap multiple heads handling, Caddy/scheduler isolated checks
8. **A11** — Docker lock files consistency
9. **A12** — ImportRecordEvent meaningful versions with provenance
---
## История коммитов (последние 20)
```
ec3a1ca A09: Use static registry for CLI choices, check enabled at runtime
e2223c6 A07: Fix _auto_publish to allow fish name fallback without external_id
883e63a A01: Fix scheduler aggregation to not mask stale/failed sources
e996c6d A08: Pass errorPage on all pages with potential errors
2a0c7b2 A04: Fix pagination 'load more' condition for server-side pages
69f0528 A13: Update RECOVERY_FIXES_REPORT with final verified status
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
```
---
## Исторический итог исходного отчёта
**Основные A01-A13 выполнены и верифицированы**
- A01-A04: Core infrastructure и CLI fixes
- A05-A08: Web frontend и SEO fixes
- A09-A11: CLI autonomy, Docker bootstrap, CI audit
- A12: Import history (D09 migration at head)
**Остаток на момент исходного отчёта (впоследствии закрыт):**
- A02/A03: State validation, common key for domains, unified limit
- A05: Server-side idempotency
- A10: Bootstrap multiple heads, Caddy/scheduler checks
- A11: Docker lock files
- A12: Meaningful versions with provenance
- A13: Final documentation update
**Исторические next steps (выполнены локально, кроме реального staging):**
1. Deploy to staging environment
2. Run full bootstrap test (`deploy/test-production-bootstrap.sh`) — выполнено 11 сентября
3. Monitor production for 24 hours
4. Close recovery plan — выполнено 11 сентября