generated from john/python-template
Fix workflow commit atomicity, error path leak, and UI error boundary
Quality Gate / gate (push) Failing after 48s
Quality Gate / gate (push) Failing after 48s
Phase 1 of docs/reviews/2026-08-23-code-review.md. HIGH-01: process_queued_job committed page evidence and the terminal job status in separate transactions, so a crash between them left a transcript persisted against a job stuck in PROCESSING that the worker never reclaims. The final page's write is now deferred into _finalize_batch_outcome so it shares the terminal transaction. Intermediate pages remain individually durable, and the terminal commit is shielded against cancellation the same way per-page writes already were. HIGH-04: added tests/integration/test_pipeline_atomicity.py covering both Transaction B and Transaction C. Confirmed failing against the previous implementation before the fix. HIGH-03: classify_unexpected_error interpolated the raw exception into AppError.message, which the UI renders and the API serializes, leaking the database path from OperationalError. message is now generic. Because message also feeds format_error_detail, which writes evidence records, the root cause is preserved on a new internal-only AppError.detail field rather than discarded. HIGH-02: replaced 8 hand-rolled ui.notify error calls in home_page and people_page with error_presenter.show_error, restoring the correlation error_id, canonical category, and suggestion. Added an AST guard to test_ui_boundaries.py so pages cannot hand-roll error notifications again. Docs updated per documentation-sync: the message/detail split in docs/error_handling.md and the multi-page atomicity rule in services.instructions.md. Verification: ruff clean, 381 tests passing, ty unchanged at 10 known SQLAlchemy descriptor false positives. Co-authored-by: Copilot App <[email protected]>
This commit is contained in:
co-authored by
Copilot App
parent
8d3c60fce1
commit
de18c2e9da
@@ -113,3 +113,42 @@ def test_only_the_designated_owners_construct_a_raw_table():
|
||||
if _calls_ui_table(ast.parse(path.read_text(encoding="utf-8")))
|
||||
)
|
||||
assert set(offenders) == TABLE_OWNERS
|
||||
|
||||
|
||||
def _notifies_negative(tree: ast.Module) -> bool:
|
||||
"""Return True if the module calls ``ui.notify(..., type="negative")``."""
|
||||
for node in ast.walk(tree):
|
||||
if not isinstance(node, ast.Call):
|
||||
continue
|
||||
func = node.func
|
||||
if not (
|
||||
isinstance(func, ast.Attribute)
|
||||
and func.attr == "notify"
|
||||
and isinstance(func.value, ast.Name)
|
||||
and func.value.id == "ui"
|
||||
):
|
||||
continue
|
||||
for keyword in node.keywords:
|
||||
if (
|
||||
keyword.arg == "type"
|
||||
and isinstance(keyword.value, ast.Constant)
|
||||
and keyword.value.value == "negative"
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def test_no_page_hand_rolls_error_notifications():
|
||||
"""HIGH-02: `ui.instructions.md:42` routes all error display through error_presenter.
|
||||
|
||||
Hand-rolled ``ui.notify(str(exc), type="negative")`` discards the correlation
|
||||
``error_id``, the canonical category, and the actionable suggestion that
|
||||
``show_error`` renders, leaving the user with nothing to report. Eight such sites
|
||||
existed in ``home_page`` and ``people_page``; this keeps them from returning.
|
||||
"""
|
||||
offenders = sorted(
|
||||
path.stem for path in _page_paths() if _notifies_negative(ast.parse(path.read_text(encoding="utf-8")))
|
||||
)
|
||||
assert offenders == [], (
|
||||
f"Pages must render errors via error_presenter.show_error, not ui.notify: {offenders}"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user