Files
inter-hub/docs/contracts/ops-hub-activity-core-mapping.md

257 lines
8.9 KiB
Markdown

# 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-discovered` and
`ops-drift-detected`;
- widget types use live seed types such as `ops-service`, `ops-endpoint`,
`ops-backup-set`, `ops-readiness-gate`, and `ops-risk`;
- policy scopes use the declared ops scopes; there is no `ops-evidence` scope
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.
```json
{
"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:
1. If the evidence payload carries a `widget_ref` and that reference exists in
`widgets.byRef`, use it.
2. If the event name is present in `eventAliases`, translate it to the live
event name before selector lookup and submission.
3. For `ops-service-discovered`, use `services["<service_id>"]`; otherwise use
`ops:service-catalog`.
4. For `ops-endpoint-verified`, use
`endpoints["<service_id>:<endpoint_id>"]`; otherwise skip or use a
deliberately seeded endpoint aggregate.
5. For `ops-backup-verified`, use
`backups["<service_id>:<backing_store_ref>"]`; otherwise skip until a
backup-set widget is seeded.
6. For `ops-drift-detected`, use
`readiness["<service_id>:<inventory_object_id>"]`; otherwise use a seeded
readiness/risk widget chosen by the operator.
7. 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.
8. 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
- `version` is required. Reject unknown major versions.
- Consumers must tolerate additional fields.
- Widget UUIDs may rotate, but `widgetRef` values 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_KEY` must 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.
```json
{
"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>" }
}
}
}
```