generated from coulomb/repo-seed
Implement ops-hub evidence intake contracts
This commit is contained in:
220
docs/contracts/ops-hub-activity-core-event-payloads.md
Normal file
220
docs/contracts/ops-hub-activity-core-event-payloads.md
Normal 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.
|
||||
206
docs/contracts/ops-hub-activity-core-mapping.md
Normal file
206
docs/contracts/ops-hub-activity-core-mapping.md
Normal 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>" }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
95
docs/evidence/ops-hub-activity-core-fallback-validation.md
Normal file
95
docs/evidence/ops-hub-activity-core-fallback-validation.md
Normal 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.
|
||||
157
docs/research/ops-hub-evidence-intake-current-state.md
Normal file
157
docs/research/ops-hub-evidence-intake-current-state.md
Normal 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?
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user