STATE-WP-0069: meter /progress workstream_id and fix MCP task queries
Wire hub-core progress legacy-meter hook for GET /progress/?workstream_id. MCP list_tasks, list_blocked_tasks, and list_human_interventions now call REST with workplan_id only so internal clients do not inflate legacy usage.
This commit is contained in:
parent
0292be0787
commit
4934655a67
5 changed files with 52 additions and 7 deletions
|
|
@ -1,13 +1,34 @@
|
||||||
|
from fastapi import Request, Response
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from api.database import get_session
|
from api.database import get_session
|
||||||
from api.models.progress_event import ProgressEvent
|
from api.models.progress_event import ProgressEvent
|
||||||
from api.schemas.progress_event import ProgressEventCreate, ProgressEventRead
|
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
|
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=<workplan_id>",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
router = create_progress_router(
|
router = create_progress_router(
|
||||||
get_session,
|
get_session,
|
||||||
progress_model=ProgressEvent,
|
progress_model=ProgressEvent,
|
||||||
progress_create_schema=ProgressEventCreate,
|
progress_create_schema=ProgressEventCreate,
|
||||||
progress_read_schema=ProgressEventRead,
|
progress_read_schema=ProgressEventRead,
|
||||||
|
meter_legacy_workstream_id=_meter_progress_workstream_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
__all__ = ["router"]
|
__all__ = ["router"]
|
||||||
|
|
@ -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 /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 /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 /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
|
Retire param aliases in T04 after zero-usage windows; callers surface via
|
||||||
`X-StateHub-Component` in weekly review.
|
`X-StateHub-Component` in weekly review.
|
||||||
|
|
|
||||||
|
|
@ -517,7 +517,7 @@ def list_tasks(
|
||||||
if not parent_id:
|
if not parent_id:
|
||||||
return _json_result(_mcp_error("list_tasks", "workplan_id is required"))
|
return _json_result(_mcp_error("list_tasks", "workplan_id is required"))
|
||||||
return json.dumps(
|
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,
|
indent=2,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -535,7 +535,7 @@ def list_blocked_tasks(
|
||||||
"""
|
"""
|
||||||
parent_id = workplan_id or workstream_id
|
parent_id = workplan_id or workstream_id
|
||||||
return json.dumps(
|
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,
|
indent=2,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1040,7 +1040,7 @@ def list_human_interventions(
|
||||||
return json.dumps(
|
return json.dumps(
|
||||||
_get(
|
_get(
|
||||||
"/tasks",
|
"/tasks",
|
||||||
{"needs_human": "true", "workplan_id": parent_id, "workstream_id": parent_id},
|
{"needs_human": "true", "workplan_id": parent_id},
|
||||||
),
|
),
|
||||||
indent=2,
|
indent=2,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -171,6 +171,24 @@ class TestWorkplanAliasesAndLegacyMeter:
|
||||||
assert item["window"]["calls"] == 1
|
assert item["window"]["calls"] == 1
|
||||||
assert item["window"]["components"] == {"old-task-client": 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=<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):
|
async def test_workplan_id_query_param_on_tasks_is_not_metered(self, client):
|
||||||
await _create_domain(client)
|
await _create_domain(client)
|
||||||
topic = await _create_topic(client)
|
topic = await _create_topic(client)
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ topic_slug: custodian
|
||||||
planning_priority: medium
|
planning_priority: medium
|
||||||
planning_order: 69
|
planning_order: 69
|
||||||
created: "2026-07-08"
|
created: "2026-07-08"
|
||||||
updated: "2026-07-10"
|
updated: "2026-07-11"
|
||||||
state_hub_workstream_id: "923bb94a-d16c-422c-b81e-16328bd7b60c"
|
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`
|
schedule (`30 8 * * 1` Europe/Berlin) posts `legacy_meter_weekly_review`
|
||||||
progress events linked to this workplan.
|
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: REST `/workstreams` compat router retirement
|
||||||
|
|
||||||
```task
|
```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
|
Progress 2026-07-10 (T04 prep): `api/services/legacy_compat.py` meters
|
||||||
`workstream_id` query-param usage on `/tasks/`, `/tasks/counts`, `/decisions/`,
|
`workstream_id` query-param usage on `/tasks/`, `/tasks/counts`, `/decisions/`,
|
||||||
`/token-events/`, and `/execution/launch-requests` with Deprecation headers and
|
`/token-events/`, `/execution/launch-requests`, and `/progress/` with Deprecation
|
||||||
legacy-meter keys. Route-level `/workstreams` removal remains gated on zero usage.
|
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
|
## Task: Legacy completion event — stop dual-publish
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue