state-hub/workplans/STATE-WP-0079-retirement-strangler.md
tegwick 190bb41b89 docs(STATE-WP-0079): registrar bottleneck status check
The registrar is reachable and always was: state-hub-primary binds [::1]:8000
while the workstation API listens on 127.0.0.1:8000, so an IPv4 probe hits
the local hub and reads as a dead tunnel.

But the two databases have diverged by roughly a third -- 987 vs 649
workplans, 5710 vs 4037 tasks -- and the registrar runs pre-rename code. Nine
registrar sync requests from three agents have queued since 2026-08-20.

Re-checked the chain: RMGR-WP-0004-T08 is done, so RMGR-WP-0005-T03's stated
blocker has closed. Its substantive blocker has not -- ADR-007 Migration
option 2 permits historical identifier collisions, and UUIDv5 derivation from
a non-unique identifier manufactures collisions.

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

333 lines
15 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.

---
id: STATE-WP-0079
type: workplan
title: "State Hub retirement strangler and disposition execution"
domain: infotech
repo: state-hub
status: active
owner: codex
topic_slug: infotech
created: "2026-08-09"
updated: "2026-08-19"
parent_project: prj-state-hub-retirement
parent_workplan: SHR-WP-0001
related:
- STATE-WP-0074
- SHR-INV-0001
- RMGR-WP-0001
- HUB-WP-0004
state_hub_workstream_id: "749beac6-3c62-4284-aab5-9ed7fce900c2"
---
# State Hub retirement strangler and disposition execution
## Goal
Execute the **keep/move/replace/retire** dispositions from
`prj-state-hub-retirement/inventory` via strangler adapters and owner handoffs,
without adding new permanent authorities here. End state: freeze window with no
normal traffic, then archive.
## Dual-run handoff (RMGR-WP-0002)
Repo Manager Stage B dual-run is available:
- Flags: `RM_WRITEBACK`, `RM_RECONCILE`, `RM_PILOT_REPOS` (see `repo-manager/docs/dual-run.md`)
- SH adapter: `api/services/repo_manager_dual_run.py` (PATCH `/tasks` + C-15 writeback + reconcile proxy)
- Evidence: `repo-manager/docs/evidence/wp0002-completion.md`
Expand pilot list here as cutover progresses; do not add new checkout mutators in State Hub.
## Freeze policy for new scope
```task
id: STATE-WP-0079-T01
status: done
priority: high
state_hub_task_id: "375c0b66-98b0-4d5b-98dd-4ad2883a4051"
```
Document and enforce: changes during retirement must preserve compatibility,
enable extraction, reduce scope, or fix operational risk — not establish new
permanent ownership (INTENT retirement status).
**Result (2026-08-20):** `policies/retirement-freeze.md`, following the existing
`policies/` convention. States the four admissible reasons, the inadmissible
case (new permanent ownership here), and the operative test — *where does this
live after cutover?* — since most inadmissible changes are otherwise good
changes. Uses `STATE-WP-0080`'s retarget as the worked example.
**Enforced by review, not by new automation.** A freeze-checker built inside
State Hub would be new permanent automation in the component being retired, so
the policy would violate itself. Gates attach to points where changes are
already reviewed: new workplan goal statements, non-trivial task starts, and
slice execution. New tables, routes, or MCP tools carry a presumption against
and are the one hard stop.
## Capability strangler plan
```task
id: STATE-WP-0079-T02
status: done
priority: high
state_hub_task_id: "595c3936-20b6-453f-92b5-84f86798054a"
```
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
id: STATE-WP-0079-T03
status: done
priority: medium
state_hub_task_id: "55716f12-fb12-4a1e-a770-076c490db111"
```
Adapt `statehub register` / scaffolding so `prj-` repos with `GOAL.md` and
`repo_flavor: project` are valid without inventing durable-product `INTENT.md`
(residual from SHR-WP-0001-T01).
**Result (2026-08-19):** Implemented by `STATE-WP-0080-T01/T02`. Project
flavor is detected from classification, `GOAL.md`, or the `prj-` slug; a repo
with an existing `GOAL.md` is registered without durable scaffolding, while a
fresh project delegates scaffolding to `rmgr scaffold`. The focused State Hub
registration suite passes (`20 passed`).
## Execute move/replace slices
```task
id: STATE-WP-0079-T04
status: todo
priority: high
state_hub_task_id: "db5291f2-3801-40fa-abf2-ceb0e77687c9"
```
Implement strangler handoffs coordinated with RMGR-WP-0001 and HUB-WP-0004.
Compatibility tests for each dispositioned route family.
**Owner-readiness correction (2026-08-20):** T02's slice plan marked all 167
repo-manager items receivable because RMGR-WP-0001/0002/0003 are `finished`.
That reads readiness off workplan status, which is too coarse. Verified against
`repo-manager/src/repo_manager/`: those workplans delivered work-record
dual-run, repository observation, and scaffolding. `parse/` holds only
`workplan.py`, and the source tree has **no** reference to `sbom`, `repo_goal`,
`contribution`, `technical_debt`, or `extension_point`.
So only **A1 (partial) and A2** are receivable — 86 items, not 165. A3
(registers, 49), A4 (UI, 22) and A5 (topic spine, 8) have no owner-side surface
at all, and RMGR-WP-0004/0005 do not cover them.
**Critical path is now repo-manager-side**, not here: a repo-manager workplan
must build the register, UI, and topic surfaces before 79 Wave A items can move.
Building them in State Hub is inadmissible under `policies/retirement-freeze.md`
— it would be new permanent ownership in the component being retired.
**A2 checked the same way (2026-08-20):** the State Hub adapter exposes exactly
three operations — `rm_update_task_status`, `rm_reconcile`, `rm_scaffold`. There
is no adapter for workplan CRUD, intake, decision, dependency, or human-flag
records, and no repo-manager parser for them either. A2 therefore splits into
**A2a** (task status + reconcile, ~6 items, receivable) and A2bA2e (~32 items,
not receivable).
**A2a is the only slice in the whole plan executable today.** Its next step is
operational rather than code: `rmgr dual-run-status` shows dual-run entirely off
(`config_exists: false`, writeback/reconcile false, no pilot repos; the 23
metered mutations are RMGR-WP-0002/0003 pilot residue). Moving A2a from `off` to
`dual` means creating `~/.repo-manager/dual-run.yaml`, enabling `RM_WRITEBACK`
and `RM_RECONCILE` for one pilot repo, and watching the mutation meter for
divergence. Reversible by clearing the flags; needs an operator decision because
it changes how live task writes flow.
Net: of 425 inventory items, roughly **6 can move today**. The retirement is
gated almost entirely on owner-side capability in `repo-manager` and `hub-core`.
**A2a executed (2026-08-20) — first live cutover slice.** Dual-run is on for the
pilot repo:
- `~/.repo-manager/dual-run.yaml` installed from
`repo-manager/config/dual-run.pilot.example.yaml``writeback: true`,
`reconcile: true`, `writeback_push: false`, `pilot_repos: [repo-manager]`
- State Hub API reloaded via its own `--reload` watcher (no service restart, the
operator's foreground `make api` was left alone)
- `statehub fix-consistency --repo repo-manager` reported
`RM dual-run reconcile ok: index=/home/worsch/repo-manager/.repo-manager/index.json`
- mutation meter grew 23 → 24 with
`{"kind": "reconcile", "repo_slug": "repo-manager", "workplan_count": 8, "task_count": 53, "record_count": 61}`
**Divergence check:** rmgr's index holds 8 workplans; the hub has
`rmgr-wp-0001``0007` registered. The single-item delta is `RMGR-WP-0008`, whose
C-06 registration was skipped because this instance is not the identifier
registrar (ADR-007 interim). That is a known registrar limitation, **not**
dual-run divergence — rmgr correctly indexed the file that exists on disk.
Slice state: **A2a is `dual`, not `owner`** — the hub remains authoritative and
rmgr executes checkout mutation alongside it. Promotion to `owner` needs a full
consistency-sweep cycle at `dual` with zero divergence, per this plan's adapter
rules. Rollback is `rm ~/.repo-manager/dual-run.yaml`.
### Registrar bottleneck — status check 2026-08-21
**The registrar is reachable, and was all along.** `bridge status` shows all
seven tunnels `connected` and LIVE, including `state-hub-primary`. It binds
`[::1]:8000` — IPv6 localhost — while the workstation API listens on
`127.0.0.1:8000` (IPv4). Same port number, different stacks, so a `127.0.0.1`
probe silently hits the local hub and looks like the tunnel is down. Anything
checking registrar reachability must use the bracketed IPv6 form.
**But the two databases have diverged badly:**
| | workstation (`127.0.0.1:8000`) | registrar (`[::1]:8000`) |
| --- | --- | --- |
| workplans | 987 | 649 |
| tasks | 5 710 | 4 037 |
| decisions | 119 | 80 |
The registrar is **338 workplans and 1 673 tasks behind**. It also runs older
code — its `/state/summary` still returns `open_workstreams` where the
workstation returns `open_workplans`, so it predates the terminology rename.
This is exactly the `ADR-007` defect `RMGR-WP-0005` describes, at fleet scale:
two instances over two databases, each minting database-local primary keys into
shared git artifacts.
**A queue has formed.** Ten unread messages to `state-hub`, nine of them
registrar sync requests from three agents (`agentic-resources`, `glas-harness`,
`kaizen-agentic`), all dated 2026-08-20 — plus this repo's own `RMGR-WP-0008`.
Agents fleet-wide are now blocked on identifier registration.
**The dependency chain, re-checked:**
- `RMGR-WP-0004-T08` (prefix uniqueness enforcement) — **`done`** 2026-08-18
- `RMGR-WP-0004-T09` (assign shared prefixes) — executed 2026-08-17 as an
authorized exception, still `wait`, with one stated residual: *"Not yet
reconciled — the workstation is not the registrar"*
- `RMGR-WP-0005-T03` (deterministic UUIDv5 derivation) — still `wait`, but its
stated blocker `RMGR-WP-0004-T08` **has closed**
T03 is the permanent fix: derived identifiers make writeback idempotent, so any
number of hubs write identical bytes and the queue disappears. Its formal gate is
clear. Its *substantive* gate is not — `ADR-007` § Migration option 2 lets
historical files keep colliding identifiers (`CUST-WP`, `RAILIANCE-WP`, and
`PRJ-WP` in `prj-forgejo-org-refactor` all still present in the 2026-08-18 scan),
and deriving UUIDv5 from a non-unique identifier manufactures collisions rather
than removing them. That tension needs a ruling before T03 can start.
**Inherited (2026-08-19) from `STATE-WP-0080-T04`:** this slice now also owns
deletion of the templating path in `statehub_register.py``INTENT.md`
generation, the `AGENTS.md` / `CLAUDE.md` / `.custodian-brief.md` templates,
`project_rules/`, and bootstrap-workplan generation. The code is dead-but-present:
project repos are already routed to `rmgr scaffold`. Gate the deletion on
`RMGR-WP-0004-T06` proving byte-identical durable output and idempotent
re-scaffolding.
## Retire legacy surfaces
```task
id: STATE-WP-0079-T05
status: todo
priority: medium
state_hub_task_id: "02e508ed-3cde-4487-907e-d324a8a877d6"
```
Complete retirement of suggestions, workstream aliases, and other `retire`
inventory items once meters/callers allow. Keep historical rows archive-readable.
**Blocker identified (2026-08-20):** the legacy-meter evidence this task gates
on was being captured over an 8-hour band, not a week —
`capture_legacy_meter_evidence.py` fell back to `hours=8` whenever `--days` was
omitted, so 39 of 40 captures sampled only 06:00Z14:00Z. Interfaces called
outside that band read as unused. Three candidates on the 2026-08-19 capture had
live callers, including `GET /tasks/?workstream_id` (seen 2026-08-18).
Script default corrected to `days=7`; corrected capture
`docs/evidence/legacy-meter-weekly-review-20260820.json`.
**Candidate rule tightened (2026-08-20):** `_retirement_state` now requires a
quiet period scaled to all-time call volume (`RETIREMENT_QUIET_LADDER`: 7d under
100 calls, 30d under 10 000, 60d above). On the 2026-08-20 capture this makes 15
of 19 legacy interfaces retirable — all `workstream` MCP tools, the `state://`
resource, and the low-volume REST aliases — while holding the three six-figure
read paths (`GET /workstreams/`, `GET /workstreams/{id}`,
`GET /workstreams/{id}/dependencies/`) and `GET /tasks/?workstream_id`, which
still has live traffic. Covered by 7 new tests in `tests/test_legacy_meter.py`.
**Slice E2 executed (2026-08-20):** the 15 evidenced interfaces are retired in
the legacy meter — all four `workstream` MCP tools, the `state://` resource, and
the low-volume REST aliases including `PATCH /workstreams/{id}` (571 calls),
`POST /workstreams/` (824) and `POST /workstreams/{id}/dependencies/` (4 971).
Meter now reads 16 retired / 4 legacy, 0 outstanding candidates. Evidence:
`docs/evidence/legacy-meter-weekly-review-20260820.json`.
Retirements are journalled fleet-side at
`the-custodian/docs/retired-legacy-interfaces.md` — deliberately outside this
repo, since State Hub is being archived and the record must outlive it.
**Remaining work for this task:** the 4 held interfaces
(`GET /workstreams/`, `GET /workstreams/{id}`,
`GET /workstreams/{id}/dependencies/`, `GET /tasks/?workstream_id`) clear as
their quiet counters run; earliest is 18 more silent days. Re-capture before
deciding — the ladder evaluates at review time. **Slice E1 part-executed (2026-08-20):** `suggestion-backlog` history archived
to `the-custodian/docs/archived-suggestion-backlog.md` — 10 suggestions, 10
notes, 5 bumps, all `declined` in the 2026-07-21 intake migration, none
promoted. Mutations already 410 since `CUST-WP-0061-T06`; reads were open only
to keep that history reachable, which the archive now replaces.
**E1 removal executed the same day:** the 8 read routes 410 with a pointer to
intake and the archive, the 6 MCP tools are removed outright, the 5
`/suggestions` write-idempotency rules are dropped, and the dashboard page is
deleted with nav, `reference.md` and `wsjf-triage.md` updated;
`dashboard/src/docs/suggestions.md` is now the archive pointer. Tables remain
for the T06 dump. Two tests that pinned the old read-live behaviour were
rewritten to pin the retirement contract.
Naming trap for cutover: `ui-feedback.md` / `todo.md` "suggestions" are
Shift+click dashboard feedback backed by `technical_debt`, **not** this
capability. Untouched.
**E3 (`dashboard-meta`) is `/ui-feedback`** — that same feedback surface. Owner
is `state-hub-until-cutover` and it is still in use, so it retires at the T06
freeze window, not now.
Then `dashboard-meta` (E3, 1); `legacy-meter` itself (E4, 9) retires last,
being the instrument.
## Stabilization window and archive prep
```task
id: STATE-WP-0079-T06
status: todo
priority: high
state_hub_task_id: "d52c95c6-af3f-4b2c-804e-e07763c9a8ab"
```
With T06 gates: zero normal read/write window, final dump, backup/restore
evidence, stop service, archive repo read-only. Residuals → live owners.
## Acceptance
- [x] Freeze policy documented
- [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
- [ ] Stabilization + archive prep ready for project retirement