feat(sync): /api/sync/health endpoint + dashboard health pill + MALFORMED UI
Backend:
- GET /api/sync/health returns {last_sync_at, last_sync_status,
last_halt_reason, recent_phase_failures, escalation_phase, is_healthy}.
healthy when last run was completed (or none yet), no phase has
tripped the 3-in-a-row escalation, and recent failures <= 1.
- Dashboard + run-level endpoints include `malformed` count so the
Defecte pill can render.
Frontend:
- Health pill in .sync-card-controls with three states — healthy
(success green, check icon), warning (amber, triangle), escalated
(error red, x-octagon + glow). Tooltip exposes the halt reason and
the top phases with recent failures.
- Status-dot + badge add MALFORMED treatment via --compare orange,
distinct from ERROR red. DESIGN.md notes the diagnostic rationale
(ERROR = runtime, MALFORMED = payload source issue).
- Defecte filter pill on dashboard + logs pages. Mobile segmented
control includes Defecte count. Counts wired to the malformed key.
- startSync() shows a native confirm modal when state is
halted_escalation — operator override still possible, not silenced.
- ORDER_STATUS.MALFORMED mirror added to shared.js.
- Cache-bust: style.css v46, shared.js v47, dashboard.js v52,
logs.js v16.
5 endpoint tests cover empty state, completed, failed, escalated,
single-failure warning. Full CI: 257 unit + 33 e2e green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -160,6 +160,52 @@ async def sync_status():
|
||||
return result
|
||||
|
||||
|
||||
@router.get("/api/sync/health")
|
||||
async def sync_health():
|
||||
"""Aggregated sync health snapshot used by the dashboard pill.
|
||||
|
||||
Fields:
|
||||
last_sync_at ISO timestamp of most recent run start (or null).
|
||||
last_sync_status completed | failed | running | halted_escalation | null.
|
||||
last_halt_reason error_message from that run (only populated on
|
||||
failed / halted_escalation).
|
||||
recent_phase_failures {phase: count} across the last 3 runs.
|
||||
escalation_phase the phase that tripped the 3-in-a-row halt, or null.
|
||||
is_healthy completed last + <=1 recent phase failure.
|
||||
"""
|
||||
db = await sqlite_service.get_sqlite()
|
||||
try:
|
||||
cursor = await db.execute(
|
||||
"SELECT run_id, started_at, status, error_message "
|
||||
"FROM sync_runs ORDER BY started_at DESC LIMIT 1"
|
||||
)
|
||||
last_row = await cursor.fetchone()
|
||||
finally:
|
||||
await db.close()
|
||||
|
||||
last = dict(last_row) if last_row else {}
|
||||
last_status = last.get("status")
|
||||
halt_reason = last.get("error_message") if last_status in ("failed", "halted_escalation") else None
|
||||
|
||||
counts = await sqlite_service.get_recent_phase_failures(limit=3)
|
||||
escalation_phase = next((p for p, c in counts.items() if c >= 3), None)
|
||||
|
||||
is_healthy = (
|
||||
last_status in (None, "completed")
|
||||
and escalation_phase is None
|
||||
and sum(counts.values()) <= 1
|
||||
)
|
||||
|
||||
return {
|
||||
"last_sync_at": last.get("started_at"),
|
||||
"last_sync_status": last_status,
|
||||
"last_halt_reason": halt_reason,
|
||||
"recent_phase_failures": counts,
|
||||
"escalation_phase": escalation_phase,
|
||||
"is_healthy": is_healthy,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/api/sync/history")
|
||||
async def sync_history(page: int = 1, per_page: int = 20):
|
||||
"""Get sync run history."""
|
||||
|
||||
Reference in New Issue
Block a user