generated from coulomb/repo-seed
257 lines
8.9 KiB
Markdown
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>" }
|
|
}
|
|
}
|
|
}
|
|
```
|