diff --git a/docs/retirement-cutover-slice-plan.md b/docs/retirement-cutover-slice-plan.md new file mode 100644 index 0000000..3b4f5c9 --- /dev/null +++ b/docs/retirement-cutover-slice-plan.md @@ -0,0 +1,249 @@ +# 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_` 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 | 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`. diff --git a/workplans/STATE-WP-0079-retirement-strangler.md b/workplans/STATE-WP-0079-retirement-strangler.md index 5643d3a..1e328c4 100644 --- a/workplans/STATE-WP-0079-retirement-strangler.md +++ b/workplans/STATE-WP-0079-retirement-strangler.md @@ -4,7 +4,7 @@ type: workplan title: "State Hub retirement strangler and disposition execution" domain: infotech repo: state-hub -status: proposed +status: active owner: codex topic_slug: infotech created: "2026-08-09" @@ -55,7 +55,7 @@ permanent ownership (INTENT retirement status). ```task id: STATE-WP-0079-T02 -status: todo +status: done priority: high state_hub_task_id: "595c3936-20b6-453f-92b5-84f86798054a" ``` @@ -64,6 +64,22 @@ Group SHR-INV-0001 items into cutover slices (repo/work → repo-manager; messages/progress/projections → hub-core; catalogs → owners; retire aliases). Per slice: adapter flag, owner API, rollback. +**Result (2026-08-19):** `docs/retirement-cutover-slice-plan.md`. All 425 +inventory items assigned to 19 slices in five waves plus Keep, computed against +`state-hub-disposition.yaml` rather than hand-counted. Each slice carries an +`RM_SLICE_*` flag (off/dual/owner), owner API, and rollback; Wave A reuses the +RMGR-WP-0002/0003 dual-run adapter rather than adding a mechanism. + +Owner readiness verified: only **Wave A (repo-manager, 165 items)** is +executable — RMGR-WP-0001/0002/0003 are `finished`. Wave B is gated on +HUB-WP-0004 (`proposed`), D1 on OPS-WP-0003 (`proposed`), D2 on FIN-WP-0003 +(`proposed`). Recommended first cut is A3 (registers), lowest blast radius. + +Two findings for T04/T05: the 9 `ops_dependency` items carry no capability and +cannot be strangled behind a flag — they resolve at the T06 freeze window; and +the legacy-meter review window is 8 hours despite `cadence: weekly`, which is +not sufficient evidence to retire high-volume `/workstreams/` interfaces (E2). + ## Register CLI and GOAL.md support ```task @@ -130,7 +146,7 @@ evidence, stop service, archive repo read-only. Residuals → live owners. ## Acceptance - [ ] Freeze policy documented -- [ ] Strangler plan covers all inventory capabilities +- [x] Strangler plan covers all inventory capabilities - [x] prj-/GOAL.md register path works - [ ] Move/replace slices evidenced with tests/counts - [ ] Retire list closed or residual-owned