STATE-WP-0069 T03: normalize MCP progress events to workplan_id
_emit_progress_event strips legacy workstream_id from POST /progress/ payloads. Task and decision side-effect events now set workplan_id explicitly so automatic progress logging stays workplan-first.
This commit is contained in:
parent
7b2348ef90
commit
242dd25253
4 changed files with 47 additions and 7 deletions
|
|
@ -152,12 +152,21 @@ def _response_error(
|
|||
return None
|
||||
|
||||
|
||||
def _normalize_progress_body(body: dict[str, Any]) -> dict[str, Any]:
|
||||
normalized = dict(body)
|
||||
workplan_id = normalized.get("workplan_id") or normalized.get("workstream_id")
|
||||
normalized.pop("workstream_id", None)
|
||||
if workplan_id is not None:
|
||||
normalized["workplan_id"] = workplan_id
|
||||
return normalized
|
||||
|
||||
|
||||
def _emit_progress_event(
|
||||
tool_name: str,
|
||||
write_result: dict[str, Any],
|
||||
body: dict[str, Any],
|
||||
) -> dict[str, Any] | None:
|
||||
progress = _post("/progress", body)
|
||||
progress = _post("/progress", _normalize_progress_body(body))
|
||||
error = _response_error(f"{tool_name}.progress_event", progress, ("id",))
|
||||
if error:
|
||||
return _mcp_error(
|
||||
|
|
@ -923,7 +932,7 @@ def update_task_status(
|
|||
|
||||
progress_error = _emit_progress_event("update_task_status", task, {
|
||||
"task_id": task_id,
|
||||
"workstream_id": task.get("workstream_id"),
|
||||
"workplan_id": task.get("workplan_id") or task.get("workstream_id"),
|
||||
"event_type": "task_status_changed",
|
||||
"summary": f"Task status → {status}: {task['title']}",
|
||||
"author": "custodian",
|
||||
|
|
@ -983,7 +992,7 @@ def flag_for_human(task_id: str, note: str) -> str:
|
|||
|
||||
progress_error = _emit_progress_event("flag_for_human", task, {
|
||||
"task_id": task_id,
|
||||
"workstream_id": task.get("workstream_id"),
|
||||
"workplan_id": task.get("workplan_id") or task.get("workstream_id"),
|
||||
"event_type": "task_flagged_human",
|
||||
"summary": f"Task flagged for human intervention: {task['title']}",
|
||||
"author": "custodian",
|
||||
|
|
@ -1012,7 +1021,7 @@ def clear_human_flag(task_id: str) -> str:
|
|||
|
||||
progress_error = _emit_progress_event("clear_human_flag", task, {
|
||||
"task_id": task_id,
|
||||
"workstream_id": task.get("workstream_id"),
|
||||
"workplan_id": task.get("workplan_id") or task.get("workstream_id"),
|
||||
"event_type": "task_flag_cleared",
|
||||
"summary": f"Human-intervention flag cleared: {task['title']}",
|
||||
"author": "custodian",
|
||||
|
|
@ -1128,7 +1137,7 @@ def resolve_decision(
|
|||
|
||||
progress_error = _emit_progress_event("resolve_decision", decision, {
|
||||
"topic_id": decision.get("topic_id"),
|
||||
"workstream_id": decision.get("workstream_id"),
|
||||
"workplan_id": decision.get("workplan_id") or decision.get("workstream_id"),
|
||||
"decision_id": decision_id,
|
||||
"event_type": "decision_resolved",
|
||||
"summary": f"Decision resolved by {decided_by}: {decision['title']}",
|
||||
|
|
|
|||
|
|
@ -5,6 +5,28 @@ import json
|
|||
import mcp_server.server as server
|
||||
|
||||
|
||||
def test_normalize_progress_body_prefers_workplan_id_only() -> None:
|
||||
body = server._normalize_progress_body({
|
||||
"workplan_id": "wp-1",
|
||||
"workstream_id": "wp-1",
|
||||
"event_type": "note",
|
||||
"summary": "test",
|
||||
})
|
||||
assert body == {
|
||||
"workplan_id": "wp-1",
|
||||
"event_type": "note",
|
||||
"summary": "test",
|
||||
}
|
||||
|
||||
legacy_only = server._normalize_progress_body({
|
||||
"workstream_id": "wp-legacy",
|
||||
"event_type": "note",
|
||||
"summary": "legacy",
|
||||
})
|
||||
assert legacy_only["workplan_id"] == "wp-legacy"
|
||||
assert "workstream_id" not in legacy_only
|
||||
|
||||
|
||||
def test_legacy_mcp_deprecation_payload_names_replacement() -> None:
|
||||
payload = server._legacy_mcp_deprecation(tool="create_workstream")
|
||||
assert payload["replacement"] == "create_workplan"
|
||||
|
|
|
|||
|
|
@ -128,6 +128,8 @@ class TestMCPWriteTools:
|
|||
}
|
||||
assert [path for path, _ in calls] == ["/tasks", "/progress"]
|
||||
assert calls[1][1]["task_id"] == "task-1"
|
||||
assert calls[1][1]["workplan_id"] == "ws-1"
|
||||
assert "workstream_id" not in calls[1][1]
|
||||
assert calls[1][1]["event_type"] == "task_created"
|
||||
|
||||
async def test_update_task_status_api_error_is_clear_and_skips_progress(self, monkeypatch):
|
||||
|
|
@ -183,6 +185,8 @@ class TestMCPWriteTools:
|
|||
}
|
||||
assert [path for path, _ in post_calls] == ["/progress"]
|
||||
assert post_calls[0][1]["task_id"] == "task-1"
|
||||
assert post_calls[0][1]["workplan_id"] == "ws-1"
|
||||
assert "workstream_id" not in post_calls[0][1]
|
||||
assert post_calls[0][1]["event_type"] == "task_status_changed"
|
||||
|
||||
async def test_add_progress_event_accepts_json_string_detail(self, monkeypatch):
|
||||
|
|
@ -372,6 +376,7 @@ class TestMCPWriteTools:
|
|||
assert body["id"] == "decision-1"
|
||||
assert calls[0][1]["workplan_id"] == "wp-1"
|
||||
assert calls[1][1]["workplan_id"] == "wp-1"
|
||||
assert "workstream_id" not in calls[1][1]
|
||||
|
||||
async def test_list_blocked_tasks_accepts_workplan_id_alias(self, monkeypatch):
|
||||
captured: dict[str, Any] = {}
|
||||
|
|
@ -386,7 +391,7 @@ class TestMCPWriteTools:
|
|||
await _call_tool("list_blocked_tasks", {"workplan_id": "wp-1"})
|
||||
|
||||
assert captured["params"]["workplan_id"] == "wp-1"
|
||||
assert captured["params"]["workstream_id"] == "wp-1"
|
||||
assert "workstream_id" not in captured["params"]
|
||||
|
||||
async def test_record_token_event_accepts_workplan_id_alias(self, monkeypatch):
|
||||
calls: list[tuple[str, dict[str, Any]]] = []
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ topic_slug: custodian
|
|||
planning_priority: medium
|
||||
planning_order: 69
|
||||
created: "2026-07-08"
|
||||
updated: "2026-07-11"
|
||||
updated: "2026-07-12"
|
||||
state_hub_workstream_id: "923bb94a-d16c-422c-b81e-16328bd7b60c"
|
||||
---
|
||||
|
||||
|
|
@ -152,6 +152,10 @@ 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.
|
||||
|
||||
Progress 2026-07-11 (T03): `_emit_progress_event` normalizes payloads to
|
||||
`workplan_id` only; task/decision/workplan automatic progress events no longer
|
||||
dual-send `workstream_id` on POST `/progress/`.
|
||||
|
||||
## Task: REST `/workstreams` compat router retirement
|
||||
|
||||
```task
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue