Files
manage/openspec/changes/observability-service-registry/tasks.md
T
Developer c13e274ca4 docs(openspec): re-scope observability-service-registry change
Rename grafana-prometheus-polish -> observability-service-registry and
rewrite proposal/design/tasks for the approved vision: all observability
integration (alertmanager, grafana, prometheus) configured as service-
registry instances in the UI, surfaced on a dedicated page, with widgets
per service definition -- nothing in the env.

Key scope decisions captured:
- Add alertmanager as a 6th service type + active_alerts widget.
- Rewire /alerts + /alertmanager-status to resolve from service records
  (first-enabled-instance default; no primary flag in v1).
- Add /grafana-status + /prometheus-status health endpoints.
- Observability page discovers services; kill VITE_GRAFANA_URL /
  VITE_PROMETHEUS_URL deep-links.
- Webhook receiver stays log-only (drop the outbound forward).
- Remove PROMETHEUS_FILE_SD_DIR + the file-writer; external Prometheus
  uses http_sd_configs against GET /api/monitoring/prometheus-targets.
  build_node_exporter_targets + that endpoint stay.
- PROMETHEUS_ENABLED stays (Manage's own /metrics toggle).
- End state: zero observability *service* env vars.

Plan = 5 slices, each <=400 changed lines, green tests/lint/build,
commit per slice.
2026-06-23 21:48:58 +00:00

275 lines
12 KiB
Markdown

# Tasks — Observability service registry
**Change:** `observability-service-registry`
**Phase:** tasks
**Date:** 2026-06-23
## Review workload forecast
| Field | Value |
|-------|-------|
| Estimated changed lines | ~790 |
| Chained PRs recommended | Yes (5 slices) |
| Chain strategy | stacked-to-main |
| Slice order | 1 (alertmanager type) → 2 (router rewire + status) → 3 (frontend) → 4 (drop file-SD writer) → 5 (env removal + docs) |
Each slice is committed separately (user pref). Every slice must leave
`cd backend && .venv/bin/ruff check . && .venv/bin/python -m pytest` **and**
`cd frontend && npm run lint && npm run build && npm run test` green.
---
## Slice 1: Backend — add `alertmanager` service type + widget
**Goal:** Alertmanager becomes a first-class registry service with an
`active_alerts` widget, mirroring Grafana/Prometheus.
- [ ] **1.1 Add integration module**
- Files: `backend/src/media_library_viewer_api/integrations/alertmanager.py` (new)
- Lines: ~45
- Details: `AlertmanagerConfig` (`base_url`, `timeout_seconds=5`),
`AlertmanagerAlertsWidgetConfig` (`severity_filter` optional),
`DEFINITION` with `service_type="alertmanager"`, secret `api_key`, widget kind
`active_alerts`. Follow `prometheus.py` exactly.
- [ ] **1.2 Register the type**
- Files: `backend/src/media_library_viewer_api/integrations/registry.py`
- Lines: ~2
- Details: import `DEFINITION as ALERTMANAGER`; add to `SERVICE_DEFINITIONS`.
- [ ] **1.3 Add widget source adapter**
- Files: `backend/src/media_library_viewer_api/widgets/sources.py`
- Lines: ~40
- Details: `AlertmanagerWidgetSource` — reuse `_summary_from_alerts` (move it to
a shared import or keep in monitoring router and import). `fetch` calls
`{base_url}/api/v1/alerts` with optional bearer token, optional
`severity_filter`, returns the summary shape. Register in `SERVICE_ADAPTERS`.
- [ ] **1.4 Update backend tests**
- Files: `backend/tests/test_services.py`, `backend/tests/test_widgets.py`
- Lines: ~50
- Details: assert `alertmanager` in service types (test count becomes 6);
test the adapter with mocked HTTP (alerts summary) and missing service.
- [ ] **1.5 Verify backend**
- Run: `cd backend && .venv/bin/ruff check . && .venv/bin/python -m pytest`
- [ ] **1.6 Commit**
- Message: `feat(observability): add alertmanager service type and widget`
**Slice 1 total:** ~140 changed lines.
---
## Slice 2: Backend — rewire monitoring router + health endpoints
**Goal:** Alertmanager is resolved from a service record; Grafana/Prometheus
health endpoints added; webhook relay dropped.
- [ ] **2.1 Add service resolution helper**
- Files: `backend/src/media_library_viewer_api/routers/monitoring.py`
- Lines: ~20
- Details: `_resolve_service_record(store, service_type, service_id)` returning
the requested or first-enabled `ServiceRecord` (reuses
`build_service_record` from `widgets/sources.py`). Delete
`_alertmanager_client`/`_webhook_client` env readers.
- [ ] **2.2 Rewire `/alerts` and `/alertmanager-status`**
- Files: `backend/src/media_library_viewer_api/routers/monitoring.py`
- Lines: ~40
- Details: `Depends(get_settings_store)`; optional `service_id` query param;
resolve via helper; attach bearer token; on `None` return not-configured body;
add `service_id` + `name` to success responses; add `name`/`peers` to
down-branches (fix type drift).
- [ ] **2.3 Make webhook receiver log-only**
- Files: `backend/src/media_library_viewer_api/routers/monitoring.py`
- Lines: ~-15 (net delete)
- Details: remove the outbound forward `POST` + `_webhook_client`; keep the
receiver logging received alerts and returning `{"status": "received"}`.
- [ ] **2.4 Add grafana/prometheus status endpoints**
- Files: `backend/src/media_library_viewer_api/routers/monitoring.py`
- Lines: ~60
- Details: `GET /api/monitoring/grafana-status?service_id=` (probe
`/api/health`), `GET /api/monitoring/prometheus-status?service_id=` (probe
`/-/healthy` + `/api/v1/status/buildinfo`). Return
`{ up, version, service_id, name, error? }`; none-configured →
`error: "no_service_configured"`.
- [ ] **2.5 Update backend tests**
- Files: `backend/tests/test_api.py`
- Lines: ~70
- Details: rewire existing `TestAlertmanager` to seed an alertmanager service
instance instead of mocking `_alertmanager_client`; add not-configured (no
instance) cases; add `TestGrafanaStatus` / `TestPrometheusStatus` (configured,
missing, unreachable); assert webhook receiver is log-only.
- [ ] **2.6 Verify backend**
- Run: `cd backend && .venv/bin/ruff check . && .venv/bin/python -m pytest`
- [ ] **2.7 Commit**
- Message: `feat(observability): resolve services from registry, add health endpoints`
**Slice 2 total:** ~210 changed lines.
---
## Slice 3: Frontend — service discovery, health cards, alertmanager widget
**Goal:** Observability page discovers services; `VITE_*` URL reads removed;
alertmanager widget added. (If this slice exceeds ~400 lines, split 3a page /
3b widget — see note.)
- [ ] **3.1 Add status hooks + client fns**
- Files: `frontend/src/api/client.ts`, `frontend/src/hooks/useObservability.ts`
- Lines: ~30
- Details: `fetchGrafanaStatus(serviceId?)`, `fetchPrometheusStatus(serviceId?)`,
`useGrafanaStatus`, `usePrometheusStatus`.
- [ ] **3.2 Rewire Observability page**
- Files: `frontend/src/components/ObservabilityPage.tsx`
- Lines: ~90
- Details: `useServiceInstances("grafana")` → derive `GRAFANA_BASE_URL` from
first enabled instance; remove `import.meta.env.VITE_GRAFANA_URL`; empty-state
linking to `/services` when none; add Grafana + Prometheus `HealthCard`s.
- [ ] **3.3 Add alertmanager widget**
- Files: `frontend/src/widgets/AlertmanagerAlertsWidget.tsx` (new),
`frontend/src/integrations/registry.ts`, `frontend/src/widgets/index.ts`
- Lines: ~90
- Details: presentational component reusing the alert summary shape; register
`alertmanager` binding with `active_alerts` kind.
- [ ] **3.4 Update types**
- Files: `frontend/src/types/index.ts`
- Lines: ~20
- Details: `GrafanaStatus`, `PrometheusStatus`; add `service_id`/`name` to
status types; `AlertmanagerAlertsWidgetConfig`.
- [ ] **3.5 Update frontend tests**
- Files: `frontend/src/integrations/registry.test.ts`, widget/page tests as needed
- Lines: ~40
- Details: assert alertmanager binding resolves; mock status hooks.
- [ ] **3.6 Verify frontend**
- Run: `cd frontend && npm run lint && npm run build && npm run test`
- [ ] **3.7 Commit**
- Message: `feat(observability): service discovery, health cards, alertmanager widget`
**Slice 3 total:** ~270 changed lines.
---
## Slice 4: Backend — remove file-SD writer
**Goal:** Drop the shared-file Prometheus bridge (`PROMETHEUS_FILE_SD_DIR`);
external Prometheus uses `http_sd_configs` against the existing endpoint.
- [ ] **4.1 Remove the file-writer call sites**
- Files: `backend/src/media_library_viewer_api/main.py` (startup call),
`backend/src/media_library_viewer_api/routers/settings.py`
(post machine create/update/delete calls)
- Lines: ~-15
- Details: delete `write_prometheus_targets(...)` invocations; remove the
import. **Keep** `services/targets.py::build_node_exporter_targets` and the
`GET /api/monitoring/prometheus-targets` endpoint (external Prometheus
consumes via `http_sd_configs`).
- [ ] **4.2 Remove the file-writer + config field**
- Files: `backend/src/media_library_viewer_api/services/targets.py`,
`backend/src/media_library_viewer_api/config.py`
- Lines: ~-30
- Details: delete `write_prometheus_targets` (the writer) from `targets.py`,
leaving `build_node_exporter_targets`; delete `prometheus_file_sd_dir` from
`config.py`. Prune now-unused imports (`Path`, etc.).
- [ ] **4.3 Remove compose mount + env var**
- Files: `docker-compose.yml`, `docker-compose.dev.yml`
- Lines: ~-4
- Details: drop `PROMETHEUS_FILE_SD_DIR` backend env var and any
`prometheus-file-sd` volume mount/bind reference (no Prometheus container
remains to read it).
- [ ] **4.4 Update backend tests**
- Files: `backend/tests/test_targets.py`, `backend/tests/test_api.py`
- Lines: ~-20 / ~+5
- Details: drop tests covering `write_prometheus_targets`; keep/extend tests
for `build_node_exporter_targets` and the `/prometheus-targets` endpoint.
Drop any `write_targets.assert_called_once` assertion in
`TestSettingsMachines`.
- [ ] **4.5 Verify backend**
- Run: `cd backend && .venv/bin/ruff check . && .venv/bin/python -m pytest`
- [ ] **4.6 Commit**
- Message: `refactor(observability): drop file-SD writer for http_sd_configs`
**Slice 4 total:** ~70 changed lines (net negative).
---
## Slice 5: Env removal + docs
**Goal:** Delete the remaining observability service env vars from config/compose
and document the change.
- [ ] **5.1 Remove backend env fields**
- Files: `backend/src/media_library_viewer_api/config.py`
- Lines: ~-2
- Details: delete `alertmanager_url` and `alertmanager_webhook_url` fields.
- [ ] **5.2 Remove compose / Dockerfile build args**
- Files: `docker-compose.yml`, `docker-compose.dev.yml`, `frontend/Dockerfile`
- Lines: ~-8
- Details: drop `ALERTMANAGER_URL`, `ALERTMANAGER_WEBHOOK_URL` from backend
env; drop `VITE_GRAFANA_URL`, `VITE_PROMETHEUS_URL` from frontend build args
(both compose files) and `ARG`/`ENV` (Dockerfile).
- [ ] **5.3 Update docs**
- Files: `docs/REQUIREMENTS.md`, `CHANGELOG.md`, `backend/README.md`,
`docs/monitoring-logging-design.md`
- Lines: ~60
- Details: REQUIREMENTS decision-log entry (alertmanager as a service type;
observability env vars removed incl. `PROMETHEUS_FILE_SD_DIR`; http_sd_configs
replaces the file bridge; thin-dashboard model unchanged); CHANGELOG
added/changed/**BREAKING** (`ALERTMANAGER_URL` users re-create the instance;
`file_sd_configs` users switch to `http_sd_configs`); backend README
monitoring section.
- [ ] **5.4 Manual follow-up (assistant-blocked) — `.env.example`**
- `.env.example` is blocked by the safety policy. Note for the user: remove
`ALERTMANAGER_URL`, `ALERTMANAGER_WEBHOOK_URL`, `VITE_GRAFANA_URL`,
`VITE_PROMETHEUS_URL`, and `PROMETHEUS_FILE_SD_DIR`; keep `PROMETHEUS_ENABLED`.
- No code lines.
- [ ] **5.5 Verify**
- Run: `cd backend && .venv/bin/ruff check . && .venv/bin/python -m pytest`
- Run: `cd frontend && npm run lint && npm run build && npm run test`
- Grep-gate: confirm no remaining references to the removed env vars in
`backend/src`, `frontend/src`, root compose files, Dockerfile.
- [ ] **5.6 Commit**
- Message: `chore(observability): remove remaining observability env vars, docs`
**Slice 5 total:** ~110 changed lines.
---
## Acceptance
- `alertmanager` is a service type; observability services are UI-configured only.
- The observability service env vars (`ALERTMANAGER_URL`,
`ALERTMANAGER_WEBHOOK_URL`, `VITE_GRAFANA_URL`, `VITE_PROMETHEUS_URL`,
`PROMETHEUS_FILE_SD_DIR`) have no remaining references. `PROMETHEUS_ENABLED`
(Manage's own `/metrics` toggle) is the only observability env var that remains.
- `/alerts`, `/alertmanager-status`, `/grafana-status`, `/prometheus-status` work
against service instances with graceful missing/unreachable states.
- Observability page shows Alertmanager/Grafana/Prometheus health and derives
Grafana links from the registry (no `VITE_*` URL).
- `active_alerts` alertmanager widget renders on the dashboard.
- External Prometheus consumes node-exporter targets via `http_sd_configs`
against `/api/monitoring/prometheus-targets`; no shared volume remains.
- All backend and frontend test suites green; docs + CHANGELOG updated.