8.9 KiB
Ops Hub Activity-Core Widget Mapping
Date: 2026-06-15 Updated: 2026-06-27
Workplan: IHUB-WP-0022
Purpose
OPS_HUB_WIDGET_MAPPING tells activity-core which Inter-Hub widget receives
each ops evidence event. The value must be non-secret JSON. It may contain
Inter-Hub widget UUIDs and stable logical refs, but it must never contain
OPS_HUB_KEY, bearer tokens, cookies, or any operator credential.
This revision aligns the contract to the live ops-hub manifest vocabulary
observed on 2026-06-27 and to the source seed files in ops-hub:
- event types use the live registry names such as
ops-service-discoveredandops-drift-detected; - widget types use live seed types such as
ops-service,ops-endpoint,ops-backup-set,ops-readiness-gate, andops-risk; - policy scopes use the declared ops scopes; there is no
ops-evidencescope in the live manifest; - access-path evidence remains deferred until ops-hub explicitly adds an access-path event and widget type or activity-core maps it to a supported readiness/risk event by decision.
Activity-core currently only checks that a mapping value is present before
returning inter_hub_sink_deferred. This document defines the contract that
the future Inter-Hub submission implementation should parse.
Versioned Shape
The shape remains version v1; this is a compatibility revision of the
vocabulary inside the mapping, not a structural breaking change.
{
"version": "ops-hub.activity-core.widget-mapping.v1",
"hub": {
"slug": "ops-hub",
"id": "<ops-hub-uuid>"
},
"defaultViewContext": "ops-inventory-probe",
"allowedPolicyScopes": [
"ops-local",
"ops-transitional-prod",
"ops-production",
"ops-threephoenix",
"ops-registry",
"ops-secrets",
"ops-backup-retention"
],
"eventAliases": {
"ops-service-observed": "ops-service-discovered",
"ops-inventory-drift": "ops-drift-detected"
},
"deferredEvents": {
"ops-access-path-checked": {
"reason": "not_declared_by_live_ops_hub_manifest",
"fallback": "state-hub-progress",
"decisionNeeded": "add ops-access-path vocabulary or map access-path evidence to ops-readiness-gate-updated/ops-risk-raised"
}
},
"events": {
"ops-service-discovered": {
"family": "services",
"selector": "service_id",
"defaultWidgetRef": "ops:service-catalog"
},
"ops-endpoint-verified": {
"family": "endpoints",
"selector": "<service_id>:<endpoint_id>",
"defaultWidgetRef": "ops:endpoint:gitea-registry"
},
"ops-backup-verified": {
"family": "backups",
"selector": "<service_id>:<backing_store_ref>",
"defaultWidgetRef": "ops:backup-set:aggregate",
"requiresSeed": true
},
"ops-drift-detected": {
"family": "readiness",
"selector": "<service_id>:<inventory_object_id>",
"defaultWidgetRef": "ops:readiness:gitea-registry"
}
},
"widgets": {
"byRef": {
"ops:service-catalog": {
"widgetId": "<uuid>",
"widgetType": "ops-service-catalog",
"policyScope": "ops-production",
"name": "Operations Service Catalog"
},
"ops:service:state-hub": {
"widgetId": "<uuid>",
"widgetType": "ops-service",
"policyScope": "ops-local",
"name": "State Hub Service"
},
"ops:service:gitea": {
"widgetId": "<uuid>",
"widgetType": "ops-service",
"policyScope": "ops-transitional-prod",
"name": "Gitea Service"
},
"ops:service:inter-hub": {
"widgetId": "<uuid>",
"widgetType": "ops-service",
"policyScope": "ops-production",
"name": "Inter-Hub Service"
},
"ops:endpoint:gitea-registry": {
"widgetId": "<uuid>",
"widgetType": "ops-endpoint",
"policyScope": "ops-registry",
"name": "Gitea Registry Endpoint"
},
"ops:readiness:gitea-registry": {
"widgetId": "<uuid>",
"widgetType": "ops-readiness-gate",
"policyScope": "ops-registry",
"name": "Gitea Registry Readiness"
},
"ops:backup-set:aggregate": {
"widgetId": "<uuid>",
"widgetType": "ops-backup-set",
"policyScope": "ops-backup-retention",
"name": "Ops Backup Evidence Intake",
"requiresSeed": true
}
},
"services": {
"state-hub": { "widgetRef": "ops:service:state-hub" },
"gitea": { "widgetRef": "ops:service:gitea" },
"inter-hub": { "widgetRef": "ops:service:inter-hub" }
},
"endpoints": {
"gitea:gitea-oci-registry": { "widgetRef": "ops:endpoint:gitea-registry" }
},
"backups": {
"gitea:database:gitea-db": { "widgetRef": "ops:backup-set:aggregate" }
},
"readiness": {
"gitea:gitea-oci-registry": { "widgetRef": "ops:readiness:gitea-registry" }
}
}
}
Selector Rules
Activity-core should choose a widget in this order:
- If the evidence payload carries a
widget_refand that reference exists inwidgets.byRef, use it. - If the event name is present in
eventAliases, translate it to the live event name before selector lookup and submission. - For
ops-service-discovered, useservices["<service_id>"]; otherwise useops:service-catalog. - For
ops-endpoint-verified, useendpoints["<service_id>:<endpoint_id>"]; otherwise skip or use a deliberately seeded endpoint aggregate. - For
ops-backup-verified, usebackups["<service_id>:<backing_store_ref>"]; otherwise skip until a backup-set widget is seeded. - For
ops-drift-detected, usereadiness["<service_id>:<inventory_object_id>"]; otherwise use a seeded readiness/risk widget chosen by the operator. - For
ops-access-path-checked, do not submit to Inter-Hub yet. Keep State Hub fallback evidence unless ops-hub adds access-path vocabulary or an explicit decision maps access-path checks to readiness/risk events. - If no selector can resolve to a widget id, skip Inter-Hub submission with a non-secret result naming the missing selector.
Bootstrap Widget Names
These refs are already present in the current ops-hub seed file and should be
looked up in the protected widget registry during the authenticated bootstrap:
| Widget ref | Widget type | Name |
|---|---|---|
ops:service-catalog |
ops-service-catalog |
Operations Service Catalog |
ops:service:gitea |
ops-service |
Gitea Service |
ops:service:state-hub |
ops-service |
State Hub Service |
ops:service:inter-hub |
ops-service |
Inter-Hub Service |
ops:endpoint:gitea-registry |
ops-endpoint |
Gitea Registry Endpoint |
ops:readiness:gitea-registry |
ops-readiness-gate |
Gitea Registry Readiness |
Additional seed required before backup evidence can be smoke-tested:
| Widget ref | Widget type | Suggested name | Policy scope |
|---|---|---|---|
ops:backup-set:aggregate |
ops-backup-set |
Ops Backup Evidence Intake | ops-backup-retention |
Per-entity widgets may be seeded later without changing the event contract. When a per-entity widget is added, update the mapping and keep a deliberate fallback only when the fallback widget actually exists in the protected widget registry.
Compatibility Rules
versionis required. Reject unknown major versions.- Consumers must tolerate additional fields.
- Widget UUIDs may rotate, but
widgetRefvalues should remain stable. - Event aliases are a transition aid only. New activity-core submissions should use live registry event names directly.
- Removing a widget mapping is a breaking change for that selector unless a verified fallback widget remains present.
- Mapping updates must be deployed before activity-core starts sending events that depend on the new selectors.
- The mapping is non-secret and may be stored in a ConfigMap or environment
variable.
OPS_HUB_KEYmust remain Secret-only.
Minimum Valid Mapping
For the first live smoke, use only live registry events and widgets that are known from the seed contract and can be confirmed through an authenticated widget lookup. Backup and access-path evidence can remain deferred.
{
"version": "ops-hub.activity-core.widget-mapping.v1",
"hub": {
"slug": "ops-hub",
"id": "<ops-hub-uuid>"
},
"defaultViewContext": "ops-inventory-probe",
"eventAliases": {
"ops-service-observed": "ops-service-discovered",
"ops-inventory-drift": "ops-drift-detected"
},
"events": {
"ops-service-discovered": {
"family": "services",
"defaultWidgetRef": "ops:service-catalog"
},
"ops-endpoint-verified": {
"family": "endpoints",
"defaultWidgetRef": "ops:endpoint:gitea-registry"
},
"ops-drift-detected": {
"family": "readiness",
"defaultWidgetRef": "ops:readiness:gitea-registry"
}
},
"widgets": {
"byRef": {
"ops:service-catalog": { "widgetId": "<uuid>" },
"ops:endpoint:gitea-registry": { "widgetId": "<uuid>" },
"ops:readiness:gitea-registry": { "widgetId": "<uuid>" }
}
}
}