# 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_alerts` gain `service_id: str | None = None`; threaded into store calls. `get_backup_dashboard` **NOT touched** (design decision 5 — widget path, PI-117 risk) (PI-110). - `services/settings_store.py`: - `list_backup_jobs(service_id=None)` — direct `WHERE service_id = ?` when truthy. - `list_backup_runs(..., service_id=None)` + `list_backup_alerts(..., service_id=None)` — subquery `job_id IN (SELECT id FROM backup_jobs WHERE service_id = ?)` per the schema asymmetry (only `backup_jobs` has 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`, `usePrometheusStatus` gain optional `serviceId` in `queryKey` (`["observability","alerts", serviceId ?? ""]` etc.). `usePrometheusTargets` + `useMonitoringMachines` **unchanged** (global by design — PI-107) (PI-101..PI-104). - `hooks/useBackups.ts` — `useBackupJobs`, `useBackupRuns`, `useBackupAlerts` gain `serviceId` in `queryKey`. `useBackupDashboard` **unchanged** (PI-105, PI-106). - `api/client.ts` + `api/backups.ts` — 6 fetch fns gain optional `serviceId` → `?service_id=` via the shared `get(path, params)` helper. `fetchBackupDashboard` **NOT touched** (PI-108, PI-109). - `pages/service-tabs/AlertsTab.tsx`, `MetricsTab.tsx`, `JobsTab.tsx` — call hooks with `instance.id`; the prior TODO comments about global scoping removed (PI-113..PI-115). The instance switcher on ServicePage naturally re-scopes: `instance.id` changes → `queryKey` changes → React Query refetches. - Tests: `useBackups.test.ts` (+85) covers queryKey isolation + serviceId threading; `AlertsTab.test.tsx` updated for the instance.id wiring. ## Deviations from tasks.md / design - None functional. `fetchBackupDashboard` / `useBackupDashboard` / `get_backup_dashboard` deliberately 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).