Diagnose & solve problems — Reliability & Availability diagnostics¶
Motivation¶
The Settings → Diagnose & solve problems section shipped with only
Identity and Security; Reliability and Availability and Performance
were Coming soon placeholders. The Settings slide-over is also too narrow to
render per-resource best-practice findings, and the monitor data plane it would
reuse degrades open (empty payload on failure), which would silently turn
"could not check" into a false "no problems found".
This change adds real Reliability and Availability/Performance diagnostics that
check the configured Azure resources against Azure Well-Architected Framework
practices, and moves the experience onto a dedicated /diagnostics page.
Design of record: docs/architecture/diagnostics.md.
User-facing change¶
- The Settings Diagnose & solve problems cards are now launchers:
clicking one closes the panel and opens the dedicated
/diagnostics/:categorypage. Identity and Security graduated out of the narrow panel onto the page. - New Reliability and Availability and Performance categories render
severity-ranked findings grouped by resource (AKS / Storage / ACR / Container
App / API), with a rollup chip set, per-finding recommendation, and a
Microsoft Learn best-practice link.
critical/warningexpand by default. - Findings degrade honestly: a fetch failure or a permission denial surfaces as
an
indeterminatefinding plus a page banner ("some checks could not be verified … re-run with a higher role"), never a fabricatedok/critical. By-design charter choices (minReplicas=1, a stopped cluster under Reliability) areinfo, not defects. - The page is on-demand (one run on entry + an explicit Re-run); no auto polling. A late response from a previous run/category never overwrites a newer one.
API / IaC diff summary¶
- New route
GET /api/diagnostics/{category}(require_caller, read-only, cached 30 s,fresh=trueto bypass). Registered in api/main.py before the frontend catch-all. - New service
api/services/diagnostics/—models.py(Finding / DiagnosticReport / ResourceSnapshot, closed severity vocabulary),snapshot.py(per-resource fetch with isolation, bounded per-fetch timeout + run deadline, non-blocking executor shutdown),engine.py(cached, sanitised report assembly + structured run log),rules/reliability.py+rules/availability.py(pure, versioned rule catalogs; k8s EOL facts carry anas_ofand degrade toinfo). - New SPA:
web/src/api/diagnostics.ts(typed client),web/src/pages/diagnostics/DiagnosticsPage.tsx+diagnosticsModel.ts.DiagnosticsSectionconverted to a launcher;IdentitySecurityDetailexported for reuse on the page. Routes/diagnosticsand/diagnostics/:categoryadded to web/src/App.tsx. - No IaC change. No new dependency. No SAS token, no Storage network flip, no
Azure Run Command — fetches reuse the existing
monitoringservice helpers.
Persona impact (§12a)¶
- Read-only diagnostic. Permission-denied is classified
indeterminate, nevercritical, so a subscription Reader sees "could not verify" instead of a false failure.test_persona_matrix.pystays green (no scope narrowed, no newrequire_calleron an SSE stream — this is a plain GET).
Validation evidence¶
- Backend:
uv run pytest -q api/tests→ 2972 passed, 3 skipped. New:test_diagnostics_rules.py,test_diagnostics_availability_rules.py,test_diagnostics_route.py,test_diagnostics_snapshot.py(golden rules + route contract + auth + failure→indeterminate + hung-fetch timeout + sidecar-degraded→indeterminate).test_persona_matrix.py,test_route_contracts.pygreen. - Backend lint:
uv run ruff check api→ clean. - Frontend:
cd web && npm run buildclean;npx vitest run→ 706 passed (incl.diagnosticsModel.test.ts); ESLint clean on the new/changed files. - Docs:
check_frontmatter.pyOK (54 pages);mkdocs build --strictsucceeds with the newarchitecture/diagnostics.mdwired into nav.
Hardening applied per phase (critique loop)¶
- Phase 1: the snapshot executor ran fetches sequentially and blocked on
unfinished threads at
with-exit → fixed to submit-all-then-collect with a non-blockingshutdown(wait=False, cancel_futures=True); a hung fetch now returns atimeoutsnapshot within the per-fetch cap (regression test added). - Phase 2: a Re-run could let a slow earlier response overwrite a newer one, and a category switch could flash stale findings → fixed with an in-flight signal ref (cancel previous) + clearing the report on category change.
- Phase 3: a Redis-unavailable sidecar snapshot returned all-
down, which would have rendered a falsecritical→ classified asunavailable(indeterminate) instead (regression test added).