state-hub/docs/retirement-cutover-slice-plan.md
tegwick 81861d816b
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Build and Publish Multi-Context Image / build-and-push (push) Successful in 25s
feat(retirement): route SBOM scans through repo-manager
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a023c0-a0a3-7c03-b395-5a0d2757214d
2026-08-21 23:15:34 +02:00

21 KiB
Raw Blame History

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 Normgr 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.

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:

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
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) · all gated

Slice Owner Capabilities Items Gate
D1 ops-hub service-catalog (9), service-catalog-ui (2) 11 OPS-WP-0003 proposed
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
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

E4 retires only after every other legacy surface is closed; retiring the meter first destroys the evidence for the retirements it gates.

E1 status (2026-08-20)

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.

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.

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.

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.

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.