state-hub/workplans/STATE-WP-0079-retirement-strangler.md
tegwick e2544b218d
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
docs(retirement): record empty projection rebuild proof
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a023c0-a0a3-7c03-b395-5a0d2757214d
2026-08-22 12:31:41 +02:00

420 lines
21 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-22"
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: progress
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`.
**Readiness update (2026-08-21):** RMGR-WP-0008 is finished, HUB-WP-0004 is
finished, and the cutover slice plan now records the corrected receiving/UI
boundaries. Workplan/register/classification adapters and compatibility tests
exist; execute them by slice rather than relying on this earlier readiness
snapshot.
**Deterministic-identifier compatibility sub-slice (2026-08-21):** fleet owner
declared namespace `helixforge`. Repo Manager's sealed plan covers 242 live
records across 42/42 eligible repositories with zero collisions, but apply is
not yet authorized. State Hub uses workplan/task UUIDs as primary keys referenced
by progress, decisions, dependencies, token events, launches, and other tables;
those foreign keys do not use `ON UPDATE CASCADE`. This task owns the temporary
transactional projection migration, durable old-id aliases, rollback rehearsal,
and compatibility evidence needed by RMGR-WP-0005-T04. Do not rewrite repository
UUID fields until that sub-slice passes in isolated PostgreSQL.
**Identifier projection gate implemented and rehearsed (2026-08-22):** Alembic
revision `b8d4f0a2c6e1` adds durable old→new alias provenance and converts all 20
foreign keys into `workplans.id` / `tasks.id` to `ON UPDATE CASCADE`, while
retaining their existing delete behavior. The internal executor verifies the
Repo Manager plan seal, `helixforge` namespace, UUIDv5 derivation, repository
membership, source presence, target absence, and alias consistency before a
repository-atomic apply. Reverse runs task-first/workplan-second and retains the
aliases as `reversed`; plan entries with `action: assign` are deferred to normal
file reconciliation because there is no old projection row to rewrite.
Isolated PostgreSQL evidence: migration upgrade produced 20/20 update-cascade
constraints plus the alias table; downgrade restored 20/20 no-action update
constraints and removed the table. Service tests cover cascades through task
parentage, progress, token accounting and dependency edges, forward/reverse
alias state, and all-or-nothing failure (`3 passed`). Full State Hub regression:
`622 passed`. No live database migration or repository UUID rewrite was run.
The remaining gate is an operator-approved pilot ordering the central database
transaction and authoritative file rewrite/reconciliation as one recoverable
cutover unit.
**Repo Manager pilot executed (2026-08-22):** the workstation projection was
the only instance holding `RMGR-WP-0005`; the production/registrar projection
had no matching row. A verified 4.4 MiB preapply PostgreSQL dump was retained at
`/tmp/state-hub-rmgr-wp-0005-pilot-preapply.dump`. The live schema advanced to
`b8d4f0a2c6e1`, then six workplan/task primary keys migrated in one transaction.
All 12 child-task links, 12 workplan progress links, one decision link, and
three task progress links followed. Six aliases are `applied`; all old rows are
absent and all new rows present. Repo Manager committed the matching file
rewrite in `5de754a`; two consistency runs produced a fresh, non-stale index and
the new workplan API lookup returns 200 while the old lookup returns 404.
The absent registrar row exposed the final rebuild gap before remote ingestion:
create schemas did not accept authoritative UUIDs and consistency classified a
missing derived row as an unfixable stale reference. The projection rebuild path
now accepts file UUIDs, derives IDs when a live file has none, and registers a
missing derived workplan/task without rewriting the authoritative identifier.
Focused rebuild/API tests and the full State Hub suite pass (`624 passed`).
**Empty-projection rebuild proof (2026-08-22):** consistency now has an explicit,
registrar-only `--bootstrap-empty-projection` mode. It is limited to one local
repository, proves the target has zero projected workplans before mutation, and
preserves authoritative UUIDs for workplans, tasks, intakes, and decisions.
Repo Manager's governed wrapper additionally verifies every expected UUID and
fails on authoritative checkout mutation. A fresh disposable database rebuilt
all 9 Repo Manager workplans, 62 tasks, and 2 intakes; a second read-only pass
reported zero issues and 9/9 bindings. State Hub commits `03c7924` and
`059de93`; evidence is recorded in Repo Manager under
`docs/evidence/RMGR-WP-0005-isolated-rebuild-2026-08-22.md`. Hub-native history
remains a separate, unresolved centralization gate and was not copied.
**Production registrar pilot completed (2026-08-22):** a verified 4.6 MiB
custom-format dump was retained at
`/tmp/state-hub-registrar-rmgr-wp-0005-preapply.dump` (SHA-256
`01753744bc844b154900ead916f122946c5414e77f888c3e18016cae6d3d8929`). Helm
revision 10 atomically promoted image `main-697dd49`; the dry-run and live pod
both confirmed `sweep.enabled: false` with no host or SSH mounts. The production
schema advanced from `f0a1b2c3d4e5` to `b8d4f0a2c6e1`; health remained green,
all 20 relevant foreign keys use `ON UPDATE CASCADE`, and the alias table exists.
The registrar did not previously contain the logical `repo-manager` repository
or its workplans. After registering that repository, governed projection repair
created only deterministic `RMGR-WP-0005` UUID
`6dcf854e-9229-569d-90f0-2d9df235a61d` and its 12 authoritative task UUIDs.
File and production task-ID sets match exactly; the new workplan lookup returns
200, the former random UUID returns 404, and a second read-only consistency pass
has no `RMGR-WP-0005` issue. The eight other random-ID Repo Manager workplans
remain explicit non-fixable stale references in this formerly empty projection
and were not ingested. Production aliases remain empty as expected for a rebuild
rather than an in-place rewrite. Widening beyond this one-repository pilot still
requires a separate fleet cutover decision.
**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