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>
15 KiB
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:
- A slice cannot move before its owner can receive it. Owner readiness is the owner repo's own workplan status, not an assertion here.
- 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, evidencerepo-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 B–D 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 A1–A3, 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 A1–A4 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:00Z–14: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
- Resolve the legacy-meter window discrepancy above — it gates E2 and T05.
- Implement
RM_SLICE_REGISTERS(A3) first as the pattern proof. - Do not open Wave B until HUB-WP-0004 reaches
ready.