Align ops-hub activity-core contract vocabulary

This commit is contained in:
2026-06-27 09:58:52 +02:00
parent b90320e4e7
commit 81cb73f593
4 changed files with 289 additions and 174 deletions

View File

@@ -1,6 +1,7 @@
# Ops Hub Activity-Core Event Payloads
Date: 2026-06-15
Updated: 2026-06-27
Workplan: `IHUB-WP-0022`
@@ -33,6 +34,28 @@ Each request body must use the Inter-Hub v2 interaction-event shape:
Inter-Hub sets `occurredAt` on receipt. Activity-core must send the actual
probe timestamp as `metadata.attributes.observed_at`.
## Live Vocabulary Alignment
Use event names declared by the live ops-hub manifest:
- `ops-service-discovered`
- `ops-endpoint-verified`
- `ops-backup-verified`
- `ops-drift-detected`
Transitional aliases may be accepted in local mapping logic but should not be
submitted to Inter-Hub:
| Earlier activity-core name | Live event name |
| --- | --- |
| `ops-service-observed` | `ops-service-discovered` |
| `ops-inventory-drift` | `ops-drift-detected` |
`ops-access-path-checked` is deferred because the live manifest has no
access-path event type or access-path widget type. Keep State Hub fallback
posting for access-path evidence until ops-hub adds that vocabulary or an
operator decision maps it to `ops-readiness-gate-updated` or `ops-risk-raised`.
## Shared Rules
- `widgetId` must be a UUID for an existing ops-hub widget.
@@ -41,7 +64,7 @@ probe timestamp as `metadata.attributes.observed_at`.
be declared by that manifest.
- `viewContext` should be `ops-inventory-probe` unless a more specific context
is useful, such as `ops-inventory-probe/endpoints`.
- `metadata.type` must match the Inter-Hub `eventType`.
- `metadata.type` must match the Inter-Hub `eventType` after alias translation.
- `metadata.version` must match the activity-core event definition version.
- `metadata.publisher` must be `activity-core`.
- `metadata.attributes.idempotency_key` is required, even though Inter-Hub does
@@ -69,26 +92,27 @@ Use `reason` for compact machine-readable explanations, for example:
- `backup_probe_not_implemented`
- `missing_endpoint`
## Example: Service Observed
## Example: Service Discovered
```json
{
"widgetId": "<service-widget-uuid>",
"eventType": "ops-service-observed",
"widgetId": "<service-or-catalog-widget-uuid>",
"eventType": "ops-service-discovered",
"viewContext": "ops-inventory-probe/services",
"metadata": {
"type": "ops-service-observed",
"type": "ops-service-discovered",
"version": "1.0",
"publisher": "activity-core",
"attributes": {
"activity_core_run_id": "12345678-aaaa-bbbb-cccc-123456789abc",
"idempotency_key": "12345678-aaaa-bbbb-cccc-123456789abc:state-hub:ops-service-observed",
"idempotency_key": "12345678-aaaa-bbbb-cccc-123456789abc:state-hub:ops-service-discovered",
"service_id": "state-hub",
"service_name": "State Hub",
"environment": "local",
"lifecycle_state": "observed",
"lifecycle_state": "discovered",
"observed_status": "ok",
"observed_at": "2026-06-05T10:15:01Z"
"observed_at": "2026-06-05T10:15:01Z",
"widget_ref": "ops:service:state-hub"
}
}
}
@@ -124,37 +148,26 @@ Use `reason` for compact machine-readable explanations, for example:
}
```
## Example: Access Path Checked
## Deferred: Access Path Checked
```json
{
"widgetId": "<access-path-widget-uuid>",
"eventType": "ops-access-path-checked",
"viewContext": "ops-inventory-probe/access-paths",
"metadata": {
"type": "ops-access-path-checked",
"version": "1.0",
"publisher": "activity-core",
"attributes": {
"activity_core_run_id": "12345678-aaaa-bbbb-cccc-123456789abc",
"idempotency_key": "12345678-aaaa-bbbb-cccc-123456789abc:gitea:gitea-access-1:ops-access-path-checked",
"service_id": "gitea",
"access_path_id": "gitea-access-1",
"access_path_type": "k8s",
"declared_status": "unknown",
"observed_status": "skipped",
"reason": "unsupported_access_path_type",
"observed_at": "2026-06-05T10:15:01Z"
}
}
}
```
Do not submit `ops-access-path-checked` to Inter-Hub while the live ops-hub
manifest lacks that event type and a matching widget type. Activity-core should
continue to include compact access-path detail in State Hub fallback summaries
and return a non-secret deferred sink result for Inter-Hub.
If the operator chooses to represent access-path state as readiness or risk,
create a separate mapping decision and send the supported event type instead of
silently rewriting access-path evidence.
## Example: Backup Verified
This event is declared by the live manifest, but it needs a target
`ops-backup-set` widget such as `ops:backup-set:aggregate` before the smoke can
submit it.
```json
{
"widgetId": "<backup-widget-uuid>",
"widgetId": "<backup-set-widget-uuid>",
"eventType": "ops-backup-verified",
"viewContext": "ops-inventory-probe/backups",
"metadata": {
@@ -170,26 +183,27 @@ Use `reason` for compact machine-readable explanations, for example:
"restore_verified": false,
"observed_status": "skipped",
"reason": "backup_probe_not_implemented",
"observed_at": "2026-06-05T10:15:01Z"
"observed_at": "2026-06-05T10:15:01Z",
"widget_ref": "ops:backup-set:aggregate"
}
}
}
```
## Example: Inventory Drift
## Example: Drift Detected
```json
{
"widgetId": "<drift-widget-uuid>",
"eventType": "ops-inventory-drift",
"widgetId": "<readiness-or-risk-widget-uuid>",
"eventType": "ops-drift-detected",
"viewContext": "ops-inventory-probe/drift",
"metadata": {
"type": "ops-inventory-drift",
"type": "ops-drift-detected",
"version": "1.0",
"publisher": "activity-core",
"attributes": {
"activity_core_run_id": "12345678-aaaa-bbbb-cccc-123456789abc",
"idempotency_key": "12345678-aaaa-bbbb-cccc-123456789abc:gitea:gitea-oci-registry:ops-inventory-drift",
"idempotency_key": "12345678-aaaa-bbbb-cccc-123456789abc:gitea:gitea-oci-registry:ops-drift-detected",
"service_id": "gitea",
"inventory_object_id": "gitea-oci-registry",
"drift_kind": "status_mismatch",
@@ -197,7 +211,8 @@ Use `reason` for compact machine-readable explanations, for example:
"observed_summary": "status_code=200",
"observed_status": "degraded",
"reason": "expected_status_mismatch",
"observed_at": "2026-06-05T10:15:01Z"
"observed_at": "2026-06-05T10:15:01Z",
"widget_ref": "ops:readiness:gitea-registry"
}
}
}
@@ -208,12 +223,12 @@ Use `reason` for compact machine-readable explanations, for example:
Activity-core should treat these as configuration or rollout errors:
| Error | Meaning | Recovery |
|---|---|---|
| --- | --- | --- |
| `401` | Missing or invalid `OPS_HUB_KEY` | Check Secret provisioning; do not log the key. |
| `422` with `unregistered_event_type` | Event type not in Inter-Hub registry | Activate the ops-hub manifest vocabulary. |
| `422` with `unregistered_event_type` | Event type not in Inter-Hub registry | Use a live manifest event or activate a corrected ops-hub manifest. |
| `422` with `event_type_not_in_manifest` | Runtime consumer manifest does not declare the event | Bind the consumer to the active manifest or activate a corrected manifest. |
| `422` with `Widget not found` | Mapping points at a missing widget | Refresh `OPS_HUB_WIDGET_MAPPING`. |
| `422` with `unregistered_policy_scope` during widget seed | Policy scope is absent | Declare and activate `ops-evidence`. |
| `422` with `Widget not found` | Mapping points at a missing widget | Refresh `OPS_HUB_WIDGET_MAPPING` from authenticated widget lookup. |
| `422` with `unregistered_policy_scope` during widget seed | Policy scope is absent | Use one of the declared ops scopes, not the old `ops-evidence` proposal. |
For the first activity-core slice, a failed Inter-Hub submission should not
fail the whole probe if State Hub fallback posting succeeds. It should return a

View File

@@ -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>" }
}
}
}

View File

@@ -1,6 +1,7 @@
# Ops Hub Evidence Intake - Current State
Date: 2026-06-15
Updated: 2026-06-27
Workplan: `IHUB-WP-0022`
@@ -17,6 +18,26 @@ slice is therefore contract-first:
- wait on live ops-hub manifest/widgets, key provisioning, and production
smoke before enabling per-entity Inter-Hub submission.
## 2026-06-27 Live Vocabulary Alignment
Public production probes now show an `ops-hub` row and public registry
vocabulary on `https://hub.coulomb.social`. The live ops-hub seed vocabulary
supersedes the early activity-core proposal for several names:
| Early proposal | Live target |
| --- | --- |
| `ops-service-observed` | `ops-service-discovered` |
| `ops-inventory-drift` | `ops-drift-detected` |
| `ops-access-path-checked` | Deferred; no live access-path event or widget type |
| `ops-evidence` policy scope | Use declared scopes such as `ops-production`, `ops-registry`, or `ops-backup-retention` |
| card-style widget types | Use live widget types such as `ops-service`, `ops-endpoint`, `ops-backup-set`, `ops-readiness-gate`, and `ops-risk` |
The contract docs have been revised to target the live seed vocabulary and to
keep access-path evidence on the State Hub fallback path until ops-hub either
adds access-path vocabulary or records an explicit readiness/risk mapping
decision.
## Inter-Hub API Surface
The current repo supports the necessary primitives through `/api/v2`.
@@ -119,30 +140,39 @@ Known gates before per-entity Inter-Hub submission can be treated as live:
## Recommended Manifest Vocabulary
Use one policy scope for the first slice:
Use the live ops-hub manifest vocabulary rather than the early proposal names.
The current target is:
- `ops-evidence`
Policy scopes:
Use one annotation category:
- `ops-local`
- `ops-transitional-prod`
- `ops-production`
- `ops-threephoenix`
- `ops-registry`
- `ops-secrets`
- `ops-backup-retention`
- `ops-risk`
Widget types for activity-core evidence:
Use these widget types unless the operator prefers to keep a smaller aggregate
surface:
- `ops-service-catalog`
- `ops-service`
- `ops-endpoint`
- `ops-backup-set`
- `ops-readiness-gate`
- `ops-risk` when risk-specific drift representation is needed
- `ops-service-card`
- `ops-endpoint-card`
- `ops-access-path-card`
- `ops-backup-card`
- `ops-drift-card`
Event types for the first Inter-Hub activity-core slice:
Use the activity-core event types exactly as published:
- `ops-service-observed`
- `ops-service-discovered`
- `ops-endpoint-verified`
- `ops-access-path-checked`
- `ops-backup-verified`
- `ops-inventory-drift`
- `ops-backup-verified` after a backup-set widget exists
- `ops-drift-detected`
Keep `ops-access-path-checked` deferred until ops-hub declares access-path
vocabulary or a separate decision maps those checks to readiness/risk events.
Do not seed the old `ops-evidence` policy scope unless the manifest is
explicitly expanded to include it.
## Open Questions

View File

@@ -8,7 +8,7 @@ status: active
owner: codex
topic_slug: inter_hub
created: "2026-06-15"
updated: "2026-06-16"
updated: "2026-06-27"
planning_priority: high
planning_order: 22
related_repos:
@@ -94,20 +94,22 @@ closed.
## Proposed Evidence Vocabulary
Activity-core has already declared the event contracts it wants to send:
The original activity-core suggestion used five evidence names. Production
ops-hub now exposes a live seed vocabulary, so the Inter-Hub activation target
is the compatibility mapping below rather than the early names verbatim:
| Event type | Suggested widget family | Purpose |
|---|---|---|
| `ops-service-observed` | service inventory | Record that a service exists and was observed. |
| `ops-endpoint-verified` | endpoint inventory | Record endpoint reachability, auth challenge, or health verification. |
| `ops-access-path-checked` | access path inventory | Record operator or service access path verification. |
| `ops-backup-verified` | backup inventory | Record backup presence, recency, or restore-drill evidence. |
| `ops-inventory-drift` | drift inventory | Record drift between expected and observed operations inventory. |
| Original evidence intent | Live Inter-Hub target | Widget family | Purpose |
|---|---|---|---|
| `ops-service-observed` | `ops-service-discovered` | `ops-service` / `ops-service-catalog` | Record that a service exists and was discovered. |
| `ops-endpoint-verified` | `ops-endpoint-verified` | `ops-endpoint` | Record endpoint reachability, auth challenge, or health verification. |
| `ops-access-path-checked` | deferred | State Hub fallback until ops-hub adds access-path vocabulary or maps it to readiness/risk | Record operator or service access path verification without inventing unsupported registry names. |
| `ops-backup-verified` | `ops-backup-verified` | `ops-backup-set` | Record backup presence, recency, or restore-drill evidence after a backup-set widget exists. |
| `ops-inventory-drift` | `ops-drift-detected` | `ops-readiness-gate` / `ops-risk` | Record drift between expected and observed operations inventory. |
The first implementation should keep one stable widget per entity and evidence
family where possible. If activity-core cannot know entity identity reliably,
use one aggregate intake widget per family as a conservative first slice, then
split into per-entity widgets after payload evidence proves stable.
The first implementation should use existing seeded widgets where possible and
seed only the missing backup/risk widgets that are needed for the attended
smoke. If activity-core cannot know entity identity reliably, keep State Hub
fallback evidence rather than submitting to a made-up aggregate widget.
## Tasks
@@ -179,6 +181,13 @@ versioned non-secret `OPS_HUB_WIDGET_MAPPING` JSON shape, aggregate-first
fallback widgets, per-entity selector rules, stable `widgetRef` values, and
Secret-only handling for `OPS_HUB_KEY`.
Compatibility note (2026-06-27): revised the mapping contract to match the
live ops-hub seed vocabulary. The mapping now aliases `ops-service-observed`
to `ops-service-discovered`, aliases `ops-inventory-drift` to
`ops-drift-detected`, uses declared ops policy scopes instead of the old
`ops-evidence` proposal, and defers access-path evidence until ops-hub has a
supported event/widget target.
---
### T03 - Prepare manifest vocabulary and seed widgets
@@ -218,6 +227,12 @@ environment with the `5101eb5` COUNT decode fix live and an authenticated
operator/runtime key path. The required vocabulary is documented, but no live
manifest or widget seed was performed in this implementation slice.
Progress note (2026-06-27): public production probes show an `ops-hub` row and
the live seed registry vocabulary, and the contract docs now target that live
vocabulary. T03 remains waiting because protected widget lookup, widget ids,
any missing backup/risk seed widgets, and authenticated smoke evidence still
require the operator/runtime key path.
---
### T04 - Provision the runtime API key outside Git
@@ -289,6 +304,11 @@ Inter-Hub request envelope, shared validation rules, idempotency expectations,
forbidden payload material, expected API errors, and one example for each
activity-core event type.
Compatibility note (2026-06-27): revised payload examples to submit only live
ops-hub event types. Access-path payloads are documented as deferred fallback
evidence, and old event names are treated as aliases that should not be posted
to Inter-Hub.
---
### T06 - Validate fallback-first intake