From 8ea7099b528a818aae7534787e356d266592b661 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 16 May 2026 04:06:38 +0200 Subject: [PATCH] docs: prepare ops-hub bootstrap handoff --- wiki/OpsHubBootstrapRunbook.md | 108 +++++++ wiki/ops-hub-bootstrap.sql | 268 ++++++++++++++++++ wiki/ops-hub-manifest.draft.json | 65 +++++ wiki/ops-hub-widgets.seed.json | 100 +++++++ ...-0001-establish-ops-hub-first-extension.md | 44 ++- 5 files changed, 579 insertions(+), 6 deletions(-) create mode 100644 wiki/OpsHubBootstrapRunbook.md create mode 100644 wiki/ops-hub-bootstrap.sql create mode 100644 wiki/ops-hub-manifest.draft.json create mode 100644 wiki/ops-hub-widgets.seed.json diff --git a/wiki/OpsHubBootstrapRunbook.md b/wiki/OpsHubBootstrapRunbook.md new file mode 100644 index 0000000..d8292a7 --- /dev/null +++ b/wiki/OpsHubBootstrapRunbook.md @@ -0,0 +1,108 @@ +# Ops Hub Bootstrap Runbook + +Date: 2026-05-16 + +## Purpose + +This runbook gives the operator-ready bootstrap path for `ops-hub`, the VSM +Operations / System 1 extension of Inter-Hub. + +Use this when an authenticated Inter-Hub admin session or deployment migration +is available. The current public v2 API is not sufficient to create the hub, +manifest, API consumer, API key, or seed widgets by itself. + +## Inputs + +- Manifest draft: `wiki/ops-hub-manifest.draft.json` +- Widget seed: `wiki/ops-hub-widgets.seed.json` +- Migration fallback: `wiki/ops-hub-bootstrap.sql` + +## Current Bootstrap Decision + +Use the authenticated Inter-Hub admin UI first. Use the SQL migration fallback +only when a repeatable deployment-side bootstrap is needed before the v2 API is +hardened. + +VSM classification is stored in the manifest capability description for now: + +- `hub_family`: `vsm` +- `vsm_function`: `OPS` +- `vsm_system`: `S1` + +Inter-Hub does not yet have first-class hub metadata columns for these values. + +## UI Path + +1. Log in to Inter-Hub at `https://hub.coulomb.social/NewSession`. +2. Open `/Hubs/new`. +3. Create the hub: + - Name: `Ops Hub` + - Slug: `ops-hub` + - Domain: `ops.coulomb.social` + - Kind: `domain` +4. Open `/HubCapabilityManifests/new?hubId=`. +5. Create a draft manifest with: + - Version: `1.0` + - Capability description from `wiki/ops-hub-manifest.draft.json` + - Contact: operator/team contact +6. Edit the manifest and copy in: + - `declaredWidgetTypes` + - `declaredEventTypes` + - `declaredAnnotationCategories` + - `declaredPolicyScopes` +7. Activate the manifest. +8. Open `/ApiConsumers/new`. +9. Create an API consumer bound to the active ops manifest: + - Name: `ops-hub` + - Description: `API consumer for the VSM Operations hub` + - Scopes for the key: `framework:read hub:ops-hub:read hub:ops-hub:write` +10. Generate an API key and store it only in the operator secret store or local + environment. Do not commit it to Git. +11. Seed the widgets from `wiki/ops-hub-widgets.seed.json` through the UI or + migration fallback. + +## Validation + +After manifest activation: + +```bash +curl -s https://hub.coulomb.social/api/v2/widget-types +curl -s https://hub.coulomb.social/api/v2/event-types +curl -s https://hub.coulomb.social/api/v2/annotation-categories +``` + +Expected: ops-owned vocabulary appears in the relevant registries. + +After API key creation: + +```bash +curl -s -X POST https://hub.coulomb.social/api/v2/token \ + -H "Content-Type: application/x-www-form-urlencoded" \ + --data-urlencode "grant_type=client_credentials" \ + --data-urlencode "client_id=" \ + --data-urlencode "client_secret=" \ + --data-urlencode "scope=framework:read hub:ops-hub:read hub:ops-hub:write" +``` + +Expected: a short-lived access token is returned. + +After widget seeding: + +```bash +curl -s https://hub.coulomb.social/api/v2/hub-registry +``` + +Expected: `ops-hub` is visible, and the operator can see the seeded widgets in +the authenticated UI. + +## Known Blockers + +- The live public v2 API has no `POST /api/v2/hubs`. +- The live public v2 API has no `POST /api/v2/widgets`. +- There are no v2 endpoints for manifest creation/activation. +- There are no v2 endpoints for API consumer or key creation. +- There is no `/api/v2/policy-scopes`. +- Interaction event create currently does not persist submitted metadata. +- Webhook dispatch currently uses the hard-coded `"clicked"` event type. + +These are tracked by HF-WP-0001 T10 for Inter-Hub hardening. diff --git a/wiki/ops-hub-bootstrap.sql b/wiki/ops-hub-bootstrap.sql new file mode 100644 index 0000000..ff70b73 --- /dev/null +++ b/wiki/ops-hub-bootstrap.sql @@ -0,0 +1,268 @@ +-- ops-hub bootstrap fallback for Inter-Hub. +-- +-- Use only when authenticated UI bootstrap is not practical and a +-- deployment-side migration/bootstrap is acceptable. +-- +-- This creates: +-- - Hub row +-- - Active HubCapabilityManifest +-- - Owned type registry entries +-- - ApiConsumer row +-- - Seed widgets +-- +-- It intentionally does not create an ApiKey. Generate the key through the +-- authenticated Inter-Hub UI so the full static key can be shown once and +-- stored in the operator secret store. + +BEGIN; + +INSERT INTO hubs (slug, name, domain, hub_kind) +VALUES ('ops-hub', 'Ops Hub', 'ops.coulomb.social', 'domain') +ON CONFLICT (slug) DO UPDATE +SET name = EXCLUDED.name, + domain = EXCLUDED.domain, + hub_kind = EXCLUDED.hub_kind; + +WITH hub AS ( + SELECT id FROM hubs WHERE slug = 'ops-hub' +) +INSERT INTO hub_capability_manifests ( + hub_id, + manifest_version, + declared_widget_types, + declared_event_types, + declared_annotation_categories, + declared_policy_scopes, + capability_description, + contact, + status, + activated_at +) +SELECT + hub.id, + '1.0', + '[ + "ops-environment", + "ops-host", + "ops-cluster", + "ops-service", + "ops-service-catalog", + "ops-endpoint", + "ops-release", + "ops-backup-set", + "ops-secret-set", + "ops-runbook", + "ops-incident", + "ops-readiness-gate", + "ops-migration-wave", + "ops-risk" + ]'::jsonb, + '[ + "ops-inventory-registered", + "ops-inventory-updated", + "ops-service-discovered", + "ops-health-checked", + "ops-release-observed", + "ops-endpoint-verified", + "ops-backup-verified", + "ops-restore-tested", + "ops-runbook-executed", + "ops-drift-detected", + "ops-risk-raised", + "ops-risk-accepted", + "ops-readiness-gate-updated", + "ops-migration-gate-passed", + "ops-migration-gate-failed" + ]'::jsonb, + '[ + "ops-drift", + "ops-service-catalog-gap", + "ops-backup-gap", + "ops-security-gap", + "ops-routing-gap", + "ops-secret-gap", + "ops-readiness-blocker", + "ops-migration-risk", + "ops-observability-gap", + "ops-recovery-gap" + ]'::jsonb, + '[ + "ops-local", + "ops-transitional-prod", + "ops-production", + "ops-threephoenix", + "ops-registry", + "ops-secrets", + "ops-backup-retention" + ]'::jsonb, + 'VSM Operations / System 1 hub for operational truth and evidence. Metadata: hub_family=vsm; vsm_function=OPS; vsm_system=S1; scope=operational truth, service catalog, readiness, incidents, runbooks, migration waves, and evidence events.', + 'operator', + 'active', + NOW() +FROM hub +ON CONFLICT (hub_id) DO UPDATE +SET manifest_version = EXCLUDED.manifest_version, + declared_widget_types = EXCLUDED.declared_widget_types, + declared_event_types = EXCLUDED.declared_event_types, + declared_annotation_categories = EXCLUDED.declared_annotation_categories, + declared_policy_scopes = EXCLUDED.declared_policy_scopes, + capability_description = EXCLUDED.capability_description, + contact = EXCLUDED.contact, + status = EXCLUDED.status, + activated_at = COALESCE(hub_capability_manifests.activated_at, NOW()), + updated_at = NOW(); + +WITH hub AS ( + SELECT id FROM hubs WHERE slug = 'ops-hub' +), names(name) AS ( + VALUES + ('ops-environment'), + ('ops-host'), + ('ops-cluster'), + ('ops-service'), + ('ops-service-catalog'), + ('ops-endpoint'), + ('ops-release'), + ('ops-backup-set'), + ('ops-secret-set'), + ('ops-runbook'), + ('ops-incident'), + ('ops-readiness-gate'), + ('ops-migration-wave'), + ('ops-risk') +) +INSERT INTO widget_type_registry (name, label, owner_hub_id, status) +SELECT names.name, names.name, hub.id, 'active' +FROM names CROSS JOIN hub +ON CONFLICT (name) DO NOTHING; + +WITH hub AS ( + SELECT id FROM hubs WHERE slug = 'ops-hub' +), names(name) AS ( + VALUES + ('ops-inventory-registered'), + ('ops-inventory-updated'), + ('ops-service-discovered'), + ('ops-health-checked'), + ('ops-release-observed'), + ('ops-endpoint-verified'), + ('ops-backup-verified'), + ('ops-restore-tested'), + ('ops-runbook-executed'), + ('ops-drift-detected'), + ('ops-risk-raised'), + ('ops-risk-accepted'), + ('ops-readiness-gate-updated'), + ('ops-migration-gate-passed'), + ('ops-migration-gate-failed') +) +INSERT INTO event_type_registry (name, label, owner_hub_id, status) +SELECT names.name, names.name, hub.id, 'active' +FROM names CROSS JOIN hub +ON CONFLICT (name) DO NOTHING; + +WITH hub AS ( + SELECT id FROM hubs WHERE slug = 'ops-hub' +), names(name) AS ( + VALUES + ('ops-drift'), + ('ops-service-catalog-gap'), + ('ops-backup-gap'), + ('ops-security-gap'), + ('ops-routing-gap'), + ('ops-secret-gap'), + ('ops-readiness-blocker'), + ('ops-migration-risk'), + ('ops-observability-gap'), + ('ops-recovery-gap') +) +INSERT INTO annotation_category_registry (name, label, owner_hub_id, status) +SELECT names.name, names.name, hub.id, 'active' +FROM names CROSS JOIN hub +ON CONFLICT (name) DO NOTHING; + +WITH hub AS ( + SELECT id FROM hubs WHERE slug = 'ops-hub' +), names(name) AS ( + VALUES + ('ops-local'), + ('ops-transitional-prod'), + ('ops-production'), + ('ops-threephoenix'), + ('ops-registry'), + ('ops-secrets'), + ('ops-backup-retention') +) +INSERT INTO policy_scope_registry (name, label, owner_hub_id, status) +SELECT names.name, names.name, hub.id, 'active' +FROM names CROSS JOIN hub +ON CONFLICT (name) DO NOTHING; + +WITH manifest AS ( + SELECT id FROM hub_capability_manifests + WHERE hub_id = (SELECT id FROM hubs WHERE slug = 'ops-hub') +) +INSERT INTO api_consumers ( + name, + description, + hub_capability_manifest_id, + rate_limit_per_minute, + quota_per_day, + is_active +) +SELECT + 'ops-hub', + 'API consumer for the VSM Operations hub', + manifest.id, + 60, + 10000, + TRUE +FROM manifest +WHERE NOT EXISTS ( + SELECT 1 FROM api_consumers WHERE name = 'ops-hub' +); + +WITH hub AS ( + SELECT id FROM hubs WHERE slug = 'ops-hub' +), seed(name, widget_type, capability_ref, view_context, policy_scope) AS ( + VALUES + ('Local Environment', 'ops-environment', 'ops:environment:local', 'ops-hub/environments/local', 'ops-local'), + ('CoulombCore Environment', 'ops-environment', 'ops:environment:coulombcore', 'ops-hub/environments/coulombcore', 'ops-transitional-prod'), + ('Railiance01 Environment', 'ops-environment', 'ops:environment:railiance01', 'ops-hub/environments/railiance01', 'ops-threephoenix'), + ('ThreePhoenix Production Environment', 'ops-environment', 'ops:environment:threephoenix-prod', 'ops-hub/environments/threephoenix-prod', 'ops-production'), + ('CoulombCore Host', 'ops-host', 'ops:host:coulombcore', 'ops-hub/hosts/coulombcore', 'ops-transitional-prod'), + ('Railiance01 Host', 'ops-host', 'ops:host:railiance01', 'ops-hub/hosts/railiance01', 'ops-threephoenix'), + ('Operations Service Catalog', 'ops-service-catalog', 'ops:service-catalog', 'ops-hub/service-catalog', 'ops-production'), + ('Gitea Service', 'ops-service', 'ops:service:gitea', 'ops-hub/services/gitea', 'ops-transitional-prod'), + ('State Hub Service', 'ops-service', 'ops:service:state-hub', 'ops-hub/services/state-hub', 'ops-local'), + ('Inter-Hub Service', 'ops-service', 'ops:service:inter-hub', 'ops-hub/services/inter-hub', 'ops-production'), + ('Gitea Registry Endpoint', 'ops-endpoint', 'ops:endpoint:gitea-registry', 'ops-hub/endpoints/gitea-registry', 'ops-registry'), + ('Gitea Registry Readiness', 'ops-readiness-gate', 'ops:readiness:gitea-registry', 'ops-hub/readiness/gitea-registry', 'ops-registry'), + ('State Hub Cluster Deploy Readiness', 'ops-readiness-gate', 'ops:readiness:state-hub-cluster-deploy', 'ops-hub/readiness/state-hub-cluster-deploy', 'ops-production'), + ('CoulombCore to ThreePhoenix Migration', 'ops-migration-wave', 'ops:migration:coulombcore-to-threephoenix', 'ops-hub/migrations/coulombcore-to-threephoenix', 'ops-threephoenix') +) +INSERT INTO widgets ( + hub_id, + name, + widget_type, + capability_ref, + view_context, + policy_scope, + status +) +SELECT + hub.id, + seed.name, + seed.widget_type, + seed.capability_ref, + seed.view_context, + seed.policy_scope, + 'active' +FROM seed CROSS JOIN hub +WHERE NOT EXISTS ( + SELECT 1 FROM widgets + WHERE hub_id = hub.id + AND capability_ref = seed.capability_ref +); + +COMMIT; diff --git a/wiki/ops-hub-manifest.draft.json b/wiki/ops-hub-manifest.draft.json new file mode 100644 index 0000000..025d8de --- /dev/null +++ b/wiki/ops-hub-manifest.draft.json @@ -0,0 +1,65 @@ +{ + "hub": { + "name": "Ops Hub", + "slug": "ops-hub", + "domain": "ops.coulomb.social", + "hubKind": "domain" + }, + "manifestVersion": "1.0", + "capabilityDescription": "VSM Operations / System 1 hub for operational truth and evidence. Metadata: hub_family=vsm; vsm_function=OPS; vsm_system=S1; scope=operational truth, service catalog, readiness, incidents, runbooks, migration waves, and evidence events.", + "contact": "operator", + "declaredWidgetTypes": [ + "ops-environment", + "ops-host", + "ops-cluster", + "ops-service", + "ops-service-catalog", + "ops-endpoint", + "ops-release", + "ops-backup-set", + "ops-secret-set", + "ops-runbook", + "ops-incident", + "ops-readiness-gate", + "ops-migration-wave", + "ops-risk" + ], + "declaredEventTypes": [ + "ops-inventory-registered", + "ops-inventory-updated", + "ops-service-discovered", + "ops-health-checked", + "ops-release-observed", + "ops-endpoint-verified", + "ops-backup-verified", + "ops-restore-tested", + "ops-runbook-executed", + "ops-drift-detected", + "ops-risk-raised", + "ops-risk-accepted", + "ops-readiness-gate-updated", + "ops-migration-gate-passed", + "ops-migration-gate-failed" + ], + "declaredAnnotationCategories": [ + "ops-drift", + "ops-service-catalog-gap", + "ops-backup-gap", + "ops-security-gap", + "ops-routing-gap", + "ops-secret-gap", + "ops-readiness-blocker", + "ops-migration-risk", + "ops-observability-gap", + "ops-recovery-gap" + ], + "declaredPolicyScopes": [ + "ops-local", + "ops-transitional-prod", + "ops-production", + "ops-threephoenix", + "ops-registry", + "ops-secrets", + "ops-backup-retention" + ] +} diff --git a/wiki/ops-hub-widgets.seed.json b/wiki/ops-hub-widgets.seed.json new file mode 100644 index 0000000..155b977 --- /dev/null +++ b/wiki/ops-hub-widgets.seed.json @@ -0,0 +1,100 @@ +[ + { + "name": "Local Environment", + "widgetType": "ops-environment", + "capabilityRef": "ops:environment:local", + "viewContext": "ops-hub/environments/local", + "policyScope": "ops-local" + }, + { + "name": "CoulombCore Environment", + "widgetType": "ops-environment", + "capabilityRef": "ops:environment:coulombcore", + "viewContext": "ops-hub/environments/coulombcore", + "policyScope": "ops-transitional-prod" + }, + { + "name": "Railiance01 Environment", + "widgetType": "ops-environment", + "capabilityRef": "ops:environment:railiance01", + "viewContext": "ops-hub/environments/railiance01", + "policyScope": "ops-threephoenix" + }, + { + "name": "ThreePhoenix Production Environment", + "widgetType": "ops-environment", + "capabilityRef": "ops:environment:threephoenix-prod", + "viewContext": "ops-hub/environments/threephoenix-prod", + "policyScope": "ops-production" + }, + { + "name": "CoulombCore Host", + "widgetType": "ops-host", + "capabilityRef": "ops:host:coulombcore", + "viewContext": "ops-hub/hosts/coulombcore", + "policyScope": "ops-transitional-prod" + }, + { + "name": "Railiance01 Host", + "widgetType": "ops-host", + "capabilityRef": "ops:host:railiance01", + "viewContext": "ops-hub/hosts/railiance01", + "policyScope": "ops-threephoenix" + }, + { + "name": "Operations Service Catalog", + "widgetType": "ops-service-catalog", + "capabilityRef": "ops:service-catalog", + "viewContext": "ops-hub/service-catalog", + "policyScope": "ops-production" + }, + { + "name": "Gitea Service", + "widgetType": "ops-service", + "capabilityRef": "ops:service:gitea", + "viewContext": "ops-hub/services/gitea", + "policyScope": "ops-transitional-prod" + }, + { + "name": "State Hub Service", + "widgetType": "ops-service", + "capabilityRef": "ops:service:state-hub", + "viewContext": "ops-hub/services/state-hub", + "policyScope": "ops-local" + }, + { + "name": "Inter-Hub Service", + "widgetType": "ops-service", + "capabilityRef": "ops:service:inter-hub", + "viewContext": "ops-hub/services/inter-hub", + "policyScope": "ops-production" + }, + { + "name": "Gitea Registry Endpoint", + "widgetType": "ops-endpoint", + "capabilityRef": "ops:endpoint:gitea-registry", + "viewContext": "ops-hub/endpoints/gitea-registry", + "policyScope": "ops-registry" + }, + { + "name": "Gitea Registry Readiness", + "widgetType": "ops-readiness-gate", + "capabilityRef": "ops:readiness:gitea-registry", + "viewContext": "ops-hub/readiness/gitea-registry", + "policyScope": "ops-registry" + }, + { + "name": "State Hub Cluster Deploy Readiness", + "widgetType": "ops-readiness-gate", + "capabilityRef": "ops:readiness:state-hub-cluster-deploy", + "viewContext": "ops-hub/readiness/state-hub-cluster-deploy", + "policyScope": "ops-production" + }, + { + "name": "CoulombCore to ThreePhoenix Migration", + "widgetType": "ops-migration-wave", + "capabilityRef": "ops:migration:coulombcore-to-threephoenix", + "viewContext": "ops-hub/migrations/coulombcore-to-threephoenix", + "policyScope": "ops-threephoenix" + } +] diff --git a/workplans/HF-WP-0001-establish-ops-hub-first-extension.md b/workplans/HF-WP-0001-establish-ops-hub-first-extension.md index 72fdb58..6d4c98e 100644 --- a/workplans/HF-WP-0001-establish-ops-hub-first-extension.md +++ b/workplans/HF-WP-0001-establish-ops-hub-first-extension.md @@ -365,7 +365,7 @@ Output: `Confirmed Bootstrap Path` section in this workplan. ```task id: HF-WP-0001-T02 -status: todo +status: blocked priority: high state_hub_task_id: "8e9bd9b2-54fc-49a4-8bb8-11c8577be48d" ``` @@ -388,13 +388,22 @@ fields as an Inter-Hub API/model gap. Done when: `ops-hub` appears in `/Hubs` and `/api/v2/hub-registry` after authentication, and a human can tell that it is the VSM Operations hub. +Blocked until: an authenticated Inter-Hub admin session or deployment-side +migration is available. + +Prepared artifacts: + +- `wiki/OpsHubBootstrapRunbook.md` +- `wiki/ops-hub-manifest.draft.json` +- `wiki/ops-hub-bootstrap.sql` + --- ### T03 — Activate the ops-hub capability manifest ```task id: HF-WP-0001-T03 -status: todo +status: blocked priority: high state_hub_task_id: "55f5aeed-21c3-4a83-bc78-f90f92c7d597" ``` @@ -421,13 +430,18 @@ Validation: Done when: the manifest status is `active` and no type conflicts remain. +Blocked until: the `ops-hub` row exists in Inter-Hub and an authenticated +operator or migration can create and activate the manifest. + +Prepared artifact: `wiki/ops-hub-manifest.draft.json`. + --- ### T04 — Create ops-hub API consumer and key ```task id: HF-WP-0001-T04 -status: todo +status: blocked priority: high state_hub_task_id: "ad08e729-8562-4a02-8bf6-dcdfebe430c8" ``` @@ -444,13 +458,17 @@ Store the key only in the operator secret store or local env file, never in Git. Done when: `POST /api/v2/token` can exchange the static key for a short-lived access token and `GET /api/v2/hub-registry` works with that token. +Blocked until: an authenticated operator creates the API key and stores the +full static key outside Git. The SQL fallback intentionally creates only the +consumer row, not the one-time visible secret. + --- ### T05 — Seed first governed ops widgets ```task id: HF-WP-0001-T05 -status: todo +status: blocked priority: high state_hub_task_id: "d303884d-d1f6-4fd0-a4ec-97afe6162164" ``` @@ -478,6 +496,13 @@ migration and record that as an API gap. Done when: the widgets appear under `ops-hub` and can accept interaction events and annotations. +Blocked until: `ops-hub` and its active manifest exist in Inter-Hub. + +Prepared artifacts: + +- `wiki/ops-hub-widgets.seed.json` +- `wiki/ops-hub-bootstrap.sql` + --- ### T06 — Build the first ops inventory artifact @@ -517,7 +542,7 @@ Output: `wiki/OpsHubInventory.md`. ```task id: HF-WP-0001-T07 -status: todo +status: blocked priority: medium state_hub_task_id: "ed3e0396-b16d-40c2-9519-e755ad6241eb" ``` @@ -544,6 +569,10 @@ Suggested event: Done when: the Gitea registry readiness event is visible in Inter-Hub and traceable back to the Railiance workplan. +Blocked until: the `ops-endpoint-gitea-registry` widget exists, the +`ops-endpoint-verified` event type is active, and an ops-hub API key is +available to the operator. + --- ### T08 — Define the ops-hub readiness gate model for ThreePhoenix migration @@ -604,7 +633,7 @@ needed. ```task id: HF-WP-0001-T10 -status: todo +status: in_progress priority: high target_repo: inter-hub state_hub_task_id: "7fa54508-7add-4885-8913-12edaadc4d92" @@ -641,6 +670,9 @@ Recommended Inter-Hub improvements: Done when: the next VSM hub can be created from a script using documented API calls and without direct DB access. +Linked Inter-Hub workplan: +`inter-hub/workplans/IHUB-WP-0019-vsm-hub-bootstrap-api.md`. + ## Initial Acceptance Criteria This workplan is complete when: