From 81cb73f593253b023fd2603c7403776ea25e609a Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 27 Jun 2026 09:58:52 +0200 Subject: [PATCH] Align ops-hub activity-core contract vocabulary --- .../ops-hub-activity-core-event-payloads.md | 103 +++++--- .../ops-hub-activity-core-mapping.md | 250 +++++++++++------- .../ops-hub-evidence-intake-current-state.md | 64 +++-- .../IHUB-WP-0022-ops-hub-evidence-intake.md | 46 +++- 4 files changed, 289 insertions(+), 174 deletions(-) diff --git a/docs/contracts/ops-hub-activity-core-event-payloads.md b/docs/contracts/ops-hub-activity-core-event-payloads.md index 2e6e5e1..9d5daad 100644 --- a/docs/contracts/ops-hub-activity-core-event-payloads.md +++ b/docs/contracts/ops-hub-activity-core-event-payloads.md @@ -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": "", - "eventType": "ops-service-observed", + "widgetId": "", + "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": "", - "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": "", + "widgetId": "", "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": "", - "eventType": "ops-inventory-drift", + "widgetId": "", + "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 diff --git a/docs/contracts/ops-hub-activity-core-mapping.md b/docs/contracts/ops-hub-activity-core-mapping.md index 986b979..8a1fca8 100644 --- a/docs/contracts/ops-hub-activity-core-mapping.md +++ b/docs/contracts/ops-hub-activity-core-mapping.md @@ -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": "" }, - "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": ":", + "defaultWidgetRef": "ops:endpoint:gitea-registry" }, "ops-backup-verified": { "family": "backups", - "aggregateWidgetRef": "ops:backup:aggregate" + "selector": ":", + "defaultWidgetRef": "ops:backup-set:aggregate", + "requiresSeed": true }, - "ops-inventory-drift": { - "family": "drift", - "aggregateWidgetRef": "ops:drift:aggregate" + "ops-drift-detected": { + "family": "readiness", + "selector": ":", + "defaultWidgetRef": "ops:readiness:gitea-registry" } }, "widgets": { - "aggregate": { - "ops:service:aggregate": { + "byRef": { + "ops:service-catalog": { "widgetId": "", - "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": "", - "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": "", - "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": "", - "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": "", - "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": "", + "widgetType": "ops-readiness-gate", + "policyScope": "ops-registry", + "name": "Gitea Registry Readiness" + }, + "ops:backup-set:aggregate": { + "widgetId": "", + "widgetType": "ops-backup-set", + "policyScope": "ops-backup-retention", + "name": "Ops Backup Evidence Intake", + "requiresSeed": true } }, "services": { - "state-hub": { - "widgetRef": "ops:service:state-hub", - "widgetId": "" - } + "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": "" - } - }, - "accessPaths": { - "gitea:gitea-access-1": { - "widgetRef": "ops:access-path:gitea-access-1", - "widgetId": "" - } + "gitea:gitea-oci-registry": { "widgetRef": "ops:endpoint:gitea-registry" } }, "backups": { - "gitea:database:gitea-db": { - "widgetRef": "ops:backup:gitea-db", - "widgetId": "" - } + "gitea:database:gitea-db": { "widgetRef": "ops:backup-set:aggregate" } }, - "drift": { - "gitea:gitea-oci-registry": { - "widgetRef": "ops:drift:gitea-oci-registry", - "widgetId": "" - } + "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[""]`. -3. For `ops-endpoint-verified`, use - `endpoints[":"]`. -4. For `ops-access-path-checked`, use - `accessPaths[":"]`. + `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[""]`; otherwise use + `ops:service-catalog`. +4. For `ops-endpoint-verified`, use + `endpoints[":"]`; otherwise skip or use a + deliberately seeded endpoint aggregate. 5. For `ops-backup-verified`, use - `backups[":"]`. -6. For `ops-inventory-drift`, use - `drift[":"]`. -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[":"]`; otherwise skip until a + backup-set widget is seeded. +6. For `ops-drift-detected`, use + `readiness[":"]`; 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": "" }, - "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": "" }, - "ops:endpoint:aggregate": { "widgetId": "" }, - "ops:access-path:aggregate": { "widgetId": "" }, - "ops:backup:aggregate": { "widgetId": "" }, - "ops:drift:aggregate": { "widgetId": "" } + "byRef": { + "ops:service-catalog": { "widgetId": "" }, + "ops:endpoint:gitea-registry": { "widgetId": "" }, + "ops:readiness:gitea-registry": { "widgetId": "" } } } } diff --git a/docs/research/ops-hub-evidence-intake-current-state.md b/docs/research/ops-hub-evidence-intake-current-state.md index 7f5edf0..771ae73 100644 --- a/docs/research/ops-hub-evidence-intake-current-state.md +++ b/docs/research/ops-hub-evidence-intake-current-state.md @@ -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 diff --git a/workplans/IHUB-WP-0022-ops-hub-evidence-intake.md b/workplans/IHUB-WP-0022-ops-hub-evidence-intake.md index 7eb2832..51fa656 100644 --- a/workplans/IHUB-WP-0022-ops-hub-evidence-intake.md +++ b/workplans/IHUB-WP-0022-ops-hub-evidence-intake.md @@ -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