diff --git a/api/routers/progress.py b/api/routers/progress.py index e148ce4..66956df 100644 --- a/api/routers/progress.py +++ b/api/routers/progress.py @@ -1,13 +1,34 @@ +from fastapi import Request, Response +from sqlalchemy.ext.asyncio import AsyncSession + from api.database import get_session from api.models.progress_event import ProgressEvent from api.schemas.progress_event import ProgressEventCreate, ProgressEventRead +from api.services.legacy_compat import meter_legacy_query_param from hub_core.routers.progress import create_progress_router + +async def _meter_progress_workstream_id( + session: AsyncSession, + request: Request, + response: Response, +) -> None: + await meter_legacy_query_param( + session=session, + request=request, + response=response, + method="GET", + route="/progress/", + replacement_ref="/progress/?workplan_id=", + ) + + router = create_progress_router( get_session, progress_model=ProgressEvent, progress_create_schema=ProgressEventCreate, progress_read_schema=ProgressEventRead, + meter_legacy_workstream_id=_meter_progress_workstream_id, ) -__all__ = ["router"] +__all__ = ["router"] \ 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 492a0b5..3c566cc 100644 --- a/docs/workplan-terminology-legacy-retirement-backlog.md +++ b/docs/workplan-terminology-legacy-retirement-backlog.md @@ -74,6 +74,7 @@ These accept `workstream_id` alongside `workplan_id` on preferred routes: | `rest_api:GET /decisions/?workstream_id` | `GET /decisions/` | `GET /decisions/?workplan_id=` | | `rest_api:GET /token-events/?workstream_id` | `GET /token-events/` | `GET /token-events/?workplan_id=` | | `rest_api:GET /execution/launch-requests?workstream_id` | `GET /execution/launch-requests` | `GET /execution/launch-requests?workplan_id=` | +| `rest_api:GET /progress/?workstream_id` | `GET /progress/` | `GET /progress/?workplan_id=` | Retire param aliases in T04 after zero-usage windows; callers surface via `X-StateHub-Component` in weekly review. diff --git a/mcp_server/server.py b/mcp_server/server.py index ab195a0..923f289 100644 --- a/mcp_server/server.py +++ b/mcp_server/server.py @@ -517,7 +517,7 @@ def list_tasks( if not parent_id: return _json_result(_mcp_error("list_tasks", "workplan_id is required")) return json.dumps( - _get("/tasks", {"workplan_id": parent_id, "workstream_id": parent_id, "status": status}), + _get("/tasks", {"workplan_id": parent_id, "status": status}), indent=2, ) @@ -535,7 +535,7 @@ def list_blocked_tasks( """ parent_id = workplan_id or workstream_id return json.dumps( - _get("/tasks", {"status": "wait", "workplan_id": parent_id, "workstream_id": parent_id}), + _get("/tasks", {"status": "wait", "workplan_id": parent_id}), indent=2, ) @@ -1040,7 +1040,7 @@ def list_human_interventions( return json.dumps( _get( "/tasks", - {"needs_human": "true", "workplan_id": parent_id, "workstream_id": parent_id}, + {"needs_human": "true", "workplan_id": parent_id}, ), indent=2, ) diff --git a/tests/test_legacy_meter.py b/tests/test_legacy_meter.py index f545a40..7e74dac 100644 --- a/tests/test_legacy_meter.py +++ b/tests/test_legacy_meter.py @@ -171,6 +171,24 @@ class TestWorkplanAliasesAndLegacyMeter: assert item["window"]["calls"] == 1 assert item["window"]["components"] == {"old-task-client": 1} + async def test_legacy_workstream_id_query_param_on_progress_is_metered(self, client): + await _create_domain(client) + topic = await _create_topic(client) + wp = await _create_workplan(client, topic["id"]) + + r = await client.get( + f"/progress/?workstream_id={wp['id']}", + headers={"X-StateHub-Component": "old-progress-client"}, + ) + assert r.status_code == 200 + assert r.headers["Deprecation"] == "true" + assert r.headers["X-StateHub-Replacement"] == "/progress/?workplan_id=" + + summary = (await client.get("/legacy-meter/summary")).json() + item = _summary_by_key(summary)["rest_api:GET /progress/?workstream_id"] + assert item["window"]["calls"] == 1 + assert item["window"]["components"] == {"old-progress-client": 1} + async def test_workplan_id_query_param_on_tasks_is_not_metered(self, client): await _create_domain(client) topic = await _create_topic(client) diff --git a/workplans/STATE-WP-0069-workplan-terminology-legacy-retirement.md b/workplans/STATE-WP-0069-workplan-terminology-legacy-retirement.md index bd5e1d9..6a85770 100644 --- a/workplans/STATE-WP-0069-workplan-terminology-legacy-retirement.md +++ b/workplans/STATE-WP-0069-workplan-terminology-legacy-retirement.md @@ -10,7 +10,7 @@ topic_slug: custodian planning_priority: medium planning_order: 69 created: "2026-07-08" -updated: "2026-07-10" +updated: "2026-07-11" state_hub_workstream_id: "923bb94a-d16c-422c-b81e-16328bd7b60c" --- @@ -148,6 +148,10 @@ Activity-core monitoring (STATE-WP-0054 T06): `weekly-legacy-meter-review` schedule (`30 8 * * 1` Europe/Berlin) posts `legacy_meter_weekly_review` progress events linked to this workplan. +Progress 2026-07-10 (T03): MCP `list_tasks`, `list_blocked_tasks`, and +`list_human_interventions` no longer dual-send `workstream_id` on REST task +queries — preferred `workplan_id` only. + ## Task: REST `/workstreams` compat router retirement ```task @@ -173,8 +177,9 @@ Done when OpenAPI documents `/workplans` as the public CRUD surface and Progress 2026-07-10 (T04 prep): `api/services/legacy_compat.py` meters `workstream_id` query-param usage on `/tasks/`, `/tasks/counts`, `/decisions/`, -`/token-events/`, and `/execution/launch-requests` with Deprecation headers and -legacy-meter keys. Route-level `/workstreams` removal remains gated on zero usage. +`/token-events/`, `/execution/launch-requests`, and `/progress/` with Deprecation +headers and legacy-meter keys. MCP task list tools now call REST with `workplan_id` +only. Route-level `/workstreams` removal remains gated on zero usage. ## Task: Legacy completion event — stop dual-publish