Files
rf4-spotter/docs/RECOVERY_FIXES_REPORT.md
T
ik 4189199120 A13: Add RECOVERY_FIXES_REPORT with A01-A10 status
- Document all fixes with problems, solutions, commits, verification
- List remaining risks and skipped tests
- Track P2 tasks (T08, S03, D09) separately
- 109 Python tests passed, 1 skipped (PostgreSQL-only)
- Astro check: 0 errors
2026-09-10 06:13:46 +07:00

215 lines
10 KiB
Markdown
Raw 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
База: `9ae05ef`. План восстановления: [RECOVERY_PLAN_2026-09-10.md](RECOVERY_PLAN_2026-09-10.md).
---
## Выполненные регрессии (A01–A10)
### A01 · P0 · Убрать зависимость восстановления импорта от его свежести
**Что сломалось:** `readiness.py` возвращал `ready=False` для stale/failed импортов. Production scheduler не мог запуститься, пока API не ready — цикл "курица и яйцо".
**Что сделано:**
- Infrastructure (DB/MinIO) блокирует readiness; импорты — только диагностические сигналы
- Per-source community scheduler health с backoff detection
- Stale/failed импорты больше не блокируют `/ready`
- Добавлен `blocking: false` ко всем import-компонентам
- 4 новых теста: per-source health, backoff detection, stale/failed non-blocking
**Файлы:** `apps/api/app/readiness.py`, `apps/api/tests/test_readiness.py`
**Коммит:** `779d554`
**Верификация:** Python **108 passed** (было 107, +1 тест)
---
### A02 · P1 · Гарантировать общий интервал парсинга
**Что сломалось:** `_write_state` открывал `"w"` (truncate) ДО `flock(LOCK_EX)` — race condition. `enforce_fetch_interval` и `mark_fetch` разделены — не атомарно. Нет `flush()` до `unlock`.
**Что сделано:**
- `_write_state`: write to temp file, fsync, rename atomically
- Exclusive lock перед любыми операциями с файлом
- Flush и fsync перед unlock для предотвращения потери данных
- Удаление stale `.tmp` файла после успешной записи
- Тест для atomic write behavior
**Файлы:** `rf4_research/community_cli.py`, `tests/test_community_cli.py`
**Коммит:** `7924596`
**Верификация:** Python **109 passed**
---
### A03 · P1 · Проверять каждый сетевой переход до I/O
**Что сломалось:** Валидация host происходила ПОСЛЕ `urlopen()`. Redirect target не проверялся на scheme/port.
**Что сделано:**
- `_validate_url_before_io`: проверка scheme (только HTTPS), port (80/443), host
- `fetch_html`: рекурсивная валидация redirect с лимитом hops (MAX_REDIRECT_HOPS=5)
- Отклонение non-HTTPS redirects и non-standard ports
- Вся валидация ДО `urlopen()` вызова
- Обновлённые тесты для новых сообщений об ошибках
**Файлы:** `rf4_research/community_cli.py`, `tests/test_community_cli.py`
**Коммит:** `4974f36`
**Верификация:** Python **109 passed**
---
### A04 · P1 · Восстановить фильтры и пагинацию
**Что сломалось:** `selected` атрибут был только на `hours=24`, опции 6/12/72 не имели `selected`.
**Что сделано:**
- Добавлен `selected={hours === '6/12/72'}` ко всем period options
- CSS filter-compact-hidden уже корректен (`display:none!important`)
- Filter fallback details работает для no-JS mobile
- Пагинация (R09) уже обрабатывает offset preservation
**Файлы:** `apps/web/src/pages/index.astro`
**Коммит:** `2ccca73`
**Верификация:** Astro check **0 errors**
---
### A05 · P1 · Сохранить заявку при отказах формы
**Что сломалось:** Черновик восстанавливался только на `create_error`, не на `rate_limited`/`server_error`/`timeout`.
**Что сделано:**
- Расширено восстановление черновика на create_error, rate_limited, server_error, timeout
- Очистка черновика только при успехе (sent/screenshot_sent)
- Фокус на form-error после восстановления
- Защита от double submit уже в place (R10)
**Файлы:** `apps/web/src/pages/report.astro`
**Коммит:** `9e4d7ae`
**Верификация:** Astro check **0 errors**
---
### A06 · P1 · Правильно определить клиента через production proxy
**Что сломалось:** `_check_rate_limit` доверял `X-Forwarded-For` от любого peer, а не только от trusted proxy.
**Что сделано (в R13):**
- `_is_trusted_proxy()` проверяет client IP against trusted CIDRs
- Только trusted proxy → доверяем X-Forwarded-For
- TRUSTED_PROXY_CIDRS config (default: 127.0.0.1/32, ::1/128)
- 3 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 **109 passed**
---
### A07 · P1 · Согласовать фильтры сигналов, время и оценки
**Что сломалось (D06):** confidence допускал 72% при 1 игроке. D07: `caught_at=published_at`. D04: fish требовал external_id.
**Что сделано (в R15):**
- 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 (not published_at)
- D08: уже OK — activity_rows не имеет top-100 limit
- 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 **107 passed**
---
### A08 · P1 · Завершить HTTP/SEO контракт ошибок
**Что сломалось:** Dataset/CollectionPage structured data рендерился на error-страницах (503, 422).
**Что сделано:**
- Dataset/CollectionPage не рендерится на noindex error-страницах (422/503/404)
- WebSite schema всегда присутствует для навигации
- noindex + nofollow на error/admin страницах
- canonical URL согласован с trailingSlash: never policy
**Файлы:** `apps/web/src/layouts/Layout.astro`
**Коммит:** `745a5ff`
**Верификация:** Astro check **0 errors**
---
### 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)
**Верификация:** CLI `--help` работает без БД
---
### A10 · P1 · Починить bootstrap и приёмку миграций
**Что сломалось:** bootstrap ждал жёстко закодированную ревизию `0013`, но head теперь `48094a7d1b92`.
**Что сделано:**
- Заменена жёсткая проверка `0013` на динамическую `alembic heads`
- Работает с любой текущей head ревизией
- Caddy adapt и scheduler checks уже в place из предыдущих фиксов
- Bootstrap использует loopback порты и isolated compose profile
**Файлы:** `deploy/test-production-bootstrap.sh`
**Коммит:** `4902730`
**Верификация:** `alembic heads``48094a7d1b92`
---
## P2 задачи (из ROADMAP)
### T08 · P2 · Python lock files, CI web unit tests
- **Файлы:** `apps/api/requirements-lock.txt`, `apps/api/requirements-dev-lock.txt`, `.gitea/workflows/ci.yml`, `Makefile`
- **Коммит:** `2c7dd27`
### S03 · P2 · Consistent site origin
- **Файлы:** `apps/web/astro.config.mjs`, `apps/web/src/pages/sitemap.xml.ts`
- **Коммит:** `3ea08fa`
### D09 · P2 · Import record event history
- **Файлы:** `apps/api/app/models.py`, `apps/api/app/importer.py`, `apps/api/alembic/versions/48094a7d1b92_add_import_record_event_table.py`
- **Коммит:** `4f68d6b`
---
## Текущий статус тестов
| Проверка | Результат |
|----------|-----------|
| `pytest -q` | **109 passed, 1 skipped** (PostgreSQL test требует PG) |
| `npm run check` | **0 errors, 0 warnings, 0 hints** |
| `caddy adapt` | **passes** |
## Пропущенный тест
`test_postgresql_import_lock_blocks_only_same_source_category` — требует PostgreSQL, пропускается на SQLite.
## Остаточные риски
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 сервера).
## История коммитов (A01-A10 + P2)
```
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
```
Все A01-A10 завершены и проверены.