generated from coulomb/repo-seed
207 lines
6.2 KiB
Markdown
207 lines
6.2 KiB
Markdown
# 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
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
1. If the evidence payload carries a `widget_ref` and that reference exists in
|
|
the mapping, use it.
|
|
2. For `ops-service-observed`, use `services["<service_id>"]`.
|
|
3. For `ops-endpoint-verified`, use
|
|
`endpoints["<service_id>:<endpoint_id>"]`.
|
|
4. For `ops-access-path-checked`, use
|
|
`accessPaths["<service_id>:<access_path_id>"]`.
|
|
5. For `ops-backup-verified`, use
|
|
`backups["<service_id>:<backing_store_ref>"]`.
|
|
6. For `ops-inventory-drift`, use
|
|
`drift["<service_id>:<inventory_object_id>"]`.
|
|
7. If no entity-specific widget exists, use the event's aggregate widget.
|
|
8. 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
|
|
|
|
- `version` is required. Reject unknown major versions.
|
|
- Consumers must tolerate additional fields.
|
|
- Widget UUIDs may rotate, but `widgetRef` values 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_KEY` must remain Secret-only.
|
|
|
|
## Minimum Valid Mapping
|
|
|
|
For the first live smoke, an aggregate-only mapping is enough:
|
|
|
|
```json
|
|
{
|
|
"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>" }
|
|
}
|
|
}
|
|
}
|
|
```
|