state-hub/docs/retirement-cutover-slice-plan.md
tegwick fb363b37d3
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Build and Publish Multi-Context Image / build-and-push (push) Successful in 37s
feat(STATE-WP-0079): retire the suggestion-backlog surface (slice E1)
History is archived fleet-side, so the read surface has no remaining job.

- api/routers/suggestions.py: whole prefix 410s with a pointer to intake and
  to the archive; the router drops from 176 lines to a stub
- mcp_server: the 6 suggestion tools removed outright rather than stubbed --
  a retired tool that still appears in the tool list costs every agent
  session context on every call, which is the opposite of retiring it
- write_idempotency: 5 /suggestions rules dropped
- dashboard: suggestions.md deleted, nav entry removed, reference.md and
  wsjf-triage.md updated; docs/suggestions.md rewritten as archive pointer
- tests: two tests pinned the old read-live behaviour and now pin the
  retirement contract instead

Tables stay: they are retire/archive in SHR-INV-0001 and are captured by the
final dump at T06.

Untouched, and worth knowing during cutover: ui-feedback.md / todo.md
'suggestions' are Shift+click dashboard feedback backed by technical_debt, a
different entity that shares the word. E3 (dashboard-meta) is that page; its
owner is state-hub-until-cutover so it retires at the T06 window, not now.

Full suite 612 passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 08:11:14 +02:00

15 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 Yes
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 fully unblocked today. Wave A is the whole of the near-term executable surface; everything else is correctly waiting on an owner-side contract, not on State Hub.

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.

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 endpoints
Rollback flag → off; these are append-mostly, so replay is cheap

Low coupling and low write volume — the natural place to prove the RM_SLICE_* pattern before A2's blast radius.

A4 · Work + repo UI — 22 items

work-coordination-ui (14, replace) · repo-ui (8)

Flag RM_SLICE_UI
Owner API owner-side dashboard; hub pages redirect
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.

A5 · Topic spine — 8 items

topic-spine (8, replace)

Flag RM_SLICE_TOPICSPINE
Owner API rmgr topic/classification contract
Rollback flag → off

Last in Wave A: it is a replace under a changed contract, and A1A4 read it.


Wave B — hub-core (128 items) · gated on HUB-WP-0004

HUB-WP-0004 is proposed. No hub-side adapter work until it is at least ready. Slices are listed to fix scope and order, not to start.

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 above — it gates E2 and T05.
  2. Implement RM_SLICE_REGISTERS (A3) first as the pattern proof.
  3. Do not open Wave B until HUB-WP-0004 reaches ready.