- 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>
7.4 KiB
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:
- Hosting —
self_hosted(coulomb operates the service) vscloud_hosted(coulomb consumes someone else's running service). - Development —
first_party(coulomb is development-responsible) vsthird_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_catalogrows migrate intoservice_catalogas(hosting_type=cloud_hosted, development_type=third_party); the GDPR/DPA/ ToS/privacy/region/retention fields move intoservice_cloud. tpsc_entries/tpsc_snapshots(per-repo dependency declarations) keep pointing at the catalog viaservice_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_catalogtable in place vs. introduceservice_catalogand keeptpsc_catalogas a view/alias. Lean: newservice_catalog, repoint TPSC reads. maturity_levelsource of truth: operator-set vs. derived from a futurecheck-domevaluation. 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:ServiceCatalogcore +ServiceThirdParty,ServiceFirstParty,ServiceCloud,ServiceSelfHostedextensions.- Alembic migration; register in
api/models/__init__.py. - Data migration: copy
tpsc_catalog→service_catalog+service_cloudwith(cloud_hosted, third_party); backfillservice_idon 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/catalogwithhosting_type/development_type/maturity_levelfilters;GET /services/{slug}returns core + applicable extensions.- Write path to register/update a service and its extensions (generalise
register_service; keep athird_party-shaped compatibility wrapper). - Keep
/tpsc/*working as adevelopment_type=third_partyview.
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_cachemigration) — 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/servicesreference; update/docs/tpscto 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_typeanddevelopment_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 generalisedpolicies/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