feat(legacy-meter): meter MCP aliases and fix brief workstream_id label (STATE-WP-0070 T03/T04)

T03: MCP alias retirement gate was unstartable — deprecation notices existed
but no mcp:* interfaces were ever registered or metered. Add
_meter_legacy_mcp() posting /legacy-meter/usage (kind mcp_tool, component
state-hub.mcp) from create_workstream, list_workstreams, update_workstream,
update_workstream_status, and state://workstreams/{topic_slug}; register all
five phase-3 backlog keys so zero-window streaks accrue from today.

T04: trace residual POST /progress/ workstream_id (3 calls/8h, unknown) to
.custodian-brief.md labelling workplans "workstream_id:"; brief generator now
prints "workplan_id:". Briefs refresh on each repo's next fix-consistency run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-07-10 15:21:49 +02:00
parent 2943251c03
commit b2640a1591
4 changed files with 58 additions and 1 deletions

View file

@ -219,6 +219,38 @@ def _legacy_mcp_deprecation(*, tool: str | None = None, resource: str | None = N
raise ValueError("tool or resource required")
def _meter_legacy_mcp(*, tool: str | None = None, resource: str | None = None) -> None:
"""Record legacy MCP alias usage on the legacy meter (best-effort).
Keys match docs/workplan-terminology-legacy-retirement-backlog.md phase 3
(`mcp:<tool_name>` / resource URI). Failures never surface to the caller
the meter gates retirement, it must not break the aliased call itself.
"""
if tool:
key = f"mcp:{tool}"
replacement = _LEGACY_MCP_TOOL_REPLACEMENTS[tool]
elif resource:
key = resource
replacement = _LEGACY_MCP_RESOURCE_REPLACEMENTS[resource]
else:
raise ValueError("tool or resource required")
try:
with _client() as c:
c.post(
"/legacy-meter/usage",
json={
"interface_key": key,
"interface_kind": "mcp_tool",
"replacement_ref": replacement,
"owner_component": "state-hub.mcp",
"replacement_verified": True,
"component_key": "state-hub.mcp",
},
)
except Exception:
pass
def _attach_legacy_deprecation(tool_name: str, result_json: str) -> str:
if tool_name not in _LEGACY_MCP_TOOL_REPLACEMENTS:
return result_json
@ -272,6 +304,7 @@ def resource_workplans(topic_slug: str) -> str:
@mcp.resource("state://workstreams/{topic_slug}")
def resource_workstreams(topic_slug: str) -> str:
"""Legacy resource alias — prefer state://workplans/{topic_slug}."""
_meter_legacy_mcp(resource="state://workstreams/{topic_slug}")
topics = _get("/topics", {"status": "active"})
match = next((t for t in topics if t["slug"] == topic_slug), None)
if not match:
@ -802,6 +835,7 @@ def create_workstream(
planning_order: int | None = None,
) -> str:
"""DEPRECATED legacy alias — use create_workplan(repo_id=...) instead."""
_meter_legacy_mcp(tool="create_workstream")
if not repo_id:
return _json_result(_mcp_error("create_workstream", "repo_id is required"))
return _create_workplan_impl(
@ -1219,6 +1253,7 @@ def list_workstreams(
slug: str | None = None,
) -> str:
"""DEPRECATED legacy alias — use list_workplans instead."""
_meter_legacy_mcp(tool="list_workstreams")
return _attach_legacy_deprecation(
"list_workstreams",
list_workplans(
@ -1249,6 +1284,7 @@ def update_workstream_status(
workstream_id: str | None = None,
) -> str:
"""DEPRECATED legacy alias — use update_workplan_status instead."""
_meter_legacy_mcp(tool="update_workstream_status")
parent_id = workplan_id or workstream_id
if not parent_id:
return _json_result(_mcp_error("update_workstream_status", "workplan_id is required"))
@ -1289,6 +1325,7 @@ def update_workstream(
status: str | None = None,
) -> str:
"""DEPRECATED legacy alias — use update_workplan instead."""
_meter_legacy_mcp(tool="update_workstream")
parent_id = workplan_id or workstream_id
if not parent_id:
return _json_result(_mcp_error("update_workstream", "workplan_id is required"))