250 lines
13 KiB
Markdown
250 lines
13 KiB
Markdown
# Отчёт по регрессионному аудиту — 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 сентября
|