Retire GET /workstreams/workplan-index with 410; railiance01 deploy runbook
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s

Return 410 Gone for legacy workplan-index route (replacement /workplans/index).
Add tests, update transition backlog, operator deploy evidence doc, and
STATE-WP-0070 progress notes.
This commit is contained in:
tegwick 2026-07-09 00:43:08 +02:00
parent 7e68f9e8e3
commit 14b7d8418d
7 changed files with 88 additions and 9 deletions

View file

@ -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( async def workplan_index(
request: Request, request: Request,
response: Response, response: Response,
refresh: bool = Query(False, description="Force cache invalidation"), refresh: bool = Query(False, description="Force cache invalidation"),
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
) -> dict[str, Any]: ) -> None:
replacement_ref = "/workplans/index"
await _meter_legacy_route( await _meter_legacy_route(
session=session, session=session,
request=request, request=request,
response=response, response=response,
interface_key=_legacy_key("GET", "/workstreams/workplan-index"), 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") @workplan_router.get("/index")

View file

@ -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 <previous-sha>` in each repo; no schema migration involved.

View file

@ -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: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: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: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: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: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 | | 2 | `rest_api:DELETE /workstreams/{workstream_id}/dependencies/{dep_id}` | `DELETE /workplans/{workplan_id}/dependencies/{dep_id}` | REST | T04 |

View file

@ -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}` | | 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}` | | 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) | | 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 | | 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}` | | 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` | | Execution intent | `PATCH /execution/workplans/{workplan_id}/intent` | `PATCH /execution/workstreams/{workstream_id}/intent` | `rest_api:PATCH /execution/workstreams/{workstream_id}/intent` |

View file

@ -165,6 +165,20 @@ class TestWorkplanAliasesAndLegacyMeter:
} }
assert items.get("event_subject:org.statehub.workstream.completed") is None 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): async def test_legacy_delete_workstream_returns_410(self, client):
await _create_domain(client) await _create_domain(client)
topic = await _create_topic(client) topic = await _create_topic(client)

View file

@ -189,8 +189,12 @@ class TestWorkstreams:
async def test_workplan_index_route(self, client): async def test_workplan_index_route(self, client):
r = await client.get("/workstreams/workplan-index") 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 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): async def test_workplan_bindings_sync_populates_index(self, client, tmp_path):
await _create_domain(client) await _create_domain(client)

View file

@ -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 dashboard loaders, cleanup_stale_tasks, validate_repo_adr. Local sweep attributes
`state-hub.fix-consistency` on task-create metering. railiance01 still at `state-hub.fix-consistency` on task-create metering. railiance01 still at
`050cbbc`; `git pull` blocked (`.git/FETCH_HEAD` permission denied) — needs `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: Retire remaining REST `/workstreams` routes per key
```task ```task
id: STATE-WP-0070-T02 id: STATE-WP-0070-T02
status: todo status: progress
priority: high priority: high
state_hub_task_id: "13122195-3b7e-4e22-a5e8-4ebe7e9a093f" 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 Return **410 Gone** (matching DELETE pattern) when legacy-meter shows seven
consecutive zero-usage windows. Update tests and capture evidence per retirement. 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: MCP alias removal (Phase 2)
```task ```task