29650ca512
Move to openspec/changes/archive/2026-07-09-per-instance-hook-scoping/ (R100 renames preserved). 9 artifacts. Canonical openspec/specs/ service-instance-scoping/ remains. Resolves multi-instance wrong-data bug (hooks now scope by instance.id; instance switcher re-scopes). Carry-overs: fetchBackupDashboard untouched (design decision 5); subquery scoping for runs/alerts (schema asymmetry).
3.3 KiB
3.3 KiB
Apply Progress: Per-Instance Hook Scoping
Change: per-instance-hook-scoping
Phase: apply-progress
Date: 2026-07-09
Status: complete — all 17 tasks done, all gates green, verified (see verify-report.md)
Slice delivered
Single slice (commit 3bc7ce5, +268/−73, 12 files) — small correctness fix, well within the 400-line budget.
Backend
routers/backups.py—get_backup_jobs,get_backup_runs,get_backup_alertsgainservice_id: str | None = None; threaded into store calls.get_backup_dashboardNOT touched (design decision 5 — widget path, PI-117 risk) (PI-110).services/settings_store.py:list_backup_jobs(service_id=None)— directWHERE service_id = ?when truthy.list_backup_runs(..., service_id=None)+list_backup_alerts(..., service_id=None)— subqueryjob_id IN (SELECT id FROM backup_jobs WHERE service_id = ?)per the schema asymmetry (onlybackup_jobshas the column; runs/alerts attributed via FK chain).- Skip filter on None/empty → backward-compat (PI-111, PI-112).
- Tests (
test_backups.py, +83 lines):service_id=None→ all;service_id="X"→ only X's; cross-instance preservation (seed A+B, scope A, confirm B's excluded).
Frontend
hooks/useObservability.ts—useAlertmanagerAlerts,useAlertmanagerStatus,usePrometheusStatusgain optionalserviceIdinqueryKey(["observability","alerts", serviceId ?? ""]etc.).usePrometheusTargets+useMonitoringMachinesunchanged (global by design — PI-107) (PI-101..PI-104).hooks/useBackups.ts—useBackupJobs,useBackupRuns,useBackupAlertsgainserviceIdinqueryKey.useBackupDashboardunchanged (PI-105, PI-106).api/client.ts+api/backups.ts— 6 fetch fns gain optionalserviceId→?service_id=via the sharedget(path, params)helper.fetchBackupDashboardNOT touched (PI-108, PI-109).pages/service-tabs/AlertsTab.tsx,MetricsTab.tsx,JobsTab.tsx— call hooks withinstance.id; the prior TODO comments about global scoping removed (PI-113..PI-115). The instance switcher on ServicePage naturally re-scopes:instance.idchanges →queryKeychanges → React Query refetches.- Tests:
useBackups.test.ts(+85) covers queryKey isolation + serviceId threading;AlertsTab.test.tsxupdated for the instance.id wiring.
Deviations from tasks.md / design
- None functional.
fetchBackupDashboard/useBackupDashboard/get_backup_dashboarddeliberately untouched (design decision 5, PI-117 preserved).
Final gate results
| Gate | Result |
|---|---|
backend && PYTHONPATH=src python3 -m pytest -q |
368 passed (+6 new), 2 warnings (pre-existing pythonjsonlogger) |
backend && PYTHONPATH=src python3 -m ruff check src tests |
All checks passed |
frontend && npm run build |
exit 0 (pre-existing chunk-size warning) |
frontend && npm run lint |
0 errors, 1 pre-existing warning (WidgetConfigDialog.tsx, untouched) |
frontend && npx vitest run |
165 passed (+7 new) |
Verification
See verify-report.md — adversarial fresh-context review: 21/21 PASS, all gates green. No blocking code findings. Archive blocker is doc-only (this file + ticked tasks clear it). Non-blocking: weak queryKey-isolation assertion in useBackups.test.ts (wiring correct in source; test could assert actual cache keys — LOW).