Files
manage/openspec/changes/service-registry/apply-progress.md
T
Developer 10fd4ead4a feat(widgets): rebind widgets to the service registry
PR 2 of 4 for the runtime service registry change.

- dashboard_widgets gains service_id + widget_kind columns (legacy
  addon_id/widget_type kept but unused).
- Source adapters take (service: ServiceRecord | None, widget_kind, config).
  SERVICE_ADAPTERS keyed by service_type; BUILTIN_ADAPTERS for backups/static.
- Backups and static stay as service-less built-ins (service_id nullable),
  exposed via GET /api/widgets/builtin.
- SSH task adapter resolves the task + instance, runs over SSH, and appends a
  service_task_runs history row on success/failure/timeout/error.
- Retire widgets/registry.py; widget metadata now comes from the integrations
  registry + widgets/builtin. Remove /api/widgets/types and /api/widgets/sources.
- Stop default widget seeding (fresh install = empty dashboard).
- Rewrite widget tests around the service-bound + built-in model (26 tests).

Backend-only breaking change; frontend is reconciled in Slice 3. Build/lint
stay green; pytest 222 passed.
2026-06-22 16:42:56 +00:00

79 lines
3.5 KiB
Markdown

# Apply Progress: Runtime Service Registry
**Change:** `service-registry`
**Apply run:** PR 1 + PR 2 / Slice 1 + Slice 2
**Date:** 2026-06-19
## Slice 1 — Backend service foundation (MERGED)
Completed in PR #7. See git history. Summary: Fernet secrets helper, closed
`integrations/` registry with Pydantic config + widget-config definitions for
grafana/prometheus/jellyfin/nextcloud/ssh_tasks, `services` + `service_task_runs`
tables with cascade delete, `/api/services*` CRUD, `MANAGE_ENCRYPTION_KEY`
required at startup, 25 tests.
## Slice 2 — Backend widget rebind to services (this PR)
### Completed tasks
- [x] 2.1 Add `service_id` / `widget_kind` columns to `dashboard_widgets`
(additive ALTER; legacy `addon_id`/`widget_type` kept but unused).
- [x] 2.2 Refactor source adapters to `fetch(service, widget_kind, config)`
with `ServiceRecord | None`. `SERVICE_ADAPTERS` keyed by service_type;
`BUILTIN_ADAPTERS` for backups/static. SSH adapter resolves the task +
instance, runs, and appends a `service_task_runs` row (success/failure/
timeout/error).
- [x] 2.3 Retire old `widgets/registry.py` (deleted; metadata now comes from
`integrations/registry` + `widgets/builtin`).
- [x] 2.4 Update widgets router + models for service-bound + built-in widgets.
Removed `/api/widgets/types` and `/api/widgets/sources`; added
`/api/widgets/builtin`.
- [x] 2.5 Rewrite widget tests around the new model.
- [x] 2.6 Stop default widget seeding (fresh install = empty dashboard).
### Decision resolved mid-slice
Backups and static widgets stay as **service-less built-ins** (`service_id`
nullable), per product decision. The data endpoint resolves built-ins via
`BUILTIN_ADAPTERS` and service-bound widgets via `SERVICE_ADAPTERS` + a
decrypted `ServiceRecord`.
### Files changed (Slice 2)
- New: `widgets/builtin.py` (built-in kinds + light config validation).
- Rewritten: `widgets/sources.py` (`ServiceRecord`, new protocol, service +
built-in adapters, SSH run logging, `_build_ssh_client`).
- Deleted: `widgets/registry.py`.
- Modified: `models/widgets.py` (service_id + widget_kind; `BuiltinWidgetKindInfo`).
- Modified: `routers/widgets.py` (new validation, `/builtin`, data resolution).
- Modified: `services/settings_store.py` (widget columns; no-op seeding).
- Modified: `integrations/base.py` (`WidgetKind.config_model` for Pydantic
widget-config validation).
- Rewritten: `tests/test_widgets.py` (26 tests).
### Verification (Slice 2)
```bash
cd backend
.venv/bin/ruff check . # All checks passed
PYTHONPATH=src .venv/bin/python -m pytest # 222 passed
cd ../frontend
npm run lint # 0 errors
npm run build # success
```
### Known transient state (resolved by Slice 3)
Slice 2 is a backend-only breaking change to the widget API. Until Slice 3
lands, the frontend still calls the removed `/api/widgets/types` and
`/api/widgets/sources` endpoints and uses the old `widget_type` shape, so the
dashboard widget config UI is non-functional at runtime. Build/lint stay green.
This is the accepted transient state for a stacked backend→frontend rebind.
## Remaining work
- Slice 3: Frontend services runtime (types, API, hooks, frontend service
registry, service pages, route swap, remove addon pages, reconcile widget UI).
- Slice 4: Dashboard picker on services, settings rework, remove
`grafana_url`/`prometheus_url` env vars, docs + changelog.