STATE-WP-0070 T03: remove legacy workstream MCP aliases

Meter-gated removal (7 consecutive zero-usage windows reached). Deletes:
- MCP tools: create_workstream, list_workstreams, update_workstream,
  update_workstream_status
- MCP resource: state://workstreams/{topic_slug}
- Dead helpers: _LEGACY_MCP_* maps, _legacy_mcp_deprecation,
  _meter_legacy_mcp, _attach_legacy_deprecation, _update_workplan_legacy_impl

TOOLS.md keeps a retired→preferred migration map (per backlog). Removed the
now-dead STATEHUB_MCP_LEGACY_METER guard in conftest. Retargeted the
create_workplan error-skip test to the preferred tool; dropped alias-only tests.

Staged on branch state-wp-0070-legacy-retirement — do not merge until the 7th
documented zero-usage window is captured.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-07-13 09:29:12 +02:00
parent 1acad4c02c
commit ef62cb3872
5 changed files with 28 additions and 328 deletions

View file

@ -23,10 +23,6 @@ from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_asyn
# Make api/ importable when running pytest from state-hub/
sys.path.insert(0, str(Path(__file__).parent.parent))
# Tests exercising legacy MCP aliases must not record real usage on the live
# legacy meter — that would reset zero-window retirement streaks every run.
os.environ.setdefault("STATEHUB_MCP_LEGACY_METER", "off")
_ASYNC_URL = os.getenv(
"TEST_DATABASE_URL",
"postgresql+asyncpg://custodian:changeme@127.0.0.1:5432/custodian_test",

View file

@ -27,22 +27,6 @@ def test_normalize_progress_body_prefers_workplan_id_only() -> None:
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"
assert payload["deprecated"] == "true"
def test_attach_legacy_deprecation_preserves_success_payload() -> None:
raw = server._attach_legacy_deprecation(
"update_workstream_status",
json.dumps({"id": "wp-1", "status": "active"}),
)
body = json.loads(raw)
assert body["id"] == "wp-1"
assert body["_deprecation"]["tool"] == "update_workstream_status"
def _fake_get(path: str, params: dict | None = None):
if path == "/topics":
return [{"id": "topic-1", "domain_slug": "infotech", "title": "Infotech"}]
@ -70,20 +54,6 @@ def _fake_get(path: str, params: dict | None = None):
return []
def test_legacy_workstreams_resource_includes_deprecation(monkeypatch) -> None:
def fake_get(path: str, params: dict | None = None):
if path == "/topics":
return [{"id": "topic-1", "slug": "infotech", "domain_slug": "infotech"}]
if path == "/workstreams":
return [{"id": "wp-1", "title": "Demo", "slug": "demo-wp"}]
return []
monkeypatch.setattr(server, "_get", fake_get)
payload = json.loads(server.resource_workstreams("infotech"))
assert payload["_deprecation"]["replacement"] == "state://workplans/{topic_slug}"
assert payload["workplans"] == payload["workstreams"]
def test_get_domain_summary_goal_guidance_is_workplan_first(monkeypatch) -> None:
monkeypatch.setattr(server, "_get", lambda path, params=None: _fake_get(path, params))
payload = json.loads(server.get_domain_summary("infotech"))

View file

@ -56,43 +56,6 @@ class TestMCPWriteTools:
assert calls[1][1]["workplan_id"] == "wp-1"
assert calls[1][1]["event_type"] == "workplan_created"
async def test_create_workstream_legacy_alias_uses_workplans_endpoint(self, monkeypatch):
calls: list[tuple[str, dict[str, Any]]] = []
def fake_post(path: str, body: dict[str, Any]) -> dict[str, Any]:
calls.append((path, body))
if path == "/workplans":
return {"id": "wp-1", "repo_id": body["repo_id"], "title": body["title"], "slug": body["slug"], "status": "active"}
if path == "/progress":
return {"id": "event-1", **body}
raise AssertionError(f"unexpected POST {path}")
monkeypatch.setattr(server, "_post", fake_post)
body = await _call_tool(
"create_workstream",
{"repo_id": "repo-1", "title": "Legacy alias"},
)
assert body["id"] == "wp-1"
assert body["_deprecation"]["replacement"] == "create_workplan"
assert [path for path, _ in calls] == ["/workplans", "/progress"]
async def test_list_workstreams_legacy_alias_wraps_deprecation(self, monkeypatch):
monkeypatch.setattr(
server,
"_get",
lambda path, params=None: [{"id": "wp-1", "title": "Example"}]
if path == "/workplans"
else [],
)
body = await _call_tool("list_workstreams", {})
assert body["_deprecation"]["replacement"] == "list_workplans"
assert body["workplans"] == [{"id": "wp-1", "title": "Example"}]
assert body["workstreams"] == body["workplans"]
async def test_create_task_returns_rest_shape_and_emits_progress(self, monkeypatch):
calls: list[tuple[str, dict[str, Any]]] = []
@ -306,7 +269,7 @@ class TestMCPWriteTools:
assert calls[1][1]["decision_id"] == "decision-1"
assert calls[1][1]["event_type"] == "decision_recorded"
async def test_create_workstream_api_error_skips_progress(self, monkeypatch):
async def test_create_workplan_api_error_skips_progress(self, monkeypatch):
calls: list[tuple[str, dict[str, Any]]] = []
def fake_post(path: str, body: dict[str, Any]) -> dict[str, Any]:
@ -316,11 +279,11 @@ class TestMCPWriteTools:
monkeypatch.setattr(server, "_post", fake_post)
body = await _call_tool(
"create_workstream",
"create_workplan",
{"repo_id": "bad-repo", "title": "No progress on failure"},
)
assert body["tool"] == "create_workstream"
assert body["tool"] == "create_workplan"
assert body["error"] == "API 422: invalid repo"
assert [path for path, _ in calls] == ["/workplans"]