Files
state-hub/workplans/STATE-WP-0062-service-catalog-two-dimensions.md
tegwick ce82ada0fa STATE-WP-0062 T5: docs, first-party↔repo test, mark workplan finished
- Add /docs/services reference (two-dimension model, persistence, API) and a
  pointer note from /docs/tpsc; add it to the Reference nav.
- Add a test asserting first_party.repo_slug resolves to a managed_repos FK
  (8 services tests green).
- Mark STATE-WP-0062 tasks done / status finished.

Known classes seeded in the live catalog via the API (Gitea, Postgres as
self-hosted/third-party; State Hub as self-hosted/first-party at Level 2).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 21:16:37 +02:00

7.4 KiB
Raw Blame History

id, type, title, domain, repo, status, owner, topic_slug, created, updated, state_hub_workstream_id
id type title domain repo status owner topic_slug created updated state_hub_workstream_id
STATE-WP-0062 workplan Two-dimension service catalog (hosting × development) + Services nav section custodian state-hub finished codex custodian 2026-06-19 2026-06-19 b5c9d93f-9f5e-4d10-bb3b-90322c7419b7

STATE-WP-0062 — Two-dimension service catalog + Services nav section

Origin: UI request to reorganise the single "Services (TPSC)" page into a Services section. During refinement the model was generalised: services vary along two independent dimensions, not one list of three, so the catalog and its persistence are restructured to reflect that.

Concept

Two orthogonal dimensions classify every service:

  • Hostingself_hosted (coulomb operates the service) vs cloud_hosted (coulomb consumes someone else's running service).
  • Developmentfirst_party (coulomb is development-responsible) vs third_party (coulomb is not development-responsible).

Their product gives four classes:

third-party first-party
cloud-hosted SaaS/APIs (today's TPSC) our service deployed to a cloud
self-hosted OSS we run (Gitea, Postgres…) our service on coulomb infra

Design principle: the "extra data" attaches to a dimension, not a class, so extension data composes (a self-hosted first-party service gets the self-hosted and the first-party extensions) rather than forcing four bespoke record shapes.

Persistence model (decided)

A single common service_catalog core table plus per-dimension extension tables (each 1:1, optional, keyed by service_id):

  • service_catalog (core, all services): id, slug (unique), name, owner_or_provider, category, description, website_url, status (active|deprecated), hosting_type (self_hosted|cloud_hosted), development_type (first_party|third_party), maturity_level (1..3 | null — the Service DoM Level), created_at, updated_at.
  • service_third_party (development_type = third_party): upstream package references, upstream service/support contacts, source URL, license.
  • service_first_party (development_type = first_party): FK to the internal dev repo (managed_repos.id), owning domain.
  • service_cloud (hosting_type = cloud_hosted): GDPR maturity, DPA available, ToS/privacy URLs, data-processing regions, retention notes — the existing TPSC data-processor block (relevant whenever data leaves coulomb infra, independent of who built the service).
  • service_self_hosted (hosting_type = self_hosted): three-helix instance/host, deployment ref, runbook ref, upstream OSS project (when third-party).

Read-model boundary (ADR-001): catalog writes are administrative bootstrap data (like the TPSC catalog today), not derived state. They reuse the existing TPSC write surface pattern; no new sanctioned read-model write is introduced.

Migration & back-compat

  • Existing tpsc_catalog rows migrate into service_catalog as (hosting_type=cloud_hosted, development_type=third_party); the GDPR/DPA/ ToS/privacy/region/retention fields move into service_cloud.
  • tpsc_entries / tpsc_snapshots (per-repo dependency declarations) keep pointing at the catalog via service_id; the /tpsc/* endpoints remain as a filtered view (development_type=third_party) so existing ingestion and the current page keep working during transition.

Open questions (resolve in T1, do not block proposal)

  • Whether to generalise the tpsc_catalog table in place vs. introduce service_catalog and keep tpsc_catalog as a view/alias. Lean: new service_catalog, repoint TPSC reads.
  • maturity_level source of truth: operator-set vs. derived from a future check-dom evaluation. Start operator-set; leave room to derive later.

Tasks

T1 — Core + extension data model and migration

id: STATE-WP-0062-T01
status: done
priority: high
state_hub_task_id: "f74612f6-b442-4b5f-ac4d-7bc6d1d4883f"
  • api/models/service_catalog.py: ServiceCatalog core + ServiceThirdParty, ServiceFirstParty, ServiceCloud, ServiceSelfHosted extensions.
  • Alembic migration; register in api/models/__init__.py.
  • Data migration: copy tpsc_catalogservice_catalog + service_cloud with (cloud_hosted, third_party); backfill service_id on TPSC entries.

T2 — API + MCP

id: STATE-WP-0062-T02
status: done
priority: high
state_hub_task_id: "373cddfa-c85b-47a7-bb8e-c86e9341c237"
  • GET /services/catalog with hosting_type / development_type / maturity_level filters; GET /services/{slug} returns core + applicable extensions.
  • Write path to register/update a service and its extensions (generalise register_service; keep a third_party-shaped compatibility wrapper).
  • Keep /tpsc/* working as a development_type=third_party view.

T3 — Services nav section + four-quadrant pages

id: STATE-WP-0062-T03
status: done
priority: high
state_hub_task_id: "b14bbbdd-347e-4f62-9ac3-ad42360fa766"
  • Replace the top-level "Services (TPSC)" entry with a Services section.
  • Pages: Third Party (current TPSC view), First Party (with internal repo link + Service Maturity Level column), Self Hosted (three-helix infra view). Each is a filtered view over service_catalog.
  • Surface the two dimensions explicitly (e.g. a quadrant filter) so the four classes are navigable.

T4 — Terminology: Tier → Level (Service DoM only)

id: STATE-WP-0062-T04
status: done
priority: medium
state_hub_task_id: "1c296d7a-c090-430f-8d0d-27b548d88d9e"
  • policies/service-dom.md: rename "Tier 1/2/3" → "Level 1/2/3"; column header "Service Maturity Level". Do not touch the DoI tier subsystem (repos.md, check_doi.py, doi_cache migration) — that is a separate, established concept.

T5 — Tests, docs, seed

id: STATE-WP-0062-T05
status: done
priority: medium
state_hub_task_id: "2edd68ee-0d9a-431d-9891-73a0f6be6a41"
  • Tests: model + migration round-trip, dimension filters, TPSC back-compat view, first-party↔repo link.
  • Docs: /docs/services reference; update /docs/tpsc to point at the generalised model.
  • Seed the known classes: TPSC SaaS (cloud/third-party), self-hosted OSS from tools.md (Gitea, Postgres — self-hosted/third-party), and State Hub itself (self-hosted/first-party).

Acceptance

  • Every service carries an explicit hosting_type and development_type; the four classes are queryable and navigable.
  • Common fields live once in service_catalog; dimension-specific data lives in composable extension tables (third-party upstream contacts, first-party repo link, cloud data-processing/GDPR, self-hosted infra).
  • The existing TPSC page and ingestion keep working through the back-compat view.
  • The Services nav section exposes Third Party / First Party / Self Hosted, with a Service Maturity Level column on First Party.
  • The Service DoM policy uses "Level", not "Tier".

See also

  • api/models/tpsc.py, api/routers/tpsc.py — prior art being generalised
  • policies/service-dom.md — Service Definition of Mature (Level source)
  • dashboard/src/tools.md — current static self-hosted listing (seed source)
  • policies/repo-doi.md / dashboard/src/repos.md — the DoI tier subsystem that is intentionally left unchanged