state-hub/docs/retirement-cutover-slice-plan.md
tegwick 613477b4d2
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
docs(STATE-WP-0079): cutover slice plan, close T02
Assign all 425 SHR-INV-0001 items to 19 slices across five waves plus Keep,
each with adapter flag, owner API, and rollback. Coverage computed against
the inventory rather than hand-counted.

Only Wave A (repo-manager, 165 items) is executable; B/D1/D2 are gated on
owner-side contracts still at proposed. Flags two findings for T04/T05: the
9 ops_dependency items cannot be strangled behind a flag, and the
legacy-meter weekly review window is 8h, not a week.

Also corrects the workplan status from proposed to active.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 23:44:27 +02:00

249 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 | caller check; no successor runtime |
| E2 | `work-records-workplan-legacy` → archive | 13 | legacy-meter — see caveat below |
| E3 | `dashboard-meta` | 1 | none |
| 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.
### Caveat on E2's evidence
The 2026-08-19 review (`docs/evidence/legacy-meter-weekly-review-20260819.json`)
reports 19 retirement candidates, all `replacement_verified: true` with zero
calls in window. But the window is `06:00Z → 14:00Z` — **8 hours, not the 7 days
`cadence: weekly` implies**. Several candidates have very large all-time counts
(`GET /workstreams/{id}` 511 406, `GET /workstreams/` 144 086,
`POST /workstreams/{id}/dependencies/` 255 865).
An 8-hour quiet window is not sufficient evidence to retire an interface at that
call volume. Either the window computation is a defect or the cadence label is
wrong; either way **E2 should not proceed on the current evidence**. Resolving
this is a prerequisite, and it is worth checking before Wave A rather than after,
since the same meter gates T05.
## 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`.