# Ops Hub Activity-Core Event Payloads Date: 2026-06-15 Updated: 2026-06-27 Workplan: `IHUB-WP-0022` ## Inter-Hub Request Shape Activity-core should submit ops evidence through: ```text POST /api/v2/interaction-events Authorization: Bearer ${OPS_HUB_KEY} Content-Type: application/json ``` Each request body must use the Inter-Hub v2 interaction-event shape: ```json { "widgetId": "", "eventType": "ops-endpoint-verified", "viewContext": "ops-inventory-probe", "metadata": { "type": "ops-endpoint-verified", "version": "1.0", "publisher": "activity-core", "attributes": {} } } ``` 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. - `eventType` must exist in Inter-Hub's event type registry. - If the API consumer is bound to an active ops-hub manifest, `eventType` must 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` 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 not currently enforce idempotency. - Duplicate tolerance is required on the reader side until Inter-Hub provides a unique idempotency constraint. - Payloads must not include secrets, authorization headers, cookies, token-like values, private key material, raw response bodies, command output, or unredacted URL query strings. ## Status Vocabulary Use the activity-core status vocabulary: - `ok` - `degraded` - `down` - `skipped` Use `reason` for compact machine-readable explanations, for example: - `expected_status_mismatch` - `expected_signal_missing` - `unsupported_access_path_type` - `backup_probe_not_implemented` - `missing_endpoint` ## Example: Service Discovered ```json { "widgetId": "", "eventType": "ops-service-discovered", "viewContext": "ops-inventory-probe/services", "metadata": { "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-discovered", "service_id": "state-hub", "service_name": "State Hub", "environment": "local", "lifecycle_state": "discovered", "observed_status": "ok", "observed_at": "2026-06-05T10:15:01Z", "widget_ref": "ops:service:state-hub" } } } ``` ## Example: Endpoint Verified ```json { "widgetId": "", "eventType": "ops-endpoint-verified", "viewContext": "ops-inventory-probe/endpoints", "metadata": { "type": "ops-endpoint-verified", "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-endpoint-verified", "service_id": "gitea", "endpoint_id": "gitea-oci-registry", "endpoint_type": "https", "endpoint_url": "https://gitea.coulomb.social/v2/", "expected_status": 401, "status_code": 401, "matched_expected_status": true, "matched_expected_signal": true, "observed_status": "ok", "observed_at": "2026-06-05T10:15:01Z", "widget_ref": "ops:endpoint:gitea-registry" } } } ``` ## Deferred: Access Path Checked 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": "", "eventType": "ops-backup-verified", "viewContext": "ops-inventory-probe/backups", "metadata": { "type": "ops-backup-verified", "version": "1.0", "publisher": "activity-core", "attributes": { "activity_core_run_id": "12345678-aaaa-bbbb-cccc-123456789abc", "idempotency_key": "12345678-aaaa-bbbb-cccc-123456789abc:gitea:database:gitea-db:ops-backup-verified", "service_id": "gitea", "backing_store_ref": "database:gitea-db", "backup_evidence_ref": "state-hub:progress:", "restore_verified": false, "observed_status": "skipped", "reason": "backup_probe_not_implemented", "observed_at": "2026-06-05T10:15:01Z", "widget_ref": "ops:backup-set:aggregate" } } } ``` ## Example: Drift Detected ```json { "widgetId": "", "eventType": "ops-drift-detected", "viewContext": "ops-inventory-probe/drift", "metadata": { "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-drift-detected", "service_id": "gitea", "inventory_object_id": "gitea-oci-registry", "drift_kind": "status_mismatch", "declared_summary": "expected_status=401", "observed_summary": "status_code=200", "observed_status": "degraded", "reason": "expected_status_mismatch", "observed_at": "2026-06-05T10:15:01Z", "widget_ref": "ops:readiness:gitea-registry" } } } ``` ## Expected API Errors 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 | 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` 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 compact sink result naming the non-secret failure class.