6.2 KiB
Ops Hub Activity-Core Widget Mapping
Date: 2026-06-15
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 logical references, but it must never contain
OPS_HUB_KEY or any operator credential.
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
{
"version": "ops-hub.activity-core.widget-mapping.v1",
"hub": {
"slug": "ops-hub",
"id": "<ops-hub-uuid>"
},
"policyScope": "ops-evidence",
"defaultViewContext": "ops-inventory-probe",
"events": {
"ops-service-observed": {
"family": "services",
"aggregateWidgetRef": "ops:service:aggregate"
},
"ops-endpoint-verified": {
"family": "endpoints",
"aggregateWidgetRef": "ops:endpoint:aggregate"
},
"ops-access-path-checked": {
"family": "accessPaths",
"aggregateWidgetRef": "ops:access-path:aggregate"
},
"ops-backup-verified": {
"family": "backups",
"aggregateWidgetRef": "ops:backup:aggregate"
},
"ops-inventory-drift": {
"family": "drift",
"aggregateWidgetRef": "ops:drift:aggregate"
}
},
"widgets": {
"aggregate": {
"ops:service:aggregate": {
"widgetId": "<uuid>",
"widgetType": "ops-service-card",
"name": "Ops Service Evidence Intake"
},
"ops:endpoint:aggregate": {
"widgetId": "<uuid>",
"widgetType": "ops-endpoint-card",
"name": "Ops Endpoint Evidence Intake"
},
"ops:access-path:aggregate": {
"widgetId": "<uuid>",
"widgetType": "ops-access-path-card",
"name": "Ops Access Path Evidence Intake"
},
"ops:backup:aggregate": {
"widgetId": "<uuid>",
"widgetType": "ops-backup-card",
"name": "Ops Backup Evidence Intake"
},
"ops:drift:aggregate": {
"widgetId": "<uuid>",
"widgetType": "ops-drift-card",
"name": "Ops Inventory Drift Evidence Intake"
}
},
"services": {
"state-hub": {
"widgetRef": "ops:service:state-hub",
"widgetId": "<uuid>"
}
},
"endpoints": {
"gitea:gitea-oci-registry": {
"widgetRef": "ops:endpoint:gitea-registry",
"widgetId": "<uuid>"
}
},
"accessPaths": {
"gitea:gitea-access-1": {
"widgetRef": "ops:access-path:gitea-access-1",
"widgetId": "<uuid>"
}
},
"backups": {
"gitea:database:gitea-db": {
"widgetRef": "ops:backup:gitea-db",
"widgetId": "<uuid>"
}
},
"drift": {
"gitea:gitea-oci-registry": {
"widgetRef": "ops:drift:gitea-oci-registry",
"widgetId": "<uuid>"
}
}
}
}
Selector Rules
Activity-core should choose a widget in this order:
- If the evidence payload carries a
widget_refand that reference exists in the mapping, use it. - For
ops-service-observed, useservices["<service_id>"]. - For
ops-endpoint-verified, useendpoints["<service_id>:<endpoint_id>"]. - For
ops-access-path-checked, useaccessPaths["<service_id>:<access_path_id>"]. - For
ops-backup-verified, usebackups["<service_id>:<backing_store_ref>"]. - For
ops-inventory-drift, usedrift["<service_id>:<inventory_object_id>"]. - If no entity-specific widget exists, use the event's aggregate widget.
- If neither an entity-specific nor aggregate widget exists, skip Inter-Hub submission with a non-secret result that names the missing selector.
Bootstrap Widget Names
The initial aggregate widgets should be seeded before activity-core is pointed at Inter-Hub:
| Widget ref | Widget type | Suggested name |
|---|---|---|
ops:service:aggregate |
ops-service-card |
Ops Service Evidence Intake |
ops:endpoint:aggregate |
ops-endpoint-card |
Ops Endpoint Evidence Intake |
ops:access-path:aggregate |
ops-access-path-card |
Ops Access Path Evidence Intake |
ops:backup:aggregate |
ops-backup-card |
Ops Backup Evidence Intake |
ops:drift:aggregate |
ops-drift-card |
Ops Inventory Drift Evidence Intake |
Per-entity widgets may be seeded later without changing the event contract. When a per-entity widget is added, update the mapping and keep the aggregate widget as the fallback.
Compatibility Rules
versionis required. Reject unknown major versions.- Consumers must tolerate additional fields.
- Widget UUIDs may rotate, but
widgetRefvalues should remain stable. - Removing a widget mapping is a breaking change for that selector unless the aggregate fallback 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, an aggregate-only mapping is enough:
{
"version": "ops-hub.activity-core.widget-mapping.v1",
"hub": {
"slug": "ops-hub",
"id": "<ops-hub-uuid>"
},
"policyScope": "ops-evidence",
"defaultViewContext": "ops-inventory-probe",
"events": {
"ops-service-observed": {
"family": "services",
"aggregateWidgetRef": "ops:service:aggregate"
},
"ops-endpoint-verified": {
"family": "endpoints",
"aggregateWidgetRef": "ops:endpoint:aggregate"
},
"ops-access-path-checked": {
"family": "accessPaths",
"aggregateWidgetRef": "ops:access-path:aggregate"
},
"ops-backup-verified": {
"family": "backups",
"aggregateWidgetRef": "ops:backup:aggregate"
},
"ops-inventory-drift": {
"family": "drift",
"aggregateWidgetRef": "ops:drift:aggregate"
}
},
"widgets": {
"aggregate": {
"ops:service:aggregate": { "widgetId": "<uuid>" },
"ops:endpoint:aggregate": { "widgetId": "<uuid>" },
"ops:access-path:aggregate": { "widgetId": "<uuid>" },
"ops:backup:aggregate": { "widgetId": "<uuid>" },
"ops:drift:aggregate": { "widgetId": "<uuid>" }
}
}
}