fix(forecasting): SAVEPOINT-isolate §22 db.execute failures + close RELEASE-trap (#2464) #2466
No reviewers
Labels
No labels
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
Fable 5 ревью
feedback/max
generative
GG-форсайт
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
вторичка
ИРД
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#2466
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "worktree-agent-a32af8863ce3aefd0"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Что
Волна 1 эпика #2464 — подкластер A: session-poisoning в §22-прогнозе (forecasting) + второй порядок этой же ловушки.
Баг-класс
§22-отчёт делит одну SQLAlchemy-сессию между всеми builder'ами. Несколько ловили сбой
db.execute, логировали и возвращали фолбэк ({}/[]/None) без rollback → Postgres оставляет транзакциюaborted→ каждый следующий builder на той же сессии падаетcurrent transaction is aborted, молча обнуляя все последующие слои (вопреки докстрингам «сбой одного ряда не валит остальные»).Фикс (SAVEPOINT
with db.begin_nested():на самом swallow-сайте)orchestrator.py_safe_call(9 §9.x-слоёв)fn()(потребовало явныйdb: Session-параметр + правку 9 call-site'ов)macro_series.py_query_key_rate_monthly/_query_inflation_monthly/_query_mortgage_monthlyspecial_indices.py_run(6 §25-индексов)builder()sales_series.py_query_source_a/_query_source_bВторой порядок — ловушка RELEASE SAVEPOINT
Внешний savepoint не спасает, если внутренний хелпер, вызванный внутри
with db.begin_nested(): return builder(), глотает свою ошибкуdb.executeбез своего savepoint: транзакция уходит в aborted, внешнийwithна нормальном выходе делаетRELEASE SAVEPOINT, а RELEASE в aborted-состоянии сам падает (Postgres разрешает только ROLLBACK / ROLLBACK TO SAVEPOINT) → внешнийexceptловит, но откат так и не происходит → poisoning каскадит. Воспроизведено на реальном Postgres 16. Фикс — savepoint на внутреннем catch-сайте:special_indices.py_query_parcel_centroid(ST_Centroid)site_finder/district_resolver.py_admin_names+resolve_objective_districts(внутренние хелперы, отравлявшие forecasting-builder'ы)Тесты
20 новых. Ключевое —
_ReleaseTrapSession-фейк моделирует все три семантики Postgres: SAVEPOINT запрещён на входе в aborted-tx, ROLLBACK TO SAVEPOINT чистит poison на exception-выходе, и RELEASE падает на нормальном выходе при poisoned-tx (сама ловушка). Каждый тест оборачивает внутренний хелпер в имитацию_run/_safe_call, роняетdb.executeодин раз → проверяет, что вернулся фолбэк самого хелпера (не внешний — доказывает, что RELEASE не выстрелил) и что следующийdb.executeна той же сессии успешен. Red/green: реверт 3 фиксов → 3 trap-теста красные; восстановление → зелёные.tests/services/forecasting/+tests/services/site_finder/: 1643 passed; полный сьют ранее 4354 passed.Ревью
code-reviewer: ✅ APPROVE (финальный, после дельты). Сам перепрогнал red/green на scratch-копии; подтвердил корректность shape SAVEPOINT, безопасность cache-write в
_admin_names(на сбое кэш не отравляется), владение сессией во всех call-site'ах district_resolver, отсутствие:x::type, отсутствие tradein/миграций.Refs #2464 (cluster A).