spec(per-instance-hook-scoping): verify + reconcile tracking

Write apply-progress.md, tick all 17 tasks, add verify-report.md (21/21 PI-101..121
PASS). Gates green: 368 pytest, ruff clean, npm build+lint 0 errors, 165 vitest.
fetchBackupDashboard/useBackupDashboard/get_backup_dashboard confirmed untouched
(design decision 5). No blocking code findings.
This commit is contained in:
Developer
2026-07-10 00:05:58 +00:00
parent 3bc7ce5269
commit 8d3c44d87f
3 changed files with 197 additions and 17 deletions
@@ -0,0 +1,45 @@
# 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).