Implement ops-hub evidence intake contracts

This commit is contained in:
2026-06-15 22:48:31 +02:00
parent 5e7b2cd11a
commit f8fde35e3e
5 changed files with 732 additions and 15 deletions

View File

@@ -0,0 +1,220 @@
# Ops Hub Activity-Core Event Payloads
Date: 2026-06-15
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": "<widget-uuid-from-OPS_HUB_WIDGET_MAPPING>",
"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`.
## 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`.
- `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 Observed
```json
{
"widgetId": "<service-widget-uuid>",
"eventType": "ops-service-observed",
"viewContext": "ops-inventory-probe/services",
"metadata": {
"type": "ops-service-observed",
"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",
"service_id": "state-hub",
"service_name": "State Hub",
"environment": "local",
"lifecycle_state": "observed",
"observed_status": "ok",
"observed_at": "2026-06-05T10:15:01Z"
}
}
}
```
## Example: Endpoint Verified
```json
{
"widgetId": "<endpoint-widget-uuid>",
"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"
}
}
}
```
## Example: 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"
}
}
}
```
## Example: Backup Verified
```json
{
"widgetId": "<backup-widget-uuid>",
"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:<progress-id>",
"restore_verified": false,
"observed_status": "skipped",
"reason": "backup_probe_not_implemented",
"observed_at": "2026-06-05T10:15:01Z"
}
}
}
```
## Example: Inventory Drift
```json
{
"widgetId": "<drift-widget-uuid>",
"eventType": "ops-inventory-drift",
"viewContext": "ops-inventory-probe/drift",
"metadata": {
"type": "ops-inventory-drift",
"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",
"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"
}
}
}
```
## 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 | Activate the ops-hub manifest vocabulary. |
| `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`. |
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.

View File

@@ -0,0 +1,206 @@
# Ops Hub Activity-Core Widget Mapping
Date: 2026-06-15
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 logical references, but it must never contain
`OPS_HUB_KEY` or any operator credential.
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
```json
{
"version": "ops-hub.activity-core.widget-mapping.v1",
"hub": {
"slug": "ops-hub",
"id": "<ops-hub-uuid>"
},
"policyScope": "ops-evidence",
"defaultViewContext": "ops-inventory-probe",
"events": {
"ops-service-observed": {
"family": "services",
"aggregateWidgetRef": "ops:service:aggregate"
},
"ops-endpoint-verified": {
"family": "endpoints",
"aggregateWidgetRef": "ops:endpoint:aggregate"
},
"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"
}
},
"widgets": {
"aggregate": {
"ops:service:aggregate": {
"widgetId": "<uuid>",
"widgetType": "ops-service-card",
"name": "Ops Service Evidence Intake"
},
"ops:endpoint:aggregate": {
"widgetId": "<uuid>",
"widgetType": "ops-endpoint-card",
"name": "Ops Endpoint Evidence Intake"
},
"ops:access-path:aggregate": {
"widgetId": "<uuid>",
"widgetType": "ops-access-path-card",
"name": "Ops Access Path Evidence Intake"
},
"ops:backup:aggregate": {
"widgetId": "<uuid>",
"widgetType": "ops-backup-card",
"name": "Ops Backup Evidence Intake"
},
"ops:drift:aggregate": {
"widgetId": "<uuid>",
"widgetType": "ops-drift-card",
"name": "Ops Inventory Drift Evidence Intake"
}
},
"services": {
"state-hub": {
"widgetRef": "ops:service:state-hub",
"widgetId": "<uuid>"
}
},
"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>"
}
},
"backups": {
"gitea:database:gitea-db": {
"widgetRef": "ops:backup:gitea-db",
"widgetId": "<uuid>"
}
},
"drift": {
"gitea:gitea-oci-registry": {
"widgetRef": "ops:drift:gitea-oci-registry",
"widgetId": "<uuid>"
}
}
}
}
```
## 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
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>"]`.
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.
## Bootstrap Widget Names
The initial aggregate widgets should be seeded before activity-core is pointed
at Inter-Hub:
| 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 |
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.
## 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.
- 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, an aggregate-only mapping is enough:
```json
{
"version": "ops-hub.activity-core.widget-mapping.v1",
"hub": {
"slug": "ops-hub",
"id": "<ops-hub-uuid>"
},
"policyScope": "ops-evidence",
"defaultViewContext": "ops-inventory-probe",
"events": {
"ops-service-observed": {
"family": "services",
"aggregateWidgetRef": "ops:service:aggregate"
},
"ops-endpoint-verified": {
"family": "endpoints",
"aggregateWidgetRef": "ops:endpoint:aggregate"
},
"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"
}
},
"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>" }
}
}
}
```

View File

@@ -0,0 +1,95 @@
# Ops Hub Activity-Core Fallback Validation
Date: 2026-06-15
Workplan: `IHUB-WP-0022`
## Validation Result
The State Hub fallback path is implemented and tested in activity-core, but no
live fallback event exists in State Hub yet.
Direct query:
```text
GET http://127.0.0.1:8000/progress/?event_type=ops_inventory_probe&limit=20
```
Observed result on 2026-06-15:
```json
[]
```
This means Inter-Hub can accept the fallback-first design, but cannot yet cite
live `ops_inventory_probe` evidence as a closure artifact.
## What Is Validated
Activity-core local tests validate the fallback sink shape:
- `state-hub-progress` posts one compact `ops_inventory_probe` progress event
per run.
- The fallback idempotency key is stable:
`activity_core_run_id:context_key:ops_inventory_probe`.
- The posted summary is compact, for example:
`Ops inventory probe: 1 ok, 0 degraded, 0 down, 1 skipped`.
- The detail includes `activity_id`, `activity_core_run_id`, `scheduled_for`,
`source_type`, `context_key`, `idempotency_key`, and a compact `probe`
payload.
- The compact probe strips response bodies, authorization headers, credentials,
URL query strings, and token-like values.
- Inter-Hub sink config absence is a clean skip with
`reason = missing_inter_hub_config`.
- When config is present but submission is not implemented, the result is a
clean skip with `reason = inter_hub_sink_deferred`.
## What Is Not Yet Validated
- No live activity-core worker has posted an `ops_inventory_probe` event to the
local State Hub instance.
- No live Inter-Hub event has been submitted from activity-core.
- No production `OPS_HUB_KEY` handoff has been verified.
- No `OPS_HUB_WIDGET_MAPPING` has been deployed into the activity-core runtime.
- No per-entity widget mapping has been smoke-tested against production
ops-hub widgets.
## Gaps Compared With Inter-Hub Submission
State Hub fallback is useful as continuity evidence, but it is not a full
replacement for Inter-Hub submission:
- It records one compact run summary, not one governed widget event per entity.
- It cannot attach annotations directly to the affected service or endpoint
widget.
- It does not prove ops-hub manifest vocabulary enforcement.
- It does not prove API consumer, key, rate-limit, or widget mapping behavior.
- It does not populate Inter-Hub event lists for hub dashboards or downstream
widget governance workflows.
## Closure Recommendation
`ACTIVITY-WP-0007/T06` should not close as fully activated based on the current
State Hub state, because no live `ops_inventory_probe` progress event exists.
Two acceptable closure paths remain:
1. Fallback-deferred closure: run one disabled/manual activity-core probe,
confirm a live non-secret `ops_inventory_probe` progress event in State Hub,
and explicitly record that Inter-Hub submission is deferred until
`IHUB-WP-0022-T03/T04/T07` complete.
2. Full Inter-Hub closure: provision `OPS_HUB_KEY`, deploy
`OPS_HUB_WIDGET_MAPPING`, seed/verify the ops-hub widgets, and submit one
accepted Inter-Hub event per activity-core event type.
Until one of those paths is satisfied, Inter-Hub should keep the activity-core
closure gate open.
## Next Evidence To Capture
- Progress id for the first live `ops_inventory_probe` event.
- Non-secret summary counts from that progress detail.
- Confirmation that Inter-Hub sink remains skipped cleanly while config is
absent or deferred.
- After ops-hub activation, event ids for one accepted Inter-Hub submission per
event type.

View File

@@ -0,0 +1,157 @@
# Ops Hub Evidence Intake - Current State
Date: 2026-06-15
Workplan: `IHUB-WP-0022`
## Summary
Inter-Hub has the generic v2 API surface needed for activity-core evidence
intake, but the activity-core path is not live yet. The safe implementation
slice is therefore contract-first:
- document the ops-hub widget mapping shape;
- document the Inter-Hub event payload shape;
- keep `OPS_HUB_KEY` outside Git;
- accept State Hub fallback as the temporary safety path;
- wait on live ops-hub manifest/widgets, key provisioning, and production
smoke before enabling per-entity Inter-Hub submission.
## Inter-Hub API Surface
The current repo supports the necessary primitives through `/api/v2`.
`Web.Controller.Api.V2.Widgets`:
- `GET /api/v2/widgets` is authenticated and paginated.
- `POST /api/v2/widgets` requires `hubId`, `name`, and `widgetType`.
- Optional fields are `capabilityRef`, `viewContext`, `policyScope`,
`status`, and `adapterSpecId`.
- `policyScope` defaults to `internal` when omitted.
- Valid widget statuses are `active`, `deprecated`, and `draft`.
- Widget type and policy scope are validated through the type registries.
- Widget creation creates an initial `WidgetVersion` snapshot.
`Web.Controller.Api.V2.InteractionEvents`:
- `GET /api/v2/interaction-events` is authenticated and paginated.
- Supported list filters are `widgetId` and `eventType`.
- `POST /api/v2/interaction-events` requires `widgetId` and `eventType`.
- `viewContext` is optional and is persisted as `viewContextRef`.
- `metadata` is accepted as a JSON object when the request content type is
`application/json`.
- The event type must exist in `event_type_registry`.
- If the API consumer is bound to an active manifest, the event type must also
be declared by that manifest.
- `occurredAt` is server-set. Activity-core should send its observed timestamp
inside `metadata.attributes.observed_at`.
- Actor attribution is `actorType = "api"` for this endpoint.
`docs/new-hub-quickstart.md` and `scripts/ops-hub-bootstrap-smoke.py` already
show the bootstrap shape for a single ops-hub endpoint event. The activity-core
intake needs that pattern expanded from one smoke widget/event to a durable
five-event contract.
## Activity-Core Contract
The neighboring `activity-core` repo already defines the intended event
vocabulary under `event-types/`:
- `ops-service-observed`
- `ops-endpoint-verified`
- `ops-access-path-checked`
- `ops-backup-verified`
- `ops-inventory-drift`
The current activity-core sink implementation is intentionally conservative:
- `state-hub-progress` is implemented and idempotent.
- It posts `ops_inventory_probe` progress with compact non-secret detail.
- The idempotency key is `activity_core_run_id + context_key + event_type`.
- The compact probe strips raw response bodies, headers, credentials, URL query
strings, and token-like material.
- Inter-Hub sink names are recognized, but the sink currently returns
`missing_inter_hub_config` or `inter_hub_sink_deferred`; it does not submit
events yet.
- Inter-Hub mode requires `INTER_HUB_URL`, `OPS_HUB_KEY`, and either
`OPS_HUB_WIDGET_MAPPING`, `widget_mapping`, or `capability_mapping`.
Activity-core deployment placeholders exist in
`activity-core/k8s/railiance/20-runtime.yaml`:
- `INTER_HUB_URL` is present but empty.
- `OPS_HUB_WIDGET_MAPPING` is present but empty.
- `OPS_HUB_KEY` is created only as an empty Secret placeholder by
`bootstrap-secrets.sh`.
## Fallback Evidence State
State Hub was queried directly for live fallback evidence:
```text
GET http://127.0.0.1:8000/progress/?event_type=ops_inventory_probe&limit=20
```
Result on 2026-06-15: an empty list.
That means the fallback sink is implemented and tested in activity-core, but no
live `ops_inventory_probe` progress event is available for Inter-Hub to accept
as closure evidence yet.
## Production Gates
Known gates before per-entity Inter-Hub submission can be treated as live:
1. The production Inter-Hub deployment must include commit `5101eb5` or an
equivalent fix for PostgreSQL `COUNT(*)` decoding in widget creation and
API rate-limit reads.
2. The active `ops-hub` manifest must declare the five activity-core event
types, the selected widget types, the annotation category, and the policy
scope.
3. Seed widgets named by the mapping contract must exist in the target
environment.
4. `OPS_HUB_KEY` must be provisioned outside Git, preferably in OpenBao at
`platform/operators/ops-hub/runtime`, field `OPS_HUB_KEY`.
5. Activity-core must receive `INTER_HUB_URL`, `OPS_HUB_KEY`, and
`OPS_HUB_WIDGET_MAPPING` through its runtime config/Secret path.
6. A controlled smoke must submit one event for each declared event type and
verify that an undeclared event type is rejected.
## Recommended Manifest Vocabulary
Use one policy scope for the first slice:
- `ops-evidence`
Use one annotation category:
- `ops-risk`
Use these widget types unless the operator prefers to keep a smaller aggregate
surface:
- `ops-service-card`
- `ops-endpoint-card`
- `ops-access-path-card`
- `ops-backup-card`
- `ops-drift-card`
Use the activity-core event types exactly as published:
- `ops-service-observed`
- `ops-endpoint-verified`
- `ops-access-path-checked`
- `ops-backup-verified`
- `ops-inventory-drift`
## Open Questions
- Does ops-hub already have a production manifest that should be patched rather
than replaced?
- Should the first production mapping use only aggregate widgets, or seed
per-entity widgets for the known Railiance inventory?
- Which OpenBao or cluster Secret path should activity-core consume for
`OPS_HUB_KEY`?
- Should activity-core close `ACTIVITY-WP-0007/T06` after a live State Hub
fallback event with explicit Inter-Hub deferral, or only after real
Inter-Hub submission?

View File

@@ -4,7 +4,7 @@ type: workplan
title: "Ops Hub Evidence Intake for Activity Core"
domain: inter_hub
repo: inter-hub
status: ready
status: active
owner: codex
topic_slug: inter_hub
created: "2026-06-15"
@@ -115,7 +115,7 @@ split into per-entity widgets after payload evidence proves stable.
```task
id: IHUB-WP-0022-T01
status: todo
status: done
priority: high
state_hub_task_id: "f9006504-e5f5-465f-9588-3f4279d12b84"
```
@@ -137,13 +137,20 @@ Answer:
Exit criteria: `docs/research/ops-hub-evidence-intake-current-state.md`
exists with non-secret findings and open gates.
Implementation note (2026-06-15): completed
`docs/research/ops-hub-evidence-intake-current-state.md`. The audit confirms
that Inter-Hub has the required v2 widget and interaction-event primitives,
activity-core has the five event definitions and a tested State Hub fallback
sink, but the Inter-Hub sink is still deferred and no live
`ops_inventory_probe` progress event exists in State Hub yet.
---
### T02 - Define the ops-hub evidence mapping contract
```task
id: IHUB-WP-0022-T02
status: todo
status: done
priority: high
depends_on: T01
state_hub_task_id: "4f8a98b9-0d01-4333-b847-f83b8c85a5ab"
@@ -166,13 +173,19 @@ The contract must specify:
Exit criteria: `docs/contracts/ops-hub-activity-core-mapping.md` documents the
mapping in copyable JSON examples without containing secrets.
Implementation note (2026-06-15): completed
`docs/contracts/ops-hub-activity-core-mapping.md`. The contract defines a
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`.
---
### T03 - Prepare manifest vocabulary and seed widgets
```task
id: IHUB-WP-0022-T03
status: todo
status: wait
priority: high
depends_on: T02
state_hub_task_id: "94fc9806-781c-45f6-a43c-a6bce13da47b"
@@ -200,6 +213,11 @@ Exit criteria:
target environment.
- Non-secret widget IDs or logical names are recorded in the mapping contract.
Current wait reason (2026-06-15): manifest/widget activation needs a target
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.
---
### T04 - Provision the runtime API key outside Git
@@ -241,7 +259,7 @@ root/sudo-capable token handoff or operator UI action.
```task
id: IHUB-WP-0022-T05
status: todo
status: done
priority: high
depends_on: T02
state_hub_task_id: "4eb04a83-8eea-4cab-861c-a39f312a5bb9"
@@ -265,13 +283,19 @@ The document must cover:
Exit criteria: `docs/contracts/ops-hub-activity-core-event-payloads.md`
exists with one example payload for each of the five event types.
Implementation note (2026-06-15): completed
`docs/contracts/ops-hub-activity-core-event-payloads.md`. It documents the
Inter-Hub request envelope, shared validation rules, idempotency expectations,
forbidden payload material, expected API errors, and one example for each
activity-core event type.
---
### T06 - Validate fallback-first intake
```task
id: IHUB-WP-0022-T06
status: todo
status: wait
priority: medium
depends_on: T01
state_hub_task_id: "38b54991-bed2-4f9d-bede-bea35821b1ef"
@@ -294,13 +318,20 @@ Validation path:
Exit criteria: `docs/evidence/ops-hub-activity-core-fallback-validation.md`
records fallback evidence, gaps, and the closure recommendation.
Implementation note (2026-06-15): created
`docs/evidence/ops-hub-activity-core-fallback-validation.md`. Activity-core's
fallback sink is implemented and locally tested, but a direct State Hub query
for `event_type=ops_inventory_probe` returned no live events. This task remains
waiting on a disabled/manual activity-core probe or other live fallback
evidence before it can close.
---
### T07 - Run end-to-end Inter-Hub submission smoke
```task
id: IHUB-WP-0022-T07
status: todo
status: wait
priority: high
depends_on: T03,T04,T05
state_hub_task_id: "23baee9b-d710-42c8-9a19-f936bd237444"
@@ -326,13 +357,17 @@ Smoke checks:
Exit criteria: non-secret smoke evidence is recorded and activity-core can
enable the Inter-Hub sink in its controlled environment.
Current wait reason (2026-06-15): depends on live manifest/widgets from T03,
runtime key provisioning from T04, and activity-core implementing actual
Inter-Hub submission beyond its current deferred sink.
---
### T08 - Coordinate ACTIVITY-WP-0007 closure handoff
```task
id: IHUB-WP-0022-T08
status: todo
status: wait
priority: medium
depends_on: T06,T07
state_hub_task_id: "4a7ed0ed-552e-42d3-a90f-1efd52b8851e"
@@ -354,18 +389,22 @@ The handoff must state:
Exit criteria: activity-core has enough non-secret evidence and configuration
shape to close or unblock `ACTIVITY-WP-0007/T06`.
Current wait reason (2026-06-15): closure handoff depends on either a live
State Hub fallback event plus an explicit Inter-Hub deferral decision, or a
successful Inter-Hub submission smoke.
## Exit Criteria Summary
| Task | Deliverable | Status |
|---|---|---|
| T01 | `docs/research/ops-hub-evidence-intake-current-state.md` | todo |
| T02 | `docs/contracts/ops-hub-activity-core-mapping.md` | todo |
| T03 | Active ops-hub manifest and seed widgets | todo |
| T01 | `docs/research/ops-hub-evidence-intake-current-state.md` | done |
| T02 | `docs/contracts/ops-hub-activity-core-mapping.md` | done |
| T03 | Active ops-hub manifest and seed widgets | wait |
| T04 | `OPS_HUB_KEY` stored outside Git and smokeable | wait |
| T05 | `docs/contracts/ops-hub-activity-core-event-payloads.md` | todo |
| T06 | `docs/evidence/ops-hub-activity-core-fallback-validation.md` | todo |
| T07 | End-to-end Inter-Hub submission smoke evidence | todo |
| T08 | activity-core closure handoff | todo |
| T05 | `docs/contracts/ops-hub-activity-core-event-payloads.md` | done |
| T06 | `docs/evidence/ops-hub-activity-core-fallback-validation.md` | wait |
| T07 | End-to-end Inter-Hub submission smoke evidence | wait |
| T08 | activity-core closure handoff | wait |
## Binding Principles