M26: harden clean observability acceptance
MobilityOps acceptance / backend (push) Canceled after 0s
MobilityOps acceptance / frontend (push) Canceled after 0s

This commit is contained in:
NuklearRabbit
2026-08-10 16:15:23 +02:00
parent 90cc3cf378
commit b00d33af11
4 changed files with 30 additions and 7 deletions
+17
View File
@@ -2594,3 +2594,20 @@ evidence yet."
prior Starlette/httpx warning is confirmed absent in the rebuilt environment. prior Starlette/httpx warning is confirmed absent in the rebuilt environment.
- Exact next action: run complete clean acceptance, push all five milestone commits, - Exact next action: run complete clean acceptance, push all five milestone commits,
create a verified live backup, redeploy and execute live browser acceptance. create a verified live backup, redeploy and execute live browser acceptance.
## M26 — clean acceptance hardening (2026-08-10)
- The clean full-suite run exposed and fixed two observability regressions that targeted
runs against a stale image had missed: readiness now returns immediately with 503 only
on database failure and reaches the healthy 200 branch correctly; Prometheus outbox
series now use the documented label order.
- All synthetic/operational and pending/delivering/succeeded/failed outbox combinations
are initialized to zero before persisted counts are applied. Fresh installs, restores
and demo resets therefore produce stable zero-valued metrics instead of misleading
`no data` panels and absent alert inputs.
- Evidence from the final rebuilt isolated stack: complete backend **239 passed with zero
warnings**, ruff passed and mypy passed across 58 source files. Frontend TypeScript
lint and production build pass; full and production-only npm audits both report zero
vulnerabilities.
- Exact next action: commit/push, take a verified pre-deployment backup, redeploy to
Unraid, then run migration, health, browser and full Playwright acceptance.
+8 -2
View File
@@ -10,7 +10,7 @@ from sqlalchemy import func, select
from app.core.config import get_settings from app.core.config import get_settings
from app.core.db import SessionLocal from app.core.db import SessionLocal
from app.core.observability import OUTBOX_EVENTS from app.core.observability import OUTBOX_EVENTS
from app.models.outbox import DEMO_SCENARIO_ERROR_CODE, OutboxEvent from app.models.outbox import DELIVERY_STATUSES, DEMO_SCENARIO_ERROR_CODE, OutboxEvent
router = APIRouter(tags=["observability"]) router = APIRouter(tags=["observability"])
settings = get_settings() settings = get_settings()
@@ -26,8 +26,14 @@ def _refresh_database_metrics() -> None:
).group_by(OutboxEvent.delivery_status, "demo") ).group_by(OutboxEvent.delivery_status, "demo")
).all() ).all()
OUTBOX_EVENTS.clear() OUTBOX_EVENTS.clear()
# Keep every time series present even when a state currently contains no rows.
# Stable zero-valued series make dashboards and alerts deterministic after resets,
# restores and fresh installations instead of turning "zero" into "no data".
for scenario in ("synthetic", "operational"):
for status in DELIVERY_STATUSES:
OUTBOX_EVENTS.labels(scenario, status).set(0)
for status, is_demo, count in rows: for status, is_demo, count in rows:
OUTBOX_EVENTS.labels(str(status), "synthetic" if is_demo else "operational").set(count) OUTBOX_EVENTS.labels("synthetic" if is_demo else "operational", str(status)).set(count)
@router.get("/metrics", include_in_schema=False) @router.get("/metrics", include_in_schema=False)
+1 -1
View File
@@ -30,7 +30,7 @@ HTTP_IN_PROGRESS = Gauge(
OUTBOX_EVENTS = Gauge( OUTBOX_EVENTS = Gauge(
"mobilityops_outbox_events", "mobilityops_outbox_events",
"Persisted outbox events by state and scenario type.", "Persisted outbox events by state and scenario type.",
("status", "scenario"), ("scenario", "status"),
) )
DATABASE_READY = Gauge( DATABASE_READY = Gauge(
"mobilityops_database_ready", "mobilityops_database_ready",
+4 -4
View File
@@ -148,11 +148,11 @@ def readiness() -> JSONResponse:
db.execute(text("SELECT 1")) db.execute(text("SELECT 1"))
except Exception: # noqa: BLE001 -- readiness must convert infrastructure errors to 503 except Exception: # noqa: BLE001 -- readiness must convert infrastructure errors to 503
DATABASE_READY.set(0) DATABASE_READY.set(0)
return JSONResponse(
status_code=503,
content={"status": "not_ready", "service": "mobilityops-api", "database": "down"},
)
DATABASE_READY.set(1) DATABASE_READY.set(1)
return JSONResponse(
status_code=503,
content={"status": "not_ready", "service": "mobilityops-api", "database": "down"},
)
return JSONResponse(content={"status": "ready", "service": "mobilityops-api", "database": "up"}) return JSONResponse(content={"status": "ready", "service": "mobilityops-api", "database": "up"})