diff --git a/api/routers/workstreams.py b/api/routers/workstreams.py index 8bf3547..7305d6c 100644 --- a/api/routers/workstreams.py +++ b/api/routers/workstreams.py @@ -412,21 +412,26 @@ async def list_workplans( ) -@router.get("/workplan-index") +@router.get("/workplan-index", status_code=status.HTTP_410_GONE) async def workplan_index( request: Request, response: Response, refresh: bool = Query(False, description="Force cache invalidation"), session: AsyncSession = Depends(get_session), -) -> dict[str, Any]: +) -> None: + replacement_ref = "/workplans/index" await _meter_legacy_route( session=session, request=request, response=response, interface_key=_legacy_key("GET", "/workstreams/workplan-index"), - replacement_ref="/workplans/index", + replacement_ref=replacement_ref, + ) + raise HTTPException( + status_code=status.HTTP_410_GONE, + detail="Legacy GET /workstreams/workplan-index retired; use GET /workplans/index", + headers=legacy_response_headers(replacement_ref), ) - return await _workplan_index(refresh=refresh, session=session) @workplan_router.get("/index") diff --git a/docs/evidence/railiance01-state-hub-deploy-20260709.md b/docs/evidence/railiance01-state-hub-deploy-20260709.md new file mode 100644 index 0000000..c21e13b --- /dev/null +++ b/docs/evidence/railiance01-state-hub-deploy-20260709.md @@ -0,0 +1,51 @@ +# railiance01 State Hub deploy — workstream caller migration + +Date: 2026-07-09 +Workplan: STATE-WP-0070 T01 +Blocked: `git pull` on railiance01 failed with `.git/FETCH_HEAD` permission denied. + +## Why + +Legacy-meter shows ~150k weekly `GET /workstreams/*` calls. Local migrations +(≥ `b05ca2c`) moved fix-consistency and activity-core to `/workplans/`. The +railiance01 sweep host was still at `050cbbc` when checked via SSH. + +## Target revisions + +| Repo | Minimum commit | Notes | +| --- | --- | --- | +| `state-hub` | `7e68f9e` | Caller migrations + identity headers + workplan-index 410 | +| `activity-core` | `5a92923` | `/workplans` resolver + progress `workplan_id` only | + +## Steps (operator on railiance01) + +```bash +# 1. Fix checkout permissions if needed (example — adjust user/group) +sudo chown -R "$(whoami):$(id -gn)" ~/state-hub/.git ~/activity-core/.git + +# 2. Pull both repos +cd ~/state-hub && git fetch origin main && git pull --ff-only origin main +git log -1 --oneline # expect 7e68f9e or newer + +cd ~/activity-core && git fetch origin main && git pull --ff-only origin main +git log -1 --oneline # expect 5a92923 or newer + +# 3. Optional: one-shot consistency sweep against workstation hub via tunnel +# (API must reach the hub — typically http://127.0.0.1:8000 through ops-bridge) +statehub fix-consistency --all --remote --api-base http://127.0.0.1:8000 + +# 4. Verify legacy-meter attribution (from any host with hub API access) +python ~/state-hub/scripts/capture_legacy_meter_evidence.py --days 7 +# Expect component bucket state-hub.fix-consistency; declining GET /workstreams/* +``` + +## Verification checklist + +- [ ] `state-hub` and `activity-core` at target commits on railiance01 +- [ ] Consistency sweep completes without C-00 automation errors +- [ ] Weekly legacy-meter review shows `state-hub.fix-consistency` (not only `unknown`) +- [ ] `GET /workstreams/*` 7-day window counts trend down week-over-week + +## Rollback + +`git checkout ` in each repo; no schema migration involved. \ No newline at end of file diff --git a/docs/workplan-terminology-legacy-retirement-backlog.md b/docs/workplan-terminology-legacy-retirement-backlog.md index cbff78f..dedbad8 100644 --- a/docs/workplan-terminology-legacy-retirement-backlog.md +++ b/docs/workplan-terminology-legacy-retirement-backlog.md @@ -47,7 +47,7 @@ Risk order: REST > MCP > events > dashboard prose > internal identifiers. | 1 | `rest_api:GET /workstreams/{workstream_id}` | `GET /workplans/{workplan_id}` | REST | T04 | | 1 | `rest_api:PATCH /workstreams/{workstream_id}` | `PATCH /workplans/{workplan_id}` | REST | T04 | | 1 | ~~`rest_api:DELETE /workstreams/{workstream_id}`~~ **retired** (410) | `DELETE /workplans/{workplan_id}` | REST | T04 ✓ | -| 1 | `rest_api:GET /workstreams/workplan-index` | `GET /workplans/index` | REST | T04 | +| 1 | ~~`rest_api:GET /workstreams/workplan-index`~~ **retired** (410) | `GET /workplans/index` | REST | T04 ✓ | | 2 | `rest_api:GET /workstreams/{workstream_id}/dependencies/` | `GET /workplans/{workplan_id}/dependencies/` | REST | T04 | | 2 | `rest_api:POST /workstreams/{workstream_id}/dependencies/` | `POST /workplans/{workplan_id}/dependencies/` | REST | T04 | | 2 | `rest_api:DELETE /workstreams/{workstream_id}/dependencies/{dep_id}` | `DELETE /workplans/{workplan_id}/dependencies/{dep_id}` | REST | T04 | diff --git a/docs/workplan-terminology-transition.md b/docs/workplan-terminology-transition.md index 6adb494..6a95d1f 100644 --- a/docs/workplan-terminology-transition.md +++ b/docs/workplan-terminology-transition.md @@ -30,7 +30,7 @@ Physical database renames are intentionally out of scope for this workplan. | Read workplan | `GET /workplans/{workplan_id}` | `GET /workstreams/{workstream_id}` | `rest_api:GET /workstreams/{workstream_id}` | | Update workplan | `PATCH /workplans/{workplan_id}` | `PATCH /workstreams/{workstream_id}` | `rest_api:PATCH /workstreams/{workstream_id}` | | Archive workplan | `DELETE /workplans/{workplan_id}` | ~~`DELETE /workstreams/{workstream_id}`~~ **410 Gone** (2026-07-08) | `rest_api:DELETE /workstreams/{workstream_id}` (retired) | -| Workplan index | `GET /workplans/index` | `GET /workstreams/workplan-index` | `rest_api:GET /workstreams/workplan-index` | +| Workplan index | `GET /workplans/index` | ~~`GET /workstreams/workplan-index`~~ **410 Gone** (2026-07-09) | `rest_api:GET /workstreams/workplan-index` (retired) | | Workplan dependencies | `GET/POST /workplans/{workplan_id}/dependencies/` | `GET/POST /workstreams/{workstream_id}/dependencies/` | matching `rest_api:* /workstreams/...` keys | | Delete dependency | `DELETE /workplans/{workplan_id}/dependencies/{dep_id}` | `DELETE /workstreams/{workstream_id}/dependencies/{dep_id}` | `rest_api:DELETE /workstreams/{workstream_id}/dependencies/{dep_id}` | | Execution intent | `PATCH /execution/workplans/{workplan_id}/intent` | `PATCH /execution/workstreams/{workstream_id}/intent` | `rest_api:PATCH /execution/workstreams/{workstream_id}/intent` | diff --git a/tests/test_legacy_meter.py b/tests/test_legacy_meter.py index ac1d630..bac4456 100644 --- a/tests/test_legacy_meter.py +++ b/tests/test_legacy_meter.py @@ -165,6 +165,20 @@ class TestWorkplanAliasesAndLegacyMeter: } assert items.get("event_subject:org.statehub.workstream.completed") is None + async def test_legacy_workplan_index_returns_410(self, client): + r = await client.get( + "/workstreams/workplan-index", + headers={"X-StateHub-Component": "old-index-client"}, + ) + assert r.status_code == 410 + assert "retired" in r.json()["detail"] + assert r.headers["X-StateHub-Replacement"] == "/workplans/index" + + summary = (await client.get("/legacy-meter/summary")).json() + item = _summary_by_key(summary)["rest_api:GET /workstreams/workplan-index"] + assert item["window"]["calls"] == 1 + assert item["window"]["components"] == {"old-index-client": 1} + async def test_legacy_delete_workstream_returns_410(self, client): await _create_domain(client) topic = await _create_topic(client) diff --git a/tests/test_routers_core.py b/tests/test_routers_core.py index b211536..1dfa38b 100644 --- a/tests/test_routers_core.py +++ b/tests/test_routers_core.py @@ -189,8 +189,12 @@ class TestWorkstreams: async def test_workplan_index_route(self, client): r = await client.get("/workstreams/workplan-index") + assert r.status_code == 410 + assert r.headers["X-StateHub-Replacement"] == "/workplans/index" + + r = await client.get("/workplans/index") assert r.status_code == 200 - assert "workstreams" in r.json() + assert "workplans" in r.json() async def test_workplan_bindings_sync_populates_index(self, client, tmp_path): await _create_domain(client) diff --git a/workplans/STATE-WP-0070-workplan-terminology-phase2-retirement.md b/workplans/STATE-WP-0070-workplan-terminology-phase2-retirement.md index 10d4fac..70c57bf 100644 --- a/workplans/STATE-WP-0070-workplan-terminology-phase2-retirement.md +++ b/workplans/STATE-WP-0070-workplan-terminology-phase2-retirement.md @@ -57,13 +57,14 @@ Progress 2026-07-09: identity headers landed on fix-consistency, custodian_cli, dashboard loaders, cleanup_stale_tasks, validate_repo_adr. Local sweep attributes `state-hub.fix-consistency` on task-create metering. railiance01 still at `050cbbc`; `git pull` blocked (`.git/FETCH_HEAD` permission denied) — needs -operator deploy. +operator deploy. Hub message posted to `ops-warden` (2026-07-09); runbook at +`docs/evidence/railiance01-state-hub-deploy-20260709.md`. ## Task: Retire remaining REST `/workstreams` routes per key ```task id: STATE-WP-0070-T02 -status: todo +status: progress priority: high state_hub_task_id: "13122195-3b7e-4e22-a5e8-4ebe7e9a093f" ``` @@ -77,6 +78,10 @@ still showing usage: Return **410 Gone** (matching DELETE pattern) when legacy-meter shows seven consecutive zero-usage windows. Update tests and capture evidence per retirement. +Progress 2026-07-09: `GET /workstreams/workplan-index` → **410 Gone** (zero-usage +candidate in 1-day capture). `DELETE /workstreams/{id}` already 410 from STATE-WP-0069. +Next candidates when meter clears: `POST .../dependencies/` (0 calls in 1-day window). + ## Task: MCP alias removal (Phase 2) ```task