docs: advance retirement with SBOM receipts and caller migrations

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a06ed7-828d-7ca0-a8d4-0c3e5a0c4102
This commit is contained in:
tegwick 2026-09-05 10:24:54 +02:00
parent 74a3b22c05
commit f04de759a1
8 changed files with 1852 additions and 483 deletions

View file

@ -256,7 +256,7 @@ use `/state/health`, not `/state/summary`.
|--------|-----------|
| `/topics` | CRUD (soft-delete: `archived`) |
| `/workplans` | Preferred CRUD surface for repo-backed workplans (soft-delete: `archived`) |
| `/workstreams` | Legacy compatibility CRUD surface; usage is recorded by legacy-meter |
| `/workstreams` | Retired REST routes (410); use `/workplans`. Rejected calls remain metered |
| `/tasks` | CRUD (soft-delete: `cancel`); `PATCH` updates status |
| `/decisions` | CRUD (soft-delete: `superseded`); auto-escalation |
| `/progress` | `GET` list + `POST` append — no DELETE |

View file

@ -127,7 +127,7 @@ The Goals page groups everything by domain:
Workplans carry an optional `repo_goal_id` field. Setting it traces *why* a workplan exists — which specific repo goal it contributes to. This connection is currently recorded in the DB but is not yet visualised in the Workplans page.
To set the link when creating a workplan through the preferred API, pass `repo_goal_id`. To update an existing one, use `PATCH /workplans/{id}/` with `{"repo_goal_id": "<uuid>"}`. Legacy `create_workstream` and `/workstreams/{id}/` callers remain compatibility-supported while they are metered.
To set the link when creating a workplan through the preferred API, pass `repo_goal_id`. To update an existing one, use `PATCH /workplans/{id}/` with `{"repo_goal_id": "<uuid>"}`. The legacy `create_workstream` tool is removed and `/workstreams/{id}` returns 410; use `create_workplan` and `/workplans/{id}`.
---

View file

@ -0,0 +1,92 @@
# STATE-WP-0079 caller and receiver review — 2026-09-05
## Actions and scope
User authorized the proposed SBOM meter retirements, caller tracing/migration,
and refreshed cutover checklist. No application image, production writer,
network policy, runtime deployment, or compatibility handler was changed.
A fresh seven-day primary review re-confirmed both candidates before mutation.
Direct Nexus licence-report and snapshot reads returned HTTP 200 from the State
Hub pod. Both meter records were patched with STATE-WP-0079-T05 provenance.
The saved before/after receipt is
`STATE-WP-0079-sbom-retirement-20260905.json`: eight retired, seven legacy,
zero remaining candidates. Handler deletion is not claimed by meter retirement.
## Caller trace
Searched non-hidden source trees under `/home/worsch` for `workstream_id`,
`/workstreams`, and the two SBOM endpoints, covering Python, shell, JS/TS and
YAML while excluding tests, dependency trees, docs, archives and fixtures.
517 matching lines were triaged by receiver and operation. A second Markdown
instruction scan excluded workplans, generated indexes, docs, archives and
session evidence. Matching strings alone were not treated as runtime callers.
- Repo Manager's licence-report client uses explicit `SBOM_NEXUS_URL`; it is a
direct Nexus client, not evidence of a State Hub caller.
- SBOM Nexus importer intentionally reads source and target snapshots/licences
for migration comparison. Its matching paths do not establish ongoing use.
- The bundled State Hub skill advertised removed `create_workstream` and
`POST /workstreams/`; README and dashboard goals documentation also described
retired routes as supported. Corrected these to current workplan contracts,
file-first sync and component attribution.
- No current hard-coded State Hub workstream route caller was found in the
scanned runtime sources. This does not cover hidden installations, remote
checkouts, dynamically built paths or past one-off commands.
Targeted local agent-session review extracted only tool-call timestamp, working
repository and legacy path. Full session contents were not copied into evidence.
| Time (UTC) | Caller context | Observed operation | Confidence / correction |
| --- | --- | --- | --- |
| 2026-09-04 17:21:25.339 | Claude agent, fluid-telegram | Task query with legacy `workstream_id`, targeting `f2373858-c932-5a4d-81b6-db9a3595135a` | Actual command in session history; within four seconds of meter last_seen 17:21:29.173850. Strong correlation, not request-ID proof. AGENTS.md now prescribes canonical query and `X-StateHub-Component: fluid-telegram` |
| 2026-08-29 12:57:27.083 | Claude agent, ops-warden | Task query with legacy `workstream_id`, targeting `ae3ff76f-883d-5e2f-b6aa-144d61e8fdef` | Actual command in session history. AGENTS.md now prescribes canonical query and `X-StateHub-Component: ops-warden` |
Caller guidance commits: fluid-telegram `f7af151`, ops-warden `96dbb22`.
Both changes are restricted to the repository-owned AGENTS.md extension.
Replayed only the **canonical** task queries with those component headers:
HTTP 200, 8 and 5 tasks respectively. No legacy query was replayed, so the
retirement clocks were not reset by verification. Agent instructions reduce
future misuse; they do not prove that future sessions will follow them.
Remaining attribution: the meter's 11 task-filter calls are not all individually
explained, seven workstream-detail calls remain unidentified, and the one SBOM
index call remains unidentified. These are STATE-WP-0079-T07 live work, not
claimed completed migrations.
## Log retention limit
Requested seven days of `deployment/state-hub` pod logs. Available logs start
2026-09-04T20:10:36Z, after the relevant last-seen timestamps. No matching legacy
requests appear in that retained interval. Kubernetes has no previous container
log for this pod. Hence these logs cannot retrospectively identify the calls;
absence in this interval is not evidence of seven quiet days.
## Receiving runtime probe
The live State Hub deployment image was `main-cdff3b7`; its configmap directs
both SBOM read and write compatibility paths to the cluster Nexus service.
`core-hub-api` and `core-hub-api-candidate` deployments both reported 1/1 ready.
From the State Hub pod, requests to both Core Hub service addresses on port 8010
were connection-refused. Cause was not established and no connectivity change
was made. From candidate pod loopback, `/openapi.json` returned a **Hub Core
Runtime** schema with 31 paths. It exposes `/api/v2/interaction-events`, but no
`/messages`, `/progress`, `/token-events` or `/interface-changes` family.
Shared Python routers exist for messages/progress, but importing them into
State Hub does not deploy them on the owner runtime or migrate their rows.
HUB-WP-0005's successful Core Hub `/api/v2` absorption must not be used as proof
of the State Hub family cutover. STATE-WP-0079-T08 owns the bounded follow-up:
identify/expose one deployed message-read contract, historical data projection,
transport and parity/rollback evidence before changing any reader/writer.
## Validation
- Live primary identity and fresh seven-day eligibility verified before PATCH.
- Direct receiving SBOM reads: 200; canonical caller task reads: 200.
- State Hub skill passes the skill-creator frontmatter validator; this also
corrected an existing unquoted colon that made its description invalid YAML.
- Documentation and caller-instruction edits pass `git diff --check`.
- No executable application code changed; no database test suite was needed.

File diff suppressed because it is too large Load diff

View file

@ -1,472 +1,93 @@
# State Hub retirement — cutover slice plan
**Workplan task:** `STATE-WP-0079-T02`
**Inventory:** `SHR-INV-0001` (`prj-state-hub-retirement/inventory/`), 425 items, v0.1.1
**Created:** 2026-08-19
Groups every dispositioned inventory item into an ordered cutover slice. Each
slice names its adapter flag, the owner-side API that receives the traffic, and
its rollback. Slices are the execution unit for `STATE-WP-0079-T04`.
## Method
Slices are cut on **capability**, not on route or table, because a capability is
the smallest unit with a single owner and a coherent contract. The inventory's
`capability` field yields 51 groups; those are bundled into waves by owner, and
ordered within a wave by dependency and blast radius.
Two rules constrain the ordering:
1. **A slice cannot move before its owner can receive it.** Owner readiness is
the owner repo's own workplan status, not an assertion here.
2. **Read-only projections move before write paths** in the same capability, so
a failed cutover degrades to stale reads rather than lost writes.
## Owner readiness (verified 2026-08-19)
| Owner | Items | Gating workplan | Status | Can receive? |
| --- | --- | --- | --- | --- |
| `repo-manager` | 167 | RMGR-WP-0001/0002/0003 | `finished` | **Partial — see correction below** |
| `hub-core` | 128 | HUB-WP-0004 runtime + extension contract | `proposed` | No |
| `state-hub-until-cutover` | 55 | — (keep/retire in place) | — | n/a |
| `activity-core` | 18 | ACTIVITY-WP-0029 hub port alignment | `active` | Partial |
| `ops-hub` | 13 | OPS-WP-0003 extension contract alignment | `proposed` | No |
| `fin-hub` | 10 | FIN-WP-0003 fabric authority boundary | `proposed` | No |
| `archive` | 30 | legacy-meter evidence | see Wave E | Conditional |
| `ops-bridge` | 2 | — (`keep`) | — | n/a |
| `the-custodian` | 2 | — | — | Yes |
Only the `repo-manager` lane is unblocked today, and **only partly** — see the
correction immediately below. Everything else is waiting on an owner-side
contract, not on State Hub.
### Correction (2026-08-20): repo-manager readiness is per-capability
The table above reads owner readiness off workplan status, which is too coarse.
RMGR-WP-0001/0002/0003 are `finished`, but what they delivered is **work-record
dual-run, repository observation, and scaffolding** — not a general register
surface. Verified against `repo-manager/src/repo_manager/` on 2026-08-20:
| `rmgr` surface | Covers | Slice |
| --- | --- | --- |
| `observe`, `load_classification` | repository representation, classification | A1 (partial) |
| `reconcile`, `index_store`, `parse/workplan.py`, `task-status`, `dual_run` | work records | A2 |
| `conform`, `standards.py`, `scaffold` | standards, onboarding | A1 `repo-onboarding` (already cut over) |
| `prefix` | workplan prefix registry | A1 (partial) |
`parse/` contains **only** `workplan.py`. A grep for `sbom`, `repo_goal`,
`contribution`, `technical_debt`, and `extension_point` across the whole source
tree returns **nothing**.
**Consequence:** A3, A4, and A5 have no owner-side surface to receive them.
| Slice | Items | Receivable? |
| --- | --- | --- |
| A1 repository registry | 27 | Partial — `observe`/`conform` cover representation; registry CRUD does not exist |
| A2 work records | 59 | **Yes** — this is exactly what RMGR-WP-0002/0003 proved |
| A3 registers | 49 | **No** — no register surface of any kind in repo-manager |
| A4 work + repo UI | 22 | **No**`rmgr` is a CLI; there is no owner-side dashboard |
| A5 topic spine | 8 | **No** — no topic/classification contract beyond `observe` |
The near-term executable surface is therefore **A1 + A2, not 165 items**, and
A2 is the only slice with a proven adapter.
**A3 is not the low-blast-radius first cut this plan originally recommended** —
it is the least ready slice in Wave A. The recommendation is withdrawn.
**Do not build the register surface in State Hub.** New register capability here
would be new permanent ownership in the component being retired, which
`policies/retirement-freeze.md` makes inadmissible. A3/A4/A5 need a
`repo-manager` workplan; RMGR-WP-0004 (standards conformance) and RMGR-WP-0005
(registrar consolidation) do not cover them.
### Owner readiness refresh (2026-08-31)
The original readiness table is retained above as planning history. Current
workplan status changes which owners can receive a slice, but it does not count
as State Hub cutover evidence by itself:
| Owner | Current owner evidence | State Hub cutover consequence |
| --- | --- | --- |
| `repo-manager` | RMGR-WP-0005 finished deterministic identifier migration; RMGR-WP-0008 finished register/classification surfaces | Receiving-side identity and register gates are satisfied; each State Hub writer/read switch still needs its own parity and rollback receipt. |
| `hub-core` | HUB-WP-0005 finished and its production runtime owns all declared hub-core route/writer groups with exercised rollback | Receiving runtime is available; B1-B5 are not complete until State Hub caller/route switches and counts are evidenced. |
| `activity-core` | ACTIVITY-WP-0029 finished schedule, sweep, interaction-event, and execution-boundary work | The launch-request defect is retired; remaining C-slice caller switches still require explicit evidence. |
| `ops-hub` | OPS-WP-0003 finished; owner package and implemented conformance profile pass | D1 receiving-side gate is satisfied. State Hub route/caller switch, count parity, rollback, and writer authorization remain cutover evidence. |
| `fin-hub` | FIN-WP-0003 remains `proposed`; 0/3 tasks done | D2 cannot move. Do not implement the fabric authority contract here. |
| `archive` | 2026-08-31 central meter: 6 newly retired, 9 legacy, 0 candidates | E2/E4 remain gated; live callers and volume-scaled quiet windows prohibit retirement. |
Accordingly, `STATE-WP-0079-T04` is waiting on the missing owner workplans,
the still-unrecorded State Hub route-switch receipts, and explicit production
writer-cutover authorization. `T05` waits on fresh central meter evidence, and
`T06` cannot begin while State Hub remains the healthy, active `railiance01`
primary.
## Adapter mechanism
Wave A reuses the dual-run strangler proven in RMGR-WP-0002/0003 rather than
introducing a new mechanism:
- Adapter: `api/services/repo_manager_dual_run.py`
- Existing flags: `RM_WRITEBACK`, `RM_WRITEBACK_PUSH`, `RM_RECONCILE`,
`RM_PILOT_REPOS`, `RM_METER_PATH`, `RM_DUAL_RUN_CONFIG`
- Reference: `repo-manager/docs/dual-run.md`, evidence
`repo-manager/docs/evidence/wp0002-completion.md`
New slices add a **slice-scoped flag** of the form `RM_SLICE_<NAME>` with three
states — `off` (hub only), `dual` (both, hub authoritative, divergence metered),
`owner` (owner authoritative, hub proxies). Rollback for every Wave A slice is
therefore the same operation: set the slice flag back one state. No slice
advances to `owner` until it has run a full consistency-sweep cycle at `dual`
with zero divergence.
Waves BD must not invent hub-side adapters ahead of their owner contract.
Per the T01 freeze rule, building an adapter for an owner that cannot yet
receive establishes new permanent ownership here — the exact thing retirement
is removing.
---
## Wave A — repo-manager (167 items, executable now)
### A1 · Repository registry — 27 items
`repository-registry` (24: 16 route, 7 mcp_tool, 1 dataset) · `repo-reconciliation` (1)
· `repo-doi` (1) · `repo-onboarding` (1)
| | |
| --- | --- |
| Flag | `RM_SLICE_REGISTRY` |
| Owner API | `rmgr` repo registry + classification spine |
| Rollback | flag → `dual`, then `off`; hub tables retain rows throughout |
First because every other repo-manager slice keys off repo identity, and because
`repo-onboarding` is already delegated (`STATE-WP-0080-T02`, `rmgr scaffold`),
so the slice starts partly cut over.
### A2 · Work records — 59 items
`work-records` (20) · `work-records-workplan` (12) · `work-records-intake` (9) ·
`work-records-decision` (8, move+replace) · `work-records-deps` (2) ·
`work-records-task` (8, repo-manager portion)
| | |
| --- | --- |
| Flag | `RM_SLICE_WORKRECORDS` |
| Owner API | `rmgr` file-backed work-record index + PATCH `/tasks` writeback |
| Rollback | flag → `dual`; C-15 writeback already reconciles file↔DB drift |
Highest blast radius in the program — this is the surface every agent session
touches. Split the flag by record kind if divergence appears; do not cut the
whole slice at once.
`work-records-task` is owner-split: the route/dataset portion is repo-manager,
the job portion is `activity-core` (Wave C1). Cut the repo-manager portion first
and leave the job dual-running.
### A2 readiness detail (2026-08-20)
Same check as for A3, applied one level down. The State Hub adapter
(`api/services/repo_manager_dual_run.py`) exposes exactly three operations:
| Adapter function | Routes to | Covers |
| --- | --- | --- |
| `rm_update_task_status` | `rmgr task-status` | task status writes |
| `rm_reconcile` | `rmgr reconcile` | work-record index rebuild |
| `rm_scaffold` | `rmgr scaffold` | repo scaffolding (A1 `repo-onboarding`, already cut over) |
There is no adapter for workplan create/update/delete, intake, decision,
dependency, or human-flag records — and no parser for them on the repo-manager
side either (`parse/` is `workplan.py` only). So A2 splits:
| Sub-slice | Items | Receivable |
| --- | --- | --- |
| **A2a** task status + reconcile | ~6 (`update_task_status`, `bulk_update_task_statuses`, `PATCH /tasks/{id}`, `POST /tasks/bulk-status-sync`, index refresh job, `table:tasks`) | **Yes — adapter exists and was proven by RMGR-WP-0002/0003** |
| A2b workplan CRUD | ~9 routes + `table:workplans` | No adapter; repo-manager parses workplans but exposes no write path |
| A2c intake | 9 | No parser, no adapter |
| A2d decision | 8 | No parser, no adapter |
| A2e dependencies + human-flag | ~6 | No parser, no adapter |
**A2a is the only slice in the entire plan that is executable today**, and it is
roughly 6 items, not 59.
### Dual-run is currently off
`rmgr dual-run-status` on 2026-08-20:
```
config_exists: false writeback: false reconcile: false pilot_repos: null
meter: {state-hub: 15, repo-manager: 8, total: 23}
```
The 23 metered mutations are the RMGR-WP-0002/0003 pilot runs. Nothing is
dual-running now — the adapter is proven but switched off, and there is no
`~/.repo-manager/dual-run.yaml`.
**So the concrete next executable step is operational, not code:** create the
dual-run config and enable `RM_WRITEBACK` + `RM_RECONCILE`, then watch the
mutation meter for divergence. That is A2a moving from `off` to `dual`.
**The pilot repo is already established — `repo-manager`**, per RMGR-WP-0003 and
the shipped `repo-manager/config/dual-run.pilot.example.yaml`. No new decision
is needed about which repo; the procedure is documented in
`repo-manager/docs/dual-run.md`:
```bash
mkdir -p ~/.repo-manager
cp ~/repo-manager/config/dual-run.pilot.example.yaml ~/.repo-manager/dual-run.yaml
# then restart the State Hub API — settings are cached per process
```
Rollback is `rm ~/.repo-manager/dual-run.yaml` plus a restart, or setting
`writeback`/`reconcile` false.
**Requires an operator**, for one reason only: it changes how live task writes
flow and needs a State Hub API restart to take effect. Piloting on
`repo-manager` keeps the blast radius to a single repo.
### A3 · Registers — 49 items
`sbom-inventory` (10) · `repo-goals` (9) · `upstream-contributions` (9) ·
`register-technical-debt` (9) · `register-extension-points` (6) · `register-entries` (6)
| | |
| --- | --- |
| Flag | `RM_SLICE_REGISTERS` |
| Owner API | `rmgr register ...`; `rmgr sbom scan|licence-report` |
| Rollback | flag → `off`; retain current State Hub projection |
**Owner ready (2026-08-21).** Finished RMGR-WP-0008 supplies the shared six-kind
register spine, governed compatibility adapter, lockfile-derived SBOM and
licence/copyleft report. Historical SBOM rows are cache provenance; rebuild
from current repository sources rather than copying them into authority.
### A4 · Work + repo UI — 22 items
`work-coordination-ui` (14, `replace`) · `repo-ui` (8)
| | |
| --- | --- |
| Flag | `RM_SLICE_UI` |
| Owner API | hub-core projection/query plus hub/ops UI clients |
| Rollback | remove redirect; hub pages are unchanged behind it |
`replace`, not `move`: the contract changes. Must follow A1A3, since the pages
render what those slices serve. RMGR-ADR-003 corrects the earlier inventory
assignment: Repo Manager owns the repository contracts, not a dashboard. A4's
UI implementation therefore rides with B5 even though these 22 inventory items
remain grouped here for traceability.
### A5 · Topic spine — 8 items
`topic-spine` (8, `replace`)
| | |
| --- | --- |
| Flag | `RM_SLICE_TOPICSPINE` |
| Owner API | `rmgr` topic/classification contract |
| Rollback | flag → `off` |
The versioned Repo Manager classification contract is published and accepted by
finished HUB-WP-0004. Consumer route switching remains a State Hub cutover
action.
---
## Wave B — hub-core (128 items) · HUB-WP-0004 owner gate satisfied
HUB-WP-0004 finished 2026-08-21 and accepted the project architecture, runtime,
ports, conformance scaffold, and absorption plan. Individual production
cutovers still require their slice evidence and operator gates.
| Slice | Capabilities | Items |
| --- | --- | --- |
| B1 catalogs | `capability-catalog` (18), `cross-domain-registry` (16) | 34 |
| B2 messaging + interface log | `agent-messaging` (11), `interface-change-log` (11) | 22 |
| B3 telemetry | `token-telemetry` (14), `third-party-software-catalog` (10), `progress-telemetry` (8) | 32 |
| B4 projections + policy | `state-projections` (9), `domain-goals` (6), `policy-integration` (2), `write-idempotency` (1), `event-bus` (1), `recently-on-scope` (1) | 20 |
| B5 hub UI | `hub-projection-ui` (14, replace), `agent-integration` (2), `dashboard` (1) | 17 |
Order B1 → B4 → B2 → B3 → B5: catalogs and projections are read-mostly and
carry the contract; messaging and telemetry are write paths; UI last.
## Wave C — activity-core (18 items) · ACTIVITY-WP-0029 `active`
| Slice | Capabilities | Items |
| --- | --- | --- |
| C1 execution | `workplan-execution-queue` (7, replace), `task-flow-engine` (6, replace) | 13 |
| C2 jobs | `ops-run-projection` (1), `repo-consistency` (3), `work-records-task` activity-core portion (1) | 5 |
Both are `replace` — schedulers move to activity-core under a different
contract. C2 must follow A2, since the consistency job writes work records.
## Wave D — remaining owners (25 items) · fin-hub gated
| Slice | Owner | Capabilities | Items | Gate |
| --- | --- | --- | --- | --- |
| D1 | `ops-hub` | `service-catalog` (9), `service-catalog-ui` (2) | 11 | OPS-WP-0003 `finished`; receiving contract ready, cutover receipt pending |
| D2 | `fin-hub` | `fabric-graph` (10) | 10 | FIN-WP-0003 `proposed` |
| D3 | `the-custodian` | `kaizen-agents` (2) | 2 | none — can go anytime |
D3 is two MCP tools with no dependants; take it opportunistically.
## Wave E — retire (42 items)
| Slice | Capability | Items | Gate |
**Execution owner:** STATE-WP-0079-T04/T05
**Re-baselined:** 2026-09-05
**Inventory:** SHR-INV-0001, original 425-item inventory in
`prj-state-hub-retirement/inventory/`. Counts below identify historical slices;
current owner contracts take precedence over the original owner assignments.
This checklist replaces the obsolete August 1920 readiness snapshot. That
snapshot and the original off/dual/owner proposal remain in Git history.
`RM_SLICE_*` names were proposed controls, not proof of deployed flags. Do not
execute the old workstation dual-run instructions against the cluster service:
ADR-002 forbids central services from mutating workstation checkouts.
## Evidence required to switch a slice
For each route family, identify the current caller and destination endpoint,
compare representative identities/content/counts in a read-only parity run,
record the exact deployment/source revisions, exercise rollback, and retain the
switch receipt. An owner workplan marked finished or an imported Python router
proves neither deployed endpoint availability nor data migration. Production
writer changes require a concrete recoverable cutover, within user authorization.
No new permanent receiving authority belongs in State Hub during retirement.
## Current route-family checklist
| Slice / historical items | Receiver and contract evidence | Caller / route-switch evidence | Remaining gate and rollback |
| --- | --- | --- | --- |
| E1 | `suggestion-backlog` → archive | 17 | history archived 2026-08-20; read surface + dashboard remain |
| E2 | `work-records-workplan-legacy` → archive | 13 | legacy-meter — candidate rule, see below |
| E3 | `dashboard-meta` (`/ui-feedback`) | 1 | T06 freeze window — owner is `state-hub-until-cutover` |
| E4 | `legacy-meter` itself | 9 | **last** — it is the instrument for E2 |
| A1 registry / 27 | Repo Manager observation/scaffolding; STATE-WP-0081 and 0086 establish forge-derived projection | `rmgr sync` verified pushed commit `74a3b22` on primary/railiance01 in the preceding session | Inventory remaining registry callers per endpoint. Keep current projection while proving the owner read; do not restore checkout mutation in central |
| A2 work records / 59 | RMGR-WP-0008 implements file-backed workplan, intake, decision, dependency and task mutations; RMGR-WP-0005 identifiers finished | Repository files + `rmgr sync` are the current write convention. Two ad-hoc task-filter callers identified and guidance corrected in this review | Work-record caller inventory and per-kind writer receipts still required. Revert source commit and reconcile for a reviewed rollback; no second file authority |
| A3 registers / 49 | RMGR-WP-0008 register spine; **SBOM Nexus** is the current SBOM product owner | Live State Hub config uses Nexus for both SBOM reads/writes. Direct Nexus licence and snapshot reads return 200. Two more compatibility interfaces retired in the meter September 5 | Other registers need individual caller/parity receipts. Preserve SBOM history; remaining `/sbom/` traffic and retained compatibility handlers prevent deleting the whole router |
| A4 work/repo UI / 22 | hub-core projection + UI clients; RMGR-ADR-003 excludes a Repo Manager dashboard | No complete State Hub UI redirect receipt established | First prove backing reads; rollback redirects to the retained UI |
| A5 topic spine / 8 | Published Repo Manager classification contract, accepted by HUB-WP-0004 | No complete State Hub caller-switch receipt established | Prove deployed consumer contract and parity; retain source-backed classification projection |
| B1 catalogs / 34 | HUB-WP-0004/0005 receiving architecture and `/api/v2` absorption | Completion covers the Core Hub route groups, not every State Hub capability/registry route | Map each State Hub catalog to a deployed endpoint and migration receipt |
| B2 messaging/interface log / 22 | Shared `hub_core.routers.messages` exists; State Hub interface log is a separate family | Live Hub Core OpenAPI has **no `/messages` or `/interface-changes`**. `/api/v2/interaction-events` exists but is not asserted equivalent | **Next bounded read slice:** STATE-WP-0079-T08. Owner must expose/identify the message read contract and history projection before a parity run or switch. Retain current reads/writer |
| B3 telemetry / 32 | Shared `hub_core.routers.progress` exists; token/software catalogs need separate mappings | Live Hub Core OpenAPI has **no `/progress` or `/token-events`** | Owner runtime assembly/mapping plus historical identity/count parity; writer exclusivity and rollback receipts remain necessary |
| B4 projection/policy / 20 | HUB-WP-0004/0005; policy publication belongs to policy-nexus | State Hub still serves its projection routes; no full family receipt established | Map individual projections and policy publication; retain source/runtime boundaries and current reads |
| B5 hub UI / 17 | Hub Core runtime/console exists | Core Hub absorption does not establish replacement of State Hub dashboard pages | Prove A4/B1B4 data contracts before redirect; retain UI rollback |
| C1 execution / 13 | ACTIVITY-WP-0029 finished; Activity Core owns scheduling/ops_run | STATE-WP-0079 records launch-request rejection (410), replacement semantics, and corrected unconsumed requests | Verify every remaining execution caller uses the owner contract. Do not revive the obsolete launch queue for rollback |
| C2 jobs / 5 | ACTIVITY-WP-0029 finished; scheduler is Activity Core, reconciliation engine is Repo Manager | Receiving/sweep handoffs recorded in owner workplan | Attach remaining per-job deployment and caller receipts; keep scheduler ownership distinct from work-record authority |
| D1 service catalog / 11 | OPS-WP-0003 finished; reviewed packages and `/api/v2` conformance gate | Owner contract completion established; State Hub family-switch receipt still missing | Compare catalog identities and UI bindings; preserve current reads until switched |
| D2 Fabric / 10 | **railiance-fabric** authority, FIN-WP-0003 consumer gate | August 31 dual-read import: 131 nodes/117 edges; green hash/count/provenance checks, rollback and roll-forward exercised | Hosted runtime/persistence/auth/freshness remains RAIL-FAB-WP-0028 (`proposed`). Keep retained import rollback; do not claim the local authority is a hosted production receiver |
| D3 kaizen / 2 | the-custodian, original two-tool disposition | No fresh removal/delegation receipt collected in this review | Locate current tools/consumers and owner contract before removal |
| E1 suggestions / 17 | Archived history in the-custodian | STATE-WP-0079 records eight read routes at 410, six MCP tools removed, dashboard removed | Completed surface removal; tables retained for final dump |
| E2 legacy workplans / 13 | Preferred `/workplans` routes and `workplan_id` parameters | Workstream REST handlers return 410 and meter rejected calls. Task query alias still accepted | Seven legacy meter records remain after today's two SBOM retirements; observe volume-scaled quiet periods and complete T07 attribution |
| E3 UI feedback / 1 | State Hub until cutover | Feedback still needed during transition | T06 zero-normal-traffic gate; do not confuse it with retired suggestions |
| E4 meter / 9 | State Hub until all other retirement evidence complete | Eight retired / seven legacy interfaces, zero current candidates after September 5 action | Retire last, after the remaining interfaces and final evidence are closed |
E4 retires only after every other legacy surface is closed; retiring the meter
first destroys the evidence for the retirements it gates.
## What today's probes establish
### E1 status (2026-08-20)
- Central health identifies `primary/railiance01`.
- The live State Hub configuration has `SBOM_NEXUS_READ_MODE=nexus` and
`SBOM_NEXUS_WRITE_MODE=nexus`, using the cluster SBOM Nexus service.
- Direct Nexus `/sbom/report/licences/` and `/sbom/snapshots/` returned 200 from
the State Hub pod. Their State Hub meter entries are now retired. This does
**not** delete the compatibility handlers or historical data.
- Hub Core candidate runtime OpenAPI, read from its own pod loopback, has 31
paths and lacks the four State Hub families identified above. The State Hub
pod's probes to both Core Hub service addresses were connection-refused;
the deployments reported 1/1 ready. Transport readiness must be resolved as
part of any proxy-based pilot; this review did not diagnose its cause.
- Session trace identifies ad-hoc legacy task reads in fluid-telegram and
ops-warden. Their corrected canonical queries return 200 with 8 and 5 tasks.
No evidence identifies the other historical callers yet.
Already half-retired before this plan: mutations have returned HTTP 410 since
2026-07-21 (`CUST-WP-0061-T06`), pointing at `POST /intakes/`. Reads were kept
live so the historical record stayed reachable.
Evidence: `docs/evidence/STATE-WP-0079-sbom-retirement-20260905.json` and
`docs/evidence/STATE-WP-0079-caller-and-receiver-review-20260905.md`.
**History is now archived** at `the-custodian/docs/archived-suggestion-backlog.md`
— all 10 suggestions, 10 notes, and 5 relevance bumps, with each entry's
migration target. Every entry was `declined` in the intake migration and none
was promoted, so nothing forward-looking is lost by removing the surface.
## Next executable work
**Executed 2026-08-20.** The 8 read routes now 410 with a pointer to intake and
to the archive, the 6 MCP tools are removed (not stubbed — a retired tool that
still appears in the tool list costs every agent session context on every call),
the 5 `/suggestions` write-idempotency rules are gone, and the dashboard page is
deleted with its nav entry, `reference.md` row, and `wsjf-triage.md` link
updated. `dashboard/src/docs/suggestions.md` is rewritten as the archive
pointer. Tables stay for the T06 dump.
1. T07: observe the remaining legacy readers with component attribution. The
known caller repositories and bundled State Hub skill now prescribe canonical
reads. Preserve the quiet clocks; do not exercise legacy URLs to test them.
2. T08: obtain one **deployed message-read contract** from hub-core, including
access/transport and historical projection. Use the State Hub inbox reader as
the bounded parity client. Compare message IDs and read/archive flags against
one consistent snapshot; do not change message writes during this proof.
Only after parity and rollback evidence should a concrete reader switch run.
3. RAIL-FAB-WP-0028-T01 remains the owner decision for runtime, persistent store,
auth, backup/restore and deployment repository. Its T02T04 deliver deployment,
freshness and final direct-consumer receipts; no new Fabric authority here.
4. Re-evaluate eligible interfaces from a fresh seven-day meter capture. The
quiet ladder is 7 days for 199 historical calls, 30 for 1009,999, and 60 for
10,000+. A new call restarts the relevant clock, including rejected requests.
One naming trap worth recording: **`ui-feedback.md` and `todo.md` "suggestions"
are a different entity** — Shift+click dashboard feedback backed by
`technical_debt`, not the suggestion backlog. They are untouched. Anything
grepping for `suggestion` during cutover will hit them.
## Freeze and infrastructure boundary
### E3 status (2026-08-20)
`dashboard-meta` is the `/ui-feedback` page — the feedback scratch surface just
mentioned. Its owner is `state-hub-until-cutover` and it is still in active use,
so it retires **at the T06 freeze window**, not before. Removing it now would
delete a working feedback loop during the retirement it serves. No action.
### E2 evidence — defect found and partly fixed (2026-08-20)
The window discrepancy flagged on 2026-08-19 was a real defect in
`scripts/capture_legacy_meter_evidence.py`, not a labelling nit.
`_review_query` fell back to `hours=8` whenever `--days` was omitted, and
`--hours` defaulted to `8`. Every unattended capture therefore sampled an
8-hour band (06:00Z14:00Z) while writing a file named `weekly-review` with
`cadence: weekly` in the payload. **39 of 40 captures ran this way** — only the
first, 2026-07-08, used a true 7-day window.
Calls in the other 16 hours of each day were never sampled, so quiet-in-band
interfaces were reported as retirement candidates while still having live
callers. On the 2026-08-19 capture three candidates had recent traffic:
| Interface | All-time calls | Last seen |
| --- | --- | --- |
| `GET /tasks/?workstream_id` | 594 | 2026-08-18 |
| `GET /progress/?workstream_id` | 2 | 2026-08-12 |
| `GET /workstreams/` | 144 086 | 2026-08-10 |
**Fixed:** the script now defaults to `days=7`, matching the endpoint name and
the cadence label. `--hours` is retained for spot checks and documented as not
retirement evidence. Corrected capture:
`docs/evidence/legacy-meter-weekly-review-20260820.json` — 18 candidates of 20
interfaces, `GET /tasks/?workstream_id` correctly excluded.
**Candidate rule tightened (2026-08-20).** `_retirement_state` now requires a
quiet period scaled to all-time call volume, because a clean window only proves
silence for that window — for a heavily used interface an infrequent caller can
fall outside it entirely. `RETIREMENT_QUIET_LADDER` in
`api/services/legacy_meter.py`:
| All-time calls | Days since `last_seen_at` required |
| --- | --- |
| 0 | none — never used |
| 1 99 | 7 |
| 100 9 999 | 30 |
| 10 000+ | 60 |
Applied to the 2026-08-20 capture this splits the 19 legacy interfaces cleanly:
**15 retirable** — all four `workstream` MCP tools, the `state://` resource, and
the low-volume REST aliases, plus `PATCH /workstreams/{id}` (571 calls, quiet
42d), `POST /workstreams/` (824, quiet 42d) and
`POST /workstreams/{id}/dependencies/` (4 971, quiet 49d).
**4 held:**
| Interface | All-time calls | Reason |
| --- | --- | --- |
| `GET /tasks/?workstream_id` | 595 | traffic inside the window |
| `GET /workstreams/` | 144 086 | quiet 9d of 60d |
| `GET /workstreams/{id}` | 511 406 | quiet 19d of 60d |
| `GET /workstreams/{id}/dependencies/` | 255 865 | quiet 42d of 60d |
**Executed 2026-08-20:** the 15 are retired; the meter reads 16 retired / 4
legacy with 0 outstanding candidates. Journalled at
`the-custodian/docs/retired-legacy-interfaces.md`. The four
held are the three six-figure read paths and the one with live traffic — exactly
the set where a false retirement would hurt most. They clear on their own as the
quiet counter runs, provided no new traffic arrives; the earliest,
`GET /workstreams/{id}/dependencies/`, needs 18 more silent days.
Re-check before executing E2: the ladder is evaluated at review time, so a
capture older than the retirement decision is not evidence for it.
## Keep — 43 items
`schema-history` (43 migrations, `keep` until cutover) plus `ops-automation`,
`connectivity` (ops-bridge). Not slices: they persist until the T06 freeze
window and are dispositioned by the final dump and archive.
## Coverage
Computed against `state-hub-disposition.yaml`, not hand-counted. Every item is
assigned by `capability`; `work-records-task` splits on `owner`.
| Wave | Slices | Items |
| --- | --- | --- |
| A repo-manager | A1 27 · A2 59 · A3 49 · A4 22 · A5 8 | 165 |
| B hub-core | B1 34 · B2 22 · B3 32 · B4 20 · B5 17 | 125 |
| C activity-core | C1 13 · C2 5 | 18 |
| D ops-hub / fin-hub / the-custodian | D1 11 · D2 10 · D3 2 | 23 |
| E retire | E1 17 · E2 13 · E3 1 · E4 9 | 40 |
| Keep | `schema-history` 43, `ops-automation` 1, `connectivity` 1 | 45 |
| Ops dependencies | see below | 9 |
| **Total** | | **425** |
Wave A totals 165 rather than the owner table's 167, and Wave B 125 rather than
128: the difference is `ops_dependency` items, which carry no `capability` and
so attach to no capability slice.
### Ops dependencies (9 items, no capability)
| Owner | Disposition | Items | Handling |
| --- | --- | --- | --- |
| `hub-core` | move / replace / keep | 3 | ride with Wave B; confirm at B4 |
| `ops-hub` | replace | 2 | ride with D1 |
| `state-hub-until-cutover` | retire | 2 | T06 freeze window |
| `activity-core` | keep | 1 | keep to cutover |
| `ops-bridge` | keep | 1 | keep — external dependency, survives retirement |
These are infrastructure couplings (DB, scheduler, tunnel), not API surface.
They cannot be strangled behind a flag and are dispositioned at the freeze
window instead. Worth an explicit owner confirmation during T04 so none is
discovered live at cutover.
## Next actions
1. ~~Resolve the legacy-meter window discrepancy~~ — done 2026-08-20; E1 and the
evidenced part of E2 are executed.
2. ~~Build the Repo Manager receiving surface for A3/A5 and settle A4
ownership~~ — finished in RMGR-WP-0008. A4 belongs to hub-core projection UI,
not Repo Manager.
3. Cut **A2a** (task status + reconcile, ~6 items) — the only slice in the plan
executable today. First step is operational: create `~/.repo-manager/dual-run.yaml`
and enable writeback/reconcile for one pilot repo, then watch the mutation
meter. A2bA2e need repo-manager parsers and adapters that do not exist.
4. HUB-WP-0004's owner-readiness gate is satisfied; advance Wave B only through
its per-slice dual-run and operator gates.
5. Do not build register, UI, or topic capability in State Hub to unblock
items 23 — `policies/retirement-freeze.md` makes that inadmissible.
T06 remains blocked until move/replace families and legacy retirement complete,
followed by a fresh zero-normal-read/write window. Final dump, restore proof,
service stop and repository archive then form one separately reviewable
operation. The original schema-history/keep items and nine infrastructure
couplings (database, scheduler, connectivity) remain explicit final-window
checks; they are not route flags. Ops Bridge connectivity survives retirement.

View file

@ -1,6 +1,6 @@
---
name: state-hub
description: Use when coordinating with Custodian State Hub: orienting with domain summaries, checking agent inbox messages, updating workplan-backed task status, recording decisions/progress, or batching task status sync through MCP/REST without re-discovering tool schemas.
description: Coordinate repository-backed work through State Hub REST or optional MCP, including canonical workplan reads, inbox checks, task status sync, and progress logging.
---
# State Hub Coordination
@ -11,21 +11,32 @@ work structure in the hub when a workplan file is the canon.
## Session Flow
1. Orient with `get_domain_summary(domain_slug)` when working inside one domain
repo. Use `get_state_summary()` only for cross-domain/custodian-wide work.
1. Prefer REST/`statehub` for Codex. Verify `/state/health` identifies the expected
primary, then read `/workplans/` with repo/topic filters. MCP is opt-in; use
`get_domain_summary(domain_slug)` when enabled.
2. Check inbox with `get_messages(to_agent=<repo-slug>, unread_only=true)`.
Mark acted-on messages with `mark_message_read(message_id)`.
3. During work, edit the workplan file first. Mirror task/workstream status to
the hub at checkpoints.
3. During work, edit the workplan file first. Use `workplan_id` in API payloads
and task queries; legacy `workstream_id` is accepted only for compatibility.
4. Prefer `bulk_update_task_statuses(...)` for checkpoint syncs with multiple
task updates. Use `update_task_status(...)` for one-off changes.
5. Close with one concise `add_progress_event(...)`, then run the repo's
`make fix-consistency REPO=<repo-slug>` command when workplan files changed.
5. Close with one concise progress event. After committing workplan file changes,
run `uv run --project ~/repo-manager rmgr sync --path . --push`. Reserve
`statehub fix-consistency` for a separate deep audit.
6. If finishing a workplan with leftovers: create **residual** work records
first (intake with `origin: residual` + `origin_ref: <WP-id>`, or a child
workplan). Residual is a role, not a kind; do not leave backlog only in
finished-file prose or `SCOPE.md`. Canon: work-record-types § Residuals.
## Canonical reads and caller attribution
Use `GET /workplans/{id}` and `GET /tasks/?workplan_id=<id>`.
The `/workstreams/` REST routes return 410 and the `create_workstream` MCP tool
has been removed. Create repository-owned records in files before syncing.
For direct HTTP calls, send `X-StateHub-Component: <repo-slug>` so a remaining
legacy call can be attributed to its caller. Do not replace authoritative
`state_hub_workstream_id` frontmatter fields; those are a separate file contract.
## High-Frequency MCP Signatures
```text
@ -33,12 +44,12 @@ get_domain_summary(domain_slug: str) -> str
get_messages(to_agent?: str, from_agent?: str, unread_only: bool = false, limit: int = 20) -> str
send_message(from_agent: str, to_agent: str, subject: str, body: str, thread_id?: str) -> str
create_workstream(topic_id: str, title: str, slug?: str, description?: str, owner?: str, due_date?: str, repo_id?: str, planning_priority?: str, planning_order?: int) -> str
create_task(workstream_id: str, title: str, priority: str = "medium", description?: str, assignee?: str, due_date?: str) -> str
create_workplan(repo_id: str, title: str, topic_id?: str, slug?: str, description?: str, owner?: str, due_date?: str, planning_priority?: str, planning_order?: int) -> str
create_task(workplan_id: str, title: str, priority: str = "medium", description?: str, assignee?: str, due_date?: str) -> str
update_task_status(task_id: str, status: str, blocking_reason?: str, tokens_in?: int, tokens_out?: int, workplan_tokens_in?: int, workplan_tokens_out?: int, note?: str, model?: str, agent?: str, session_id?: str) -> str
bulk_update_task_statuses(updates: list[dict], author?: str = "custodian", session_id?: str) -> str
add_progress_event(summary: str, event_type: str = "note", topic_id?: str, workstream_id?: str, task_id?: str, detail?: dict | str) -> str
record_decision(title: str, decision_type: str = "pending", topic_id?: str, workstream_id?: str, description?: str, rationale?: str, decided_by?: str, deadline?: str) -> str
add_progress_event(summary: str, event_type: str = "note", topic_id?: str, workplan_id?: str, task_id?: str, detail?: dict | str) -> str
record_decision(title: str, decision_type: str = "pending", topic_id?: str, workplan_id?: str, description?: str, rationale?: str, decided_by?: str, deadline?: str) -> str
```
`bulk_update_task_statuses` updates `N` task statuses in one call:

View file

@ -10,8 +10,8 @@ MCP: get_domain_summary(domain_slug)
REST: GET /state/summary then filter by topic/domain when MCP is unavailable
```
Use `get_domain_summary("custodian")` inside State Hub work. It returns the
domain topic, active workstreams, blocking decisions, recent progress, repos,
Use `get_domain_summary("infotech")` inside State Hub work. It returns the
domain topic, active workplans, blocking decisions, recent progress, repos,
and compact capability hints.
## Agent Messages
@ -30,19 +30,25 @@ REST: PATCH /messages/{message_id}/read
Use repo slugs as agent names. Use `broadcast` only for genuinely shared
coordination.
## Workstreams and Tasks
## Workplans and Tasks
```text
MCP: create_workstream(topic_id, title, slug?, description?, owner?, due_date?, repo_id?, planning_priority?, planning_order?)
REST: POST /workstreams/
MCP: create_workplan(repo_id, title, topic_id?, slug?, description?, owner?, due_date?, planning_priority?, planning_order?)
REST: POST /workplans/
MCP: create_task(workstream_id, title, priority="medium", description?, assignee?, due_date?)
MCP: create_task(workplan_id, title, priority="medium", description?, assignee?, due_date?)
REST: POST /tasks/
MCP: update_task_status(task_id, status, blocking_reason?, tokens_in?, tokens_out?, workplan_tokens_in?, workplan_tokens_out?, note?, model?, agent?, session_id?)
REST: PATCH /tasks/{task_id}
```
For repository-owned work, write the workplan/task files and sync with Repo
Manager; the create signatures above describe the projection API. Read using
`GET /workplans/{id}` and `GET /tasks/?workplan_id=<id>`. Send
`X-StateHub-Component: <repo-slug>` on direct HTTP requests for caller attribution.
The removed workstream tools and `/workstreams/` routes are not fallbacks.
Canonical task statuses are `wait`, `todo`, `progress`, `done`, and `cancel`.
Legacy aliases are accepted during migration, but do not emit new workplan files
with old vocabulary.
@ -86,10 +92,10 @@ The endpoint rejects duplicate task ids with `400` and missing task ids with
## Progress and Decisions
```text
MCP: add_progress_event(summary, event_type="note", topic_id?, workstream_id?, task_id?, detail?)
MCP: add_progress_event(summary, event_type="note", topic_id?, workplan_id?, task_id?, detail?)
REST: POST /progress/
MCP: record_decision(title, decision_type="pending", topic_id?, workstream_id?, description?, rationale?, decided_by?, deadline?)
MCP: record_decision(title, decision_type="pending", topic_id?, workplan_id?, description?, rationale?, decided_by?, deadline?)
REST: POST /decisions/
```

View file

@ -425,6 +425,16 @@ direct route are live owner residual `RAIL-FAB-WP-0028`. Evidence:
the non-D2 repo-manager/hub-core/activity-core/ops-hub route receipts and the
explicit writer-change authorization already stated above.
**Cutover re-baseline 2026-09-05.** Replaced the obsolete slice-readiness
snapshot in `docs/retirement-cutover-slice-plan.md` with a family-by-family
receiver/caller/parity/rollback checklist. Live Hub Core runtime OpenAPI lacks
`/messages`, `/progress`, `/token-events` and `/interface-changes`; its working
`/api/v2/interaction-events` is not assumed equivalent. Shared library routers
and HUB-WP-0005 completion are insufficient receiving receipts. T08 now records
the concrete first read-contract gap. State Hub-to-Core Hub service probes were
connection-refused although deployments were ready; transport is also part of
that gate. RAIL-FAB-WP-0028 remains proposed and owns hosted Fabric readiness.
## Retire legacy surfaces
```task
@ -536,6 +546,22 @@ calendar passage alone is not retirement evidence.
the existing hold; no additional retirement is justified. T04/T05/T06 remain
`wait` with their existing owner and quiet-window dependencies.
**Authorized retirement and caller migration 2026-09-05.** Fresh seven-day
primary evidence made `GET /sbom/report/licences/` and `GET /sbom/snapshots/`
eligible. Verified direct Nexus endpoints (200), then marked both interfaces
retired with T05 provenance. Receipt:
`docs/evidence/STATE-WP-0079-sbom-retirement-20260905.json`. Eight retired,
seven legacy, zero remaining candidates. Compatibility handlers/history remain;
this is meter retirement, not a router deletion or application deployment.
Session traces identified ad-hoc task-filter callers in fluid-telegram and
ops-warden. Corrected their AGENTS.md canonical-query/component-header guidance,
plus this repository's stale skill and API documentation. Canonical verification
returned 200 with 8/5 tasks. This does not explain every historical call or prove
future silence: unresolved task/workstream-detail/SBOM-index attribution is T07.
Available pod logs begin after the last calls. Evidence and limits:
`docs/evidence/STATE-WP-0079-caller-and-receiver-review-20260905.md`.
## Stabilization window and archive prep
```task
@ -555,6 +581,47 @@ stop, or repository archive would therefore interrupt normal work and destroy
the evidence needed by T05. T06 remains `wait` until T04 and T05 are done and a
fresh central meter demonstrates the required zero normal read/write window.
## Attribute and finish migrating remaining legacy readers
```task
id: STATE-WP-0079-T07
status: wait
priority: high
```
Owner: state-hub. T05 follow-up from the 2026-09-05 scan. Two ad-hoc task-query
callers were identified and their instructions corrected; the full 11 task
calls, seven workstream-detail calls and one SBOM index call remain only
partially attributed. Existing retained logs postdate these calls. Wait for
new attributed observations or recoverable historical request evidence; do not
reset the clocks with test calls. Trace remaining callers, migrate them to the
verified replacements, and attach fresh quiet-window evidence before retirement.
Use `X-StateHub-Component` for direct agent HTTP requests. No inference from
an `unknown` bucket alone is sufficient to assign an owner.
## Prove the first deployed hub-core message-read contract
```task
id: STATE-WP-0079-T08
status: todo
priority: high
```
Receiving owner: hub-core; State Hub owns source/caller evidence under T04.
The live receiver's 31-path OpenAPI has no message/progress/token/interface-log
families. Identify or assemble the owner message-read contract, auth/transport,
and historical projection in the receiving runtime. State Hub-to-owner service
probes were connection-refused in this review; diagnose the actual transport
before assuming a usable proxy path. Do not substitute `/api/v2/interaction-events`
without an explicit semantic mapping.
Then run a bounded read-only inbox parity pilot over one consistent snapshot,
checking message IDs, ordering, read/archive flags and scope filters; record
exact revisions and rollback to the retained State Hub read. Done when receiving
readiness and parity/rollback receipts make one concrete reader switch reviewable.
Production message writers stay unchanged during this proof. Related owners:
HUB-WP-0004/0005; the full B2/B3 route migration is not implied by this task.
## Acceptance
- [x] Freeze policy documented