generated from coulomb/repo-seed
Align ops-hub activity-core contract vocabulary
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
# Ops Hub Activity-Core Widget Mapping
|
||||
|
||||
Date: 2026-06-15
|
||||
Updated: 2026-06-27
|
||||
|
||||
Workplan: `IHUB-WP-0022`
|
||||
|
||||
@@ -8,8 +9,21 @@ Workplan: `IHUB-WP-0022`
|
||||
|
||||
`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.
|
||||
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
|
||||
@@ -17,6 +31,9 @@ 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",
|
||||
@@ -24,87 +41,109 @@ the future Inter-Hub submission implementation should parse.
|
||||
"slug": "ops-hub",
|
||||
"id": "<ops-hub-uuid>"
|
||||
},
|
||||
"policyScope": "ops-evidence",
|
||||
"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-observed": {
|
||||
"ops-service-discovered": {
|
||||
"family": "services",
|
||||
"aggregateWidgetRef": "ops:service:aggregate"
|
||||
"selector": "service_id",
|
||||
"defaultWidgetRef": "ops:service-catalog"
|
||||
},
|
||||
"ops-endpoint-verified": {
|
||||
"family": "endpoints",
|
||||
"aggregateWidgetRef": "ops:endpoint:aggregate"
|
||||
},
|
||||
"ops-access-path-checked": {
|
||||
"family": "accessPaths",
|
||||
"aggregateWidgetRef": "ops:access-path:aggregate"
|
||||
"selector": "<service_id>:<endpoint_id>",
|
||||
"defaultWidgetRef": "ops:endpoint:gitea-registry"
|
||||
},
|
||||
"ops-backup-verified": {
|
||||
"family": "backups",
|
||||
"aggregateWidgetRef": "ops:backup:aggregate"
|
||||
"selector": "<service_id>:<backing_store_ref>",
|
||||
"defaultWidgetRef": "ops:backup-set:aggregate",
|
||||
"requiresSeed": true
|
||||
},
|
||||
"ops-inventory-drift": {
|
||||
"family": "drift",
|
||||
"aggregateWidgetRef": "ops:drift:aggregate"
|
||||
"ops-drift-detected": {
|
||||
"family": "readiness",
|
||||
"selector": "<service_id>:<inventory_object_id>",
|
||||
"defaultWidgetRef": "ops:readiness:gitea-registry"
|
||||
}
|
||||
},
|
||||
"widgets": {
|
||||
"aggregate": {
|
||||
"ops:service:aggregate": {
|
||||
"byRef": {
|
||||
"ops:service-catalog": {
|
||||
"widgetId": "<uuid>",
|
||||
"widgetType": "ops-service-card",
|
||||
"name": "Ops Service Evidence Intake"
|
||||
"widgetType": "ops-service-catalog",
|
||||
"policyScope": "ops-production",
|
||||
"name": "Operations Service Catalog"
|
||||
},
|
||||
"ops:endpoint:aggregate": {
|
||||
"ops:service:state-hub": {
|
||||
"widgetId": "<uuid>",
|
||||
"widgetType": "ops-endpoint-card",
|
||||
"name": "Ops Endpoint Evidence Intake"
|
||||
"widgetType": "ops-service",
|
||||
"policyScope": "ops-local",
|
||||
"name": "State Hub Service"
|
||||
},
|
||||
"ops:access-path:aggregate": {
|
||||
"ops:service:gitea": {
|
||||
"widgetId": "<uuid>",
|
||||
"widgetType": "ops-access-path-card",
|
||||
"name": "Ops Access Path Evidence Intake"
|
||||
"widgetType": "ops-service",
|
||||
"policyScope": "ops-transitional-prod",
|
||||
"name": "Gitea Service"
|
||||
},
|
||||
"ops:backup:aggregate": {
|
||||
"ops:service:inter-hub": {
|
||||
"widgetId": "<uuid>",
|
||||
"widgetType": "ops-backup-card",
|
||||
"name": "Ops Backup Evidence Intake"
|
||||
"widgetType": "ops-service",
|
||||
"policyScope": "ops-production",
|
||||
"name": "Inter-Hub Service"
|
||||
},
|
||||
"ops:drift:aggregate": {
|
||||
"ops:endpoint:gitea-registry": {
|
||||
"widgetId": "<uuid>",
|
||||
"widgetType": "ops-drift-card",
|
||||
"name": "Ops Inventory Drift Evidence Intake"
|
||||
"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",
|
||||
"widgetId": "<uuid>"
|
||||
}
|
||||
"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",
|
||||
"widgetId": "<uuid>"
|
||||
}
|
||||
},
|
||||
"accessPaths": {
|
||||
"gitea:gitea-access-1": {
|
||||
"widgetRef": "ops:access-path:gitea-access-1",
|
||||
"widgetId": "<uuid>"
|
||||
}
|
||||
"gitea:gitea-oci-registry": { "widgetRef": "ops:endpoint:gitea-registry" }
|
||||
},
|
||||
"backups": {
|
||||
"gitea:database:gitea-db": {
|
||||
"widgetRef": "ops:backup:gitea-db",
|
||||
"widgetId": "<uuid>"
|
||||
}
|
||||
"gitea:database:gitea-db": { "widgetRef": "ops:backup-set:aggregate" }
|
||||
},
|
||||
"drift": {
|
||||
"gitea:gitea-oci-registry": {
|
||||
"widgetRef": "ops:drift:gitea-oci-registry",
|
||||
"widgetId": "<uuid>"
|
||||
}
|
||||
"readiness": {
|
||||
"gitea:gitea-oci-registry": { "widgetRef": "ops:readiness:gitea-registry" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,44 +154,60 @@ the future Inter-Hub submission implementation should parse.
|
||||
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>"]`.
|
||||
`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>"]`.
|
||||
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.
|
||||
`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
|
||||
|
||||
The initial aggregate widgets should be seeded before activity-core is pointed
|
||||
at Inter-Hub:
|
||||
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 | 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 |
|
||||
| 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 the aggregate
|
||||
widget as the fallback.
|
||||
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.
|
||||
- Removing a widget mapping is a breaking change for that selector unless the
|
||||
aggregate fallback remains present.
|
||||
- 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
|
||||
@@ -160,7 +215,9 @@ widget as the fallback.
|
||||
|
||||
## Minimum Valid Mapping
|
||||
|
||||
For the first live smoke, an aggregate-only mapping is enough:
|
||||
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
|
||||
{
|
||||
@@ -169,37 +226,30 @@ For the first live smoke, an aggregate-only mapping is enough:
|
||||
"slug": "ops-hub",
|
||||
"id": "<ops-hub-uuid>"
|
||||
},
|
||||
"policyScope": "ops-evidence",
|
||||
"defaultViewContext": "ops-inventory-probe",
|
||||
"eventAliases": {
|
||||
"ops-service-observed": "ops-service-discovered",
|
||||
"ops-inventory-drift": "ops-drift-detected"
|
||||
},
|
||||
"events": {
|
||||
"ops-service-observed": {
|
||||
"ops-service-discovered": {
|
||||
"family": "services",
|
||||
"aggregateWidgetRef": "ops:service:aggregate"
|
||||
"defaultWidgetRef": "ops:service-catalog"
|
||||
},
|
||||
"ops-endpoint-verified": {
|
||||
"family": "endpoints",
|
||||
"aggregateWidgetRef": "ops:endpoint:aggregate"
|
||||
"defaultWidgetRef": "ops:endpoint:gitea-registry"
|
||||
},
|
||||
"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"
|
||||
"ops-drift-detected": {
|
||||
"family": "readiness",
|
||||
"defaultWidgetRef": "ops:readiness:gitea-registry"
|
||||
}
|
||||
},
|
||||
"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>" }
|
||||
"byRef": {
|
||||
"ops:service-catalog": { "widgetId": "<uuid>" },
|
||||
"ops:endpoint:gitea-registry": { "widgetId": "<uuid>" },
|
||||
"ops:readiness:gitea-registry": { "widgetId": "<uuid>" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user