From b59cff508a37df28e04232f45647186d3ada85bc Mon Sep 17 00:00:00 2001 From: tegwick Date: Mon, 13 Jul 2026 10:53:31 +0200 Subject: [PATCH 001/321] chore(wp-0070): daily legacy-meter evidence captures (07-12, 07-13) 8h zero-usage window snapshots for the STATE-WP-0070 retirement gate. Co-Authored-By: Claude Opus 4.8 --- .../legacy-meter-weekly-review-20260712.json | 27 +- .../legacy-meter-weekly-review-20260713.json | 1665 +++++++++++++++++ 2 files changed, 1670 insertions(+), 22 deletions(-) create mode 100644 docs/evidence/legacy-meter-weekly-review-20260713.json diff --git a/docs/evidence/legacy-meter-weekly-review-20260712.json b/docs/evidence/legacy-meter-weekly-review-20260712.json index 49b8eb7..8a49779 100644 --- a/docs/evidence/legacy-meter-weekly-review-20260712.json +++ b/docs/evidence/legacy-meter-weekly-review-20260712.json @@ -1,29 +1,12 @@ { - "captured_at": "2026-07-11T23:07:21.434523+00:00", + "captured_at": "2026-07-12T14:00:09.343613+00:00", "api_base": "http://127.0.0.1:8000", "workplan": "STATE-WP-0070", - "retired_interfaces": [ - { - "id": "8051fb40-66c9-4dc4-a413-13e8573e04a3", - "interface_key": "event_subject:org.statehub.workstream.completed", - "interface_kind": "event_subject", - "legacy_since": "2026-06-04T06:09:42.198193Z", - "replacement_ref": "org.statehub.workplan.completed", - "owner_component": "state-hub.events", - "status": "retired", - "replacement_verified": true, - "manual_hold": false, - "hold_reason": null, - "notes": "Retired by STATE-WP-0069 closeout", - "retired_at": "2026-07-08T21:38:52.773601Z", - "created_at": "2026-06-04T06:09:42.198193Z", - "updated_at": "2026-07-08T21:38:52.761243Z" - } - ], + "retired_interfaces": [], "weekly_review": { - "generated_at": "2026-07-11T23:07:21.408590Z", - "window_start": "2026-07-11T15:07:21.328695Z", - "window_end": "2026-07-11T23:07:21.328695Z", + "generated_at": "2026-07-12T14:00:09.336673Z", + "window_start": "2026-07-12T06:00:08.911717Z", + "window_end": "2026-07-12T14:00:08.911717Z", "cadence": "weekly", "activity_core_handoff": { "activity_id": "statehub-legacy-interface-review", diff --git a/docs/evidence/legacy-meter-weekly-review-20260713.json b/docs/evidence/legacy-meter-weekly-review-20260713.json new file mode 100644 index 0000000..126308a --- /dev/null +++ b/docs/evidence/legacy-meter-weekly-review-20260713.json @@ -0,0 +1,1665 @@ +{ + "captured_at": "2026-07-12T22:00:01.424861+00:00", + "api_base": "http://127.0.0.1:8000", + "workplan": "STATE-WP-0070", + "retired_interfaces": [], + "weekly_review": { + "generated_at": "2026-07-12T22:00:01.418415Z", + "window_start": "2026-07-12T14:00:01.350670Z", + "window_end": "2026-07-12T22:00:01.350670Z", + "cadence": "weekly", + "activity_core_handoff": { + "activity_id": "statehub-legacy-interface-review", + "cadence": "weekly", + "source_endpoint": "/legacy-meter/weekly-review", + "state_owner": "state-hub", + "scheduler_owner": "activity-core" + }, + "interfaces": [ + { + "interface": { + "id": "8051fb40-66c9-4dc4-a413-13e8573e04a3", + "interface_key": "event_subject:org.statehub.workstream.completed", + "interface_kind": "event_subject", + "legacy_since": "2026-06-04T06:09:42.198193Z", + "replacement_ref": "org.statehub.workplan.completed", + "owner_component": "state-hub.events", + "status": "retired", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Retired by STATE-WP-0069 closeout", + "retired_at": "2026-07-08T21:38:52.773601Z", + "created_at": "2026-06-04T06:09:42.198193Z", + "updated_at": "2026-07-08T21:38:52.761243Z" + }, + "all_time": { + "calls": 255, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 255 + }, + "users": { + "unknown": 255 + }, + "components": { + "state-hub.events": 255 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T20:49:12.839383Z", + "retirement_candidate": false, + "retirement_reason": "already retired" + }, + { + "interface": { + "id": "63ed90e3-42b4-431a-94db-e1254a52f9e2", + "interface_key": "mcp:create_workstream", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.205900Z", + "replacement_ref": "create_workplan", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.205900Z", + "updated_at": "2026-07-10T13:19:44.205900Z" + }, + "all_time": { + "calls": 2, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 2 + }, + "users": { + "unknown": 2 + }, + "components": { + "state-hub.mcp": 2 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T13:20:43.378546Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "b6a5fe44-fb85-4ef7-a273-78d09da777af", + "interface_key": "mcp:list_workstreams", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:26.075491Z", + "replacement_ref": "list_workplans", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-10T13:19:26.075491Z", + "updated_at": "2026-07-10T13:19:26.075491Z" + }, + "all_time": { + "calls": 2, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 2 + }, + "users": { + "unknown": 2 + }, + "components": { + "state-hub.mcp": 2 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T13:20:42.374940Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "53c3372b-307d-4d6a-8ea4-bc36cacdcaf2", + "interface_key": "mcp:update_workstream", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.255216Z", + "replacement_ref": "update_workplan", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.255216Z", + "updated_at": "2026-07-10T13:19:44.255216Z" + }, + "all_time": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": null, + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "bed4d08a-3471-48f5-9f0e-f9d550383a26", + "interface_key": "mcp:update_workstream_status", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.303612Z", + "replacement_ref": "update_workplan_status", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.303612Z", + "updated_at": "2026-07-10T13:19:44.303612Z" + }, + "all_time": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": null, + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "46900dc2-0893-405b-ba59-80f31fb6ebb1", + "interface_key": "state://workstreams/{topic_slug}", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.344493Z", + "replacement_ref": "state://workplans/{topic_slug}", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.344493Z", + "updated_at": "2026-07-10T13:19:44.344493Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "state-hub.mcp": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T13:20:34.491811Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "cd9342fd-27e7-4a46-a1be-d41499e106a3", + "interface_key": "rest_api:DELETE /workstreams/{workstream_id}", + "interface_kind": "rest_api", + "legacy_since": "2026-06-06T17:28:13.052813Z", + "replacement_ref": "/workplans/{workplan_id}", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-06T17:28:13.052813Z", + "updated_at": "2026-06-06T17:28:13.052813Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "unknown": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-06-06T17:28:13.047529Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "a4e0948c-cd4a-461e-8ae2-7fd7b51d04a7", + "interface_key": "rest_api:GET /decisions/?workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-09T23:07:53.093223Z", + "replacement_ref": "/decisions/?workplan_id=", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-09T23:07:53.093223Z", + "updated_at": "2026-07-09T23:07:53.093223Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "unknown": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-09T23:07:53.083299Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "be3cdcf8-250b-4bf0-801f-02ffd5f57e00", + "interface_key": "rest_api:GET /tasks/?workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-08T19:32:21.628513Z", + "replacement_ref": "/tasks/?workplan_id=", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-08T19:32:21.628513Z", + "updated_at": "2026-07-08T19:32:21.628513Z" + }, + "all_time": { + "calls": 577, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 577 + }, + "users": { + "unknown": 577 + }, + "components": { + "unknown": 577 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T21:38:17.035426Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "3e4a3d0b-08fa-45c3-91e2-4f514a914b97", + "interface_key": "rest_api:GET /workstreams/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T00:26:14.533764Z", + "replacement_ref": "/workplans/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T00:26:14.533764Z", + "updated_at": "2026-06-04T00:26:14.533764Z" + }, + "all_time": { + "calls": 144084, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 144084 + }, + "users": { + "unknown": 144084 + }, + "components": { + "unknown": 144084 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T21:14:39.652776Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "e4c140ec-7479-46f9-b185-4b533f19d639", + "interface_key": "rest_api:GET /workstreams/workplan-index", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T05:20:58.321869Z", + "replacement_ref": "/workplans/index", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T05:20:58.321869Z", + "updated_at": "2026-06-04T05:20:58.321869Z" + }, + "all_time": { + "calls": 30, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 30 + }, + "users": { + "unknown": 30 + }, + "components": { + "unknown": 30 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-03T05:20:34.534439Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "3a00b19e-e7db-4403-98aa-a77c9ab61ecc", + "interface_key": "rest_api:GET /workstreams/{workstream_id}", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T00:25:59.014966Z", + "replacement_ref": "/workplans/{workplan_id}", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T00:25:59.014966Z", + "updated_at": "2026-06-04T00:25:59.014966Z" + }, + "all_time": { + "calls": 511405, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 511405 + }, + "users": { + "unknown": 511405 + }, + "components": { + "unknown": 511405 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T21:14:20.781103Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "69e1a255-a8b9-4101-8a68-b1bed23abfda", + "interface_key": "rest_api:GET /workstreams/{workstream_id}/dependencies/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T00:25:59.291135Z", + "replacement_ref": "/workplans/{workplan_id}/dependencies/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T00:25:59.291135Z", + "updated_at": "2026-06-04T00:25:59.291135Z" + }, + "all_time": { + "calls": 255865, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 255865 + }, + "users": { + "unknown": 255865 + }, + "components": { + "unknown": 255865 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T21:14:12.747005Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "b1aae931-b51f-4a85-a723-865dd65f6132", + "interface_key": "rest_api:PATCH /workstreams/{workstream_id}", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T06:09:41.901035Z", + "replacement_ref": "/workplans/{workplan_id}", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T06:09:41.901035Z", + "updated_at": "2026-06-04T06:09:41.901035Z" + }, + "all_time": { + "calls": 571, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 571 + }, + "users": { + "unknown": 571 + }, + "components": { + "unknown": 571 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T20:49:12.335710Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "4aea9591-970b-4918-ac4a-4da95a0a1a8c", + "interface_key": "rest_api:POST /decisions/ workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-09T22:56:35.473130Z", + "replacement_ref": "POST /decisions/ with workplan_id", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-09T22:56:35.473130Z", + "updated_at": "2026-07-09T22:56:35.473130Z" + }, + "all_time": { + "calls": 3, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 3 + }, + "users": { + "unknown": 3 + }, + "components": { + "unknown": 3 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-09T22:57:05.912456Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "4a70583c-1e41-4ec1-99a9-728856a7d79c", + "interface_key": "rest_api:POST /progress/ workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-08T23:35:09.148333Z", + "replacement_ref": "POST /progress/ with workplan_id", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-08T23:35:09.148333Z", + "updated_at": "2026-07-08T23:35:09.148333Z" + }, + "all_time": { + "calls": 14, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 14 + }, + "users": { + "unknown": 14 + }, + "components": { + "unknown": 14 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T08:34:42.959724Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "6df4e443-7bf1-461a-923b-a6eb52fe7b07", + "interface_key": "rest_api:POST /tasks/ workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-08T22:38:29.405720Z", + "replacement_ref": "POST /tasks/ with workplan_id", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-08T22:38:29.405720Z", + "updated_at": "2026-07-08T22:38:29.405720Z" + }, + "all_time": { + "calls": 5, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 5 + }, + "users": { + "unknown": 5 + }, + "components": { + "state-hub.fix-consistency": 5 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T22:38:29.976918Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "ed6451c9-d2cc-4486-9afe-d3852782cabc", + "interface_key": "rest_api:POST /workstreams/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T07:40:24.168535Z", + "replacement_ref": "/workplans/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T07:40:24.168535Z", + "updated_at": "2026-06-04T07:40:24.168535Z" + }, + "all_time": { + "calls": 824, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 824 + }, + "users": { + "unknown": 824 + }, + "components": { + "unknown": 824 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T20:38:05.903144Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "281fd706-b192-4194-8055-2d8733c115f9", + "interface_key": "rest_api:POST /workstreams/{workstream_id}/dependencies/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T22:57:37.149207Z", + "replacement_ref": "/workplans/{workplan_id}/dependencies/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T22:57:37.149207Z", + "updated_at": "2026-06-04T22:57:37.149207Z" + }, + "all_time": { + "calls": 4971, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 4971 + }, + "users": { + "unknown": 4971 + }, + "components": { + "unknown": 4971 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-01T22:09:24.229011Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + } + ], + "retirement_candidates": [ + { + "interface": { + "id": "63ed90e3-42b4-431a-94db-e1254a52f9e2", + "interface_key": "mcp:create_workstream", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.205900Z", + "replacement_ref": "create_workplan", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.205900Z", + "updated_at": "2026-07-10T13:19:44.205900Z" + }, + "all_time": { + "calls": 2, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 2 + }, + "users": { + "unknown": 2 + }, + "components": { + "state-hub.mcp": 2 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T13:20:43.378546Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "b6a5fe44-fb85-4ef7-a273-78d09da777af", + "interface_key": "mcp:list_workstreams", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:26.075491Z", + "replacement_ref": "list_workplans", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-10T13:19:26.075491Z", + "updated_at": "2026-07-10T13:19:26.075491Z" + }, + "all_time": { + "calls": 2, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 2 + }, + "users": { + "unknown": 2 + }, + "components": { + "state-hub.mcp": 2 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T13:20:42.374940Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "53c3372b-307d-4d6a-8ea4-bc36cacdcaf2", + "interface_key": "mcp:update_workstream", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.255216Z", + "replacement_ref": "update_workplan", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.255216Z", + "updated_at": "2026-07-10T13:19:44.255216Z" + }, + "all_time": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": null, + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "bed4d08a-3471-48f5-9f0e-f9d550383a26", + "interface_key": "mcp:update_workstream_status", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.303612Z", + "replacement_ref": "update_workplan_status", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.303612Z", + "updated_at": "2026-07-10T13:19:44.303612Z" + }, + "all_time": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": null, + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "46900dc2-0893-405b-ba59-80f31fb6ebb1", + "interface_key": "state://workstreams/{topic_slug}", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.344493Z", + "replacement_ref": "state://workplans/{topic_slug}", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.344493Z", + "updated_at": "2026-07-10T13:19:44.344493Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "state-hub.mcp": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T13:20:34.491811Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "cd9342fd-27e7-4a46-a1be-d41499e106a3", + "interface_key": "rest_api:DELETE /workstreams/{workstream_id}", + "interface_kind": "rest_api", + "legacy_since": "2026-06-06T17:28:13.052813Z", + "replacement_ref": "/workplans/{workplan_id}", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-06T17:28:13.052813Z", + "updated_at": "2026-06-06T17:28:13.052813Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "unknown": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-06-06T17:28:13.047529Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "a4e0948c-cd4a-461e-8ae2-7fd7b51d04a7", + "interface_key": "rest_api:GET /decisions/?workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-09T23:07:53.093223Z", + "replacement_ref": "/decisions/?workplan_id=", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-09T23:07:53.093223Z", + "updated_at": "2026-07-09T23:07:53.093223Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "unknown": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-09T23:07:53.083299Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "be3cdcf8-250b-4bf0-801f-02ffd5f57e00", + "interface_key": "rest_api:GET /tasks/?workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-08T19:32:21.628513Z", + "replacement_ref": "/tasks/?workplan_id=", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-08T19:32:21.628513Z", + "updated_at": "2026-07-08T19:32:21.628513Z" + }, + "all_time": { + "calls": 577, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 577 + }, + "users": { + "unknown": 577 + }, + "components": { + "unknown": 577 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T21:38:17.035426Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "3e4a3d0b-08fa-45c3-91e2-4f514a914b97", + "interface_key": "rest_api:GET /workstreams/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T00:26:14.533764Z", + "replacement_ref": "/workplans/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T00:26:14.533764Z", + "updated_at": "2026-06-04T00:26:14.533764Z" + }, + "all_time": { + "calls": 144084, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 144084 + }, + "users": { + "unknown": 144084 + }, + "components": { + "unknown": 144084 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T21:14:39.652776Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "e4c140ec-7479-46f9-b185-4b533f19d639", + "interface_key": "rest_api:GET /workstreams/workplan-index", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T05:20:58.321869Z", + "replacement_ref": "/workplans/index", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T05:20:58.321869Z", + "updated_at": "2026-06-04T05:20:58.321869Z" + }, + "all_time": { + "calls": 30, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 30 + }, + "users": { + "unknown": 30 + }, + "components": { + "unknown": 30 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-03T05:20:34.534439Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "3a00b19e-e7db-4403-98aa-a77c9ab61ecc", + "interface_key": "rest_api:GET /workstreams/{workstream_id}", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T00:25:59.014966Z", + "replacement_ref": "/workplans/{workplan_id}", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T00:25:59.014966Z", + "updated_at": "2026-06-04T00:25:59.014966Z" + }, + "all_time": { + "calls": 511405, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 511405 + }, + "users": { + "unknown": 511405 + }, + "components": { + "unknown": 511405 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T21:14:20.781103Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "69e1a255-a8b9-4101-8a68-b1bed23abfda", + "interface_key": "rest_api:GET /workstreams/{workstream_id}/dependencies/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T00:25:59.291135Z", + "replacement_ref": "/workplans/{workplan_id}/dependencies/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T00:25:59.291135Z", + "updated_at": "2026-06-04T00:25:59.291135Z" + }, + "all_time": { + "calls": 255865, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 255865 + }, + "users": { + "unknown": 255865 + }, + "components": { + "unknown": 255865 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T21:14:12.747005Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "b1aae931-b51f-4a85-a723-865dd65f6132", + "interface_key": "rest_api:PATCH /workstreams/{workstream_id}", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T06:09:41.901035Z", + "replacement_ref": "/workplans/{workplan_id}", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T06:09:41.901035Z", + "updated_at": "2026-06-04T06:09:41.901035Z" + }, + "all_time": { + "calls": 571, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 571 + }, + "users": { + "unknown": 571 + }, + "components": { + "unknown": 571 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T20:49:12.335710Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "4aea9591-970b-4918-ac4a-4da95a0a1a8c", + "interface_key": "rest_api:POST /decisions/ workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-09T22:56:35.473130Z", + "replacement_ref": "POST /decisions/ with workplan_id", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-09T22:56:35.473130Z", + "updated_at": "2026-07-09T22:56:35.473130Z" + }, + "all_time": { + "calls": 3, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 3 + }, + "users": { + "unknown": 3 + }, + "components": { + "unknown": 3 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-09T22:57:05.912456Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "4a70583c-1e41-4ec1-99a9-728856a7d79c", + "interface_key": "rest_api:POST /progress/ workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-08T23:35:09.148333Z", + "replacement_ref": "POST /progress/ with workplan_id", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-08T23:35:09.148333Z", + "updated_at": "2026-07-08T23:35:09.148333Z" + }, + "all_time": { + "calls": 14, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 14 + }, + "users": { + "unknown": 14 + }, + "components": { + "unknown": 14 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T08:34:42.959724Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "6df4e443-7bf1-461a-923b-a6eb52fe7b07", + "interface_key": "rest_api:POST /tasks/ workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-08T22:38:29.405720Z", + "replacement_ref": "POST /tasks/ with workplan_id", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-08T22:38:29.405720Z", + "updated_at": "2026-07-08T22:38:29.405720Z" + }, + "all_time": { + "calls": 5, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 5 + }, + "users": { + "unknown": 5 + }, + "components": { + "state-hub.fix-consistency": 5 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T22:38:29.976918Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "ed6451c9-d2cc-4486-9afe-d3852782cabc", + "interface_key": "rest_api:POST /workstreams/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T07:40:24.168535Z", + "replacement_ref": "/workplans/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T07:40:24.168535Z", + "updated_at": "2026-06-04T07:40:24.168535Z" + }, + "all_time": { + "calls": 824, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 824 + }, + "users": { + "unknown": 824 + }, + "components": { + "unknown": 824 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T20:38:05.903144Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "281fd706-b192-4194-8055-2d8733c115f9", + "interface_key": "rest_api:POST /workstreams/{workstream_id}/dependencies/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T22:57:37.149207Z", + "replacement_ref": "/workplans/{workplan_id}/dependencies/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T22:57:37.149207Z", + "updated_at": "2026-06-04T22:57:37.149207Z" + }, + "all_time": { + "calls": 4971, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 4971 + }, + "users": { + "unknown": 4971 + }, + "components": { + "unknown": 4971 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-01T22:09:24.229011Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + } + ] + }, + "hours": 8 +} From d15a9928b3d32700349fac460b0ecc573d098ae3 Mon Sep 17 00:00:00 2001 From: custodian-sync Date: Mon, 13 Jul 2026 13:22:05 +0000 Subject: [PATCH 002/321] chore(consistency): sync task status from DB [auto] Updated by fix-consistency on 2026-07-13: - update .custodian-brief.md for state-hub --- .custodian-brief.md | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/.custodian-brief.md b/.custodian-brief.md index 7c62258..daac2d0 100644 --- a/.custodian-brief.md +++ b/.custodian-brief.md @@ -2,23 +2,18 @@ # Custodian Brief — state-hub **Domain:** infotech -**Last synced:** 2026-07-10 13:22 UTC +**Last synced:** 2026-07-13 13:22 UTC **State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)* ## Active Workstreams -### Workplan terminology phase-2 legacy retirement -Progress: 2/5 done | workplan_id: `9aa92529-6ee8-4b3f-b573-78b0ef8d9788` - -**Open tasks:** -- ► Task: MCP alias removal (Phase 2) `e2b9015a` -- ► Task: Internal dual-key and param alias cleanup `18af98bf` -- · Task: Closeout and fleet gate `dcc81665` +*(none — repo may need first-session setup)* ## Inbox Hygiene -**Stale unread:** 4 message(s) older than 3 day(s) — triage at session start. -**Missing thread_id:** 4 unread message(s) lack supersession chains. +**Stale unread:** 5 message(s) older than 3 day(s) — triage at session start. +**Missing thread_id:** 5 unread message(s) lack supersession chains. +- ! activity-core: Suggestion: scheduled-wait vs blocked lifecycle + dashboard colors `83a9e875` - ! activity-core: Optional pattern suggestion: standardized deployment evidence handoff `10d37752` - ! inter-hub: Suggestion: document cross-repo coordination in State Hub suggestions handling `6e8510b1` From ac26bf2e664d7587f8a28bf702706c184fbd4019 Mon Sep 17 00:00:00 2001 From: tegwick Date: Tue, 14 Jul 2026 00:21:53 +0200 Subject: [PATCH 003/321] =?UTF-8?q?STATE-WP-0070:=20closeout=20=E2=80=94?= =?UTF-8?q?=20T03/T04/T05=20done,=20status=20finished?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge landed (df19c46), gate cleared, MCP restarted, suite green. Field-alias + flows/workstream.yaml retirement deferred to CUST-WP-0055 phase-3. Co-Authored-By: Claude Opus 4.8 --- ...-workplan-terminology-phase2-retirement.md | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/workplans/STATE-WP-0070-workplan-terminology-phase2-retirement.md b/workplans/STATE-WP-0070-workplan-terminology-phase2-retirement.md index 7f3a76d..2a10c19 100644 --- a/workplans/STATE-WP-0070-workplan-terminology-phase2-retirement.md +++ b/workplans/STATE-WP-0070-workplan-terminology-phase2-retirement.md @@ -4,13 +4,13 @@ type: workplan title: "Workplan terminology phase-2 legacy retirement" domain: infotech repo: state-hub -status: active +status: finished owner: codex topic_slug: custodian planning_priority: medium planning_order: 70 created: "2026-07-09" -updated: "2026-07-13" +updated: "2026-07-14" state_hub_workstream_id: "9aa92529-6ee8-4b3f-b573-78b0ef8d9788" --- @@ -112,7 +112,7 @@ snapshots, not 7 windows). Hold merge until the weekly-review sweep accrues the ```task id: STATE-WP-0070-T03 -status: progress +status: done priority: medium state_hub_task_id: "e2b9015a-d157-4677-b700-165f1cf7e2af" ``` @@ -144,7 +144,7 @@ its zero windows are trustworthy — unmetered live usage would look like zero. ```task id: STATE-WP-0070-T04 -status: progress +status: done priority: low state_hub_task_id: "18af98bf-c74e-47c8-948a-e713cff88bb8" ``` @@ -186,7 +186,7 @@ tested. Merge gate same as T03: 7th documented zero-usage window. ```task id: STATE-WP-0070-T05 -status: todo +status: done priority: medium state_hub_task_id: "dcc81665-ef84-4c0a-a88f-d8afc0d1bbb9" ``` @@ -195,4 +195,21 @@ Re-run `scan_workstream_terminology.py`, grep budget check, full test suite, archive this workplan, notify CUST-WP-0055 fleet gate complete. Done when legacy-meter weekly review shows no active legacy REST keys and MCP -aliases are removed or marked retired. \ No newline at end of file +aliases are removed or marked retired. + +**Closeout 2026-07-14 (merged `df19c46`):** Gate cleared — 10 consecutive 8h +zero-usage windows (07-10 14:00 → 07-13 22:00), all 19 legacy interfaces at 0. +Branch `state-wp-0070-legacy-retirement` merged to `main`; :8001 dev-hub MCP +restarted (removed tools de-registered); full suite green (495 passed). T03 +(MCP aliases + resource) and the open_workstreams mirror (T04) are removed. + +**Deferred to a phase-3 followup (handed to CUST-WP-0055 fleet gate):** two T04 +sub-items still have live fleet callers and were intentionally NOT removed: +1. `workstream_id` query/body field alias on preferred routes (`/tasks`, + `/decisions`, `/progress`, `/execution`, `/token-events`) — external + session-close curls and scripts still send it; scan shows ~4.1k `api:workstream_id` + occurrences fleet-wide. Retire after briefs/scripts refresh and the meter key + holds 7 zero windows. +2. `flows/workstream.yaml` — the `/flows/workstream/{id}` routes are still served + and tested. Retire when no callers remain. +Backlog: `docs/workplan-terminology-legacy-retirement-backlog.md` (phase-3 rows). \ No newline at end of file From 739d76fb58b66cf18fe0b0cd3fd347ab23bca6ba Mon Sep 17 00:00:00 2001 From: custodian-sync Date: Tue, 14 Jul 2026 00:27:13 +0200 Subject: [PATCH 004/321] chore(consistency): sync task status from DB [auto] Updated by fix-consistency on 2026-07-14: - update .custodian-brief.md for state-hub --- .custodian-brief.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.custodian-brief.md b/.custodian-brief.md index daac2d0..a809a49 100644 --- a/.custodian-brief.md +++ b/.custodian-brief.md @@ -2,7 +2,7 @@ # Custodian Brief — state-hub **Domain:** infotech -**Last synced:** 2026-07-13 13:22 UTC +**Last synced:** 2026-07-13 22:27 UTC **State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)* ## Active Workstreams @@ -11,9 +11,8 @@ ## Inbox Hygiene -**Stale unread:** 5 message(s) older than 3 day(s) — triage at session start. -**Missing thread_id:** 5 unread message(s) lack supersession chains. -- ! activity-core: Suggestion: scheduled-wait vs blocked lifecycle + dashboard colors `83a9e875` +**Stale unread:** 4 message(s) older than 3 day(s) — triage at session start. +**Missing thread_id:** 4 unread message(s) lack supersession chains. - ! activity-core: Optional pattern suggestion: standardized deployment evidence handoff `10d37752` - ! inter-hub: Suggestion: document cross-repo coordination in State Hub suggestions handling `6e8510b1` From 92b23f25349246d00e101decc754510dd7433a62 Mon Sep 17 00:00:00 2001 From: tegwick Date: Tue, 14 Jul 2026 00:27:57 +0200 Subject: [PATCH 005/321] chore: archive finished STATE-WP-0070 (workplan terminology phase-2 retirement) Co-Authored-By: Claude Opus 4.8 --- .../legacy-meter-weekly-review-20260714.json | 1665 +++++++++++++++++ ...workplan-terminology-phase2-retirement.md} | 0 2 files changed, 1665 insertions(+) create mode 100644 docs/evidence/legacy-meter-weekly-review-20260714.json rename workplans/{STATE-WP-0070-workplan-terminology-phase2-retirement.md => archived/260714-STATE-WP-0070-workplan-terminology-phase2-retirement.md} (100%) diff --git a/docs/evidence/legacy-meter-weekly-review-20260714.json b/docs/evidence/legacy-meter-weekly-review-20260714.json new file mode 100644 index 0000000..97b1a48 --- /dev/null +++ b/docs/evidence/legacy-meter-weekly-review-20260714.json @@ -0,0 +1,1665 @@ +{ + "captured_at": "2026-07-13T22:00:05.734375+00:00", + "api_base": "http://127.0.0.1:8000", + "workplan": "STATE-WP-0070", + "retired_interfaces": [], + "weekly_review": { + "generated_at": "2026-07-13T22:00:05.724722Z", + "window_start": "2026-07-13T14:00:05.618726Z", + "window_end": "2026-07-13T22:00:05.618726Z", + "cadence": "weekly", + "activity_core_handoff": { + "activity_id": "statehub-legacy-interface-review", + "cadence": "weekly", + "source_endpoint": "/legacy-meter/weekly-review", + "state_owner": "state-hub", + "scheduler_owner": "activity-core" + }, + "interfaces": [ + { + "interface": { + "id": "8051fb40-66c9-4dc4-a413-13e8573e04a3", + "interface_key": "event_subject:org.statehub.workstream.completed", + "interface_kind": "event_subject", + "legacy_since": "2026-06-04T06:09:42.198193Z", + "replacement_ref": "org.statehub.workplan.completed", + "owner_component": "state-hub.events", + "status": "retired", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Retired by STATE-WP-0069 closeout", + "retired_at": "2026-07-08T21:38:52.773601Z", + "created_at": "2026-06-04T06:09:42.198193Z", + "updated_at": "2026-07-08T21:38:52.761243Z" + }, + "all_time": { + "calls": 255, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 255 + }, + "users": { + "unknown": 255 + }, + "components": { + "state-hub.events": 255 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T20:49:12.839383Z", + "retirement_candidate": false, + "retirement_reason": "already retired" + }, + { + "interface": { + "id": "63ed90e3-42b4-431a-94db-e1254a52f9e2", + "interface_key": "mcp:create_workstream", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.205900Z", + "replacement_ref": "create_workplan", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.205900Z", + "updated_at": "2026-07-10T13:19:44.205900Z" + }, + "all_time": { + "calls": 2, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 2 + }, + "users": { + "unknown": 2 + }, + "components": { + "state-hub.mcp": 2 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T13:20:43.378546Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "b6a5fe44-fb85-4ef7-a273-78d09da777af", + "interface_key": "mcp:list_workstreams", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:26.075491Z", + "replacement_ref": "list_workplans", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-10T13:19:26.075491Z", + "updated_at": "2026-07-10T13:19:26.075491Z" + }, + "all_time": { + "calls": 2, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 2 + }, + "users": { + "unknown": 2 + }, + "components": { + "state-hub.mcp": 2 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T13:20:42.374940Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "53c3372b-307d-4d6a-8ea4-bc36cacdcaf2", + "interface_key": "mcp:update_workstream", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.255216Z", + "replacement_ref": "update_workplan", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.255216Z", + "updated_at": "2026-07-10T13:19:44.255216Z" + }, + "all_time": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": null, + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "bed4d08a-3471-48f5-9f0e-f9d550383a26", + "interface_key": "mcp:update_workstream_status", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.303612Z", + "replacement_ref": "update_workplan_status", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.303612Z", + "updated_at": "2026-07-10T13:19:44.303612Z" + }, + "all_time": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": null, + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "46900dc2-0893-405b-ba59-80f31fb6ebb1", + "interface_key": "state://workstreams/{topic_slug}", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.344493Z", + "replacement_ref": "state://workplans/{topic_slug}", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.344493Z", + "updated_at": "2026-07-10T13:19:44.344493Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "state-hub.mcp": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T13:20:34.491811Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "cd9342fd-27e7-4a46-a1be-d41499e106a3", + "interface_key": "rest_api:DELETE /workstreams/{workstream_id}", + "interface_kind": "rest_api", + "legacy_since": "2026-06-06T17:28:13.052813Z", + "replacement_ref": "/workplans/{workplan_id}", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-06T17:28:13.052813Z", + "updated_at": "2026-06-06T17:28:13.052813Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "unknown": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-06-06T17:28:13.047529Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "a4e0948c-cd4a-461e-8ae2-7fd7b51d04a7", + "interface_key": "rest_api:GET /decisions/?workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-09T23:07:53.093223Z", + "replacement_ref": "/decisions/?workplan_id=", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-09T23:07:53.093223Z", + "updated_at": "2026-07-09T23:07:53.093223Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "unknown": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-09T23:07:53.083299Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "be3cdcf8-250b-4bf0-801f-02ffd5f57e00", + "interface_key": "rest_api:GET /tasks/?workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-08T19:32:21.628513Z", + "replacement_ref": "/tasks/?workplan_id=", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-08T19:32:21.628513Z", + "updated_at": "2026-07-08T19:32:21.628513Z" + }, + "all_time": { + "calls": 577, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 577 + }, + "users": { + "unknown": 577 + }, + "components": { + "unknown": 577 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T21:38:17.035426Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "3e4a3d0b-08fa-45c3-91e2-4f514a914b97", + "interface_key": "rest_api:GET /workstreams/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T00:26:14.533764Z", + "replacement_ref": "/workplans/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T00:26:14.533764Z", + "updated_at": "2026-06-04T00:26:14.533764Z" + }, + "all_time": { + "calls": 144084, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 144084 + }, + "users": { + "unknown": 144084 + }, + "components": { + "unknown": 144084 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T21:14:39.652776Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "e4c140ec-7479-46f9-b185-4b533f19d639", + "interface_key": "rest_api:GET /workstreams/workplan-index", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T05:20:58.321869Z", + "replacement_ref": "/workplans/index", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T05:20:58.321869Z", + "updated_at": "2026-06-04T05:20:58.321869Z" + }, + "all_time": { + "calls": 30, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 30 + }, + "users": { + "unknown": 30 + }, + "components": { + "unknown": 30 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-03T05:20:34.534439Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "3a00b19e-e7db-4403-98aa-a77c9ab61ecc", + "interface_key": "rest_api:GET /workstreams/{workstream_id}", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T00:25:59.014966Z", + "replacement_ref": "/workplans/{workplan_id}", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T00:25:59.014966Z", + "updated_at": "2026-06-04T00:25:59.014966Z" + }, + "all_time": { + "calls": 511405, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 511405 + }, + "users": { + "unknown": 511405 + }, + "components": { + "unknown": 511405 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T21:14:20.781103Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "69e1a255-a8b9-4101-8a68-b1bed23abfda", + "interface_key": "rest_api:GET /workstreams/{workstream_id}/dependencies/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T00:25:59.291135Z", + "replacement_ref": "/workplans/{workplan_id}/dependencies/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T00:25:59.291135Z", + "updated_at": "2026-06-04T00:25:59.291135Z" + }, + "all_time": { + "calls": 255865, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 255865 + }, + "users": { + "unknown": 255865 + }, + "components": { + "unknown": 255865 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T21:14:12.747005Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "b1aae931-b51f-4a85-a723-865dd65f6132", + "interface_key": "rest_api:PATCH /workstreams/{workstream_id}", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T06:09:41.901035Z", + "replacement_ref": "/workplans/{workplan_id}", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T06:09:41.901035Z", + "updated_at": "2026-06-04T06:09:41.901035Z" + }, + "all_time": { + "calls": 571, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 571 + }, + "users": { + "unknown": 571 + }, + "components": { + "unknown": 571 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T20:49:12.335710Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "4aea9591-970b-4918-ac4a-4da95a0a1a8c", + "interface_key": "rest_api:POST /decisions/ workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-09T22:56:35.473130Z", + "replacement_ref": "POST /decisions/ with workplan_id", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-09T22:56:35.473130Z", + "updated_at": "2026-07-09T22:56:35.473130Z" + }, + "all_time": { + "calls": 3, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 3 + }, + "users": { + "unknown": 3 + }, + "components": { + "unknown": 3 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-09T22:57:05.912456Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "4a70583c-1e41-4ec1-99a9-728856a7d79c", + "interface_key": "rest_api:POST /progress/ workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-08T23:35:09.148333Z", + "replacement_ref": "POST /progress/ with workplan_id", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-08T23:35:09.148333Z", + "updated_at": "2026-07-08T23:35:09.148333Z" + }, + "all_time": { + "calls": 14, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 14 + }, + "users": { + "unknown": 14 + }, + "components": { + "unknown": 14 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T08:34:42.959724Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "6df4e443-7bf1-461a-923b-a6eb52fe7b07", + "interface_key": "rest_api:POST /tasks/ workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-08T22:38:29.405720Z", + "replacement_ref": "POST /tasks/ with workplan_id", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-08T22:38:29.405720Z", + "updated_at": "2026-07-08T22:38:29.405720Z" + }, + "all_time": { + "calls": 5, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 5 + }, + "users": { + "unknown": 5 + }, + "components": { + "state-hub.fix-consistency": 5 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T22:38:29.976918Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "ed6451c9-d2cc-4486-9afe-d3852782cabc", + "interface_key": "rest_api:POST /workstreams/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T07:40:24.168535Z", + "replacement_ref": "/workplans/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T07:40:24.168535Z", + "updated_at": "2026-06-04T07:40:24.168535Z" + }, + "all_time": { + "calls": 824, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 824 + }, + "users": { + "unknown": 824 + }, + "components": { + "unknown": 824 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T20:38:05.903144Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "281fd706-b192-4194-8055-2d8733c115f9", + "interface_key": "rest_api:POST /workstreams/{workstream_id}/dependencies/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T22:57:37.149207Z", + "replacement_ref": "/workplans/{workplan_id}/dependencies/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T22:57:37.149207Z", + "updated_at": "2026-06-04T22:57:37.149207Z" + }, + "all_time": { + "calls": 4971, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 4971 + }, + "users": { + "unknown": 4971 + }, + "components": { + "unknown": 4971 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-01T22:09:24.229011Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + } + ], + "retirement_candidates": [ + { + "interface": { + "id": "63ed90e3-42b4-431a-94db-e1254a52f9e2", + "interface_key": "mcp:create_workstream", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.205900Z", + "replacement_ref": "create_workplan", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.205900Z", + "updated_at": "2026-07-10T13:19:44.205900Z" + }, + "all_time": { + "calls": 2, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 2 + }, + "users": { + "unknown": 2 + }, + "components": { + "state-hub.mcp": 2 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T13:20:43.378546Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "b6a5fe44-fb85-4ef7-a273-78d09da777af", + "interface_key": "mcp:list_workstreams", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:26.075491Z", + "replacement_ref": "list_workplans", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-10T13:19:26.075491Z", + "updated_at": "2026-07-10T13:19:26.075491Z" + }, + "all_time": { + "calls": 2, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 2 + }, + "users": { + "unknown": 2 + }, + "components": { + "state-hub.mcp": 2 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T13:20:42.374940Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "53c3372b-307d-4d6a-8ea4-bc36cacdcaf2", + "interface_key": "mcp:update_workstream", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.255216Z", + "replacement_ref": "update_workplan", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.255216Z", + "updated_at": "2026-07-10T13:19:44.255216Z" + }, + "all_time": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": null, + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "bed4d08a-3471-48f5-9f0e-f9d550383a26", + "interface_key": "mcp:update_workstream_status", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.303612Z", + "replacement_ref": "update_workplan_status", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.303612Z", + "updated_at": "2026-07-10T13:19:44.303612Z" + }, + "all_time": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": null, + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "46900dc2-0893-405b-ba59-80f31fb6ebb1", + "interface_key": "state://workstreams/{topic_slug}", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.344493Z", + "replacement_ref": "state://workplans/{topic_slug}", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.344493Z", + "updated_at": "2026-07-10T13:19:44.344493Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "state-hub.mcp": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T13:20:34.491811Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "cd9342fd-27e7-4a46-a1be-d41499e106a3", + "interface_key": "rest_api:DELETE /workstreams/{workstream_id}", + "interface_kind": "rest_api", + "legacy_since": "2026-06-06T17:28:13.052813Z", + "replacement_ref": "/workplans/{workplan_id}", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-06T17:28:13.052813Z", + "updated_at": "2026-06-06T17:28:13.052813Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "unknown": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-06-06T17:28:13.047529Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "a4e0948c-cd4a-461e-8ae2-7fd7b51d04a7", + "interface_key": "rest_api:GET /decisions/?workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-09T23:07:53.093223Z", + "replacement_ref": "/decisions/?workplan_id=", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-09T23:07:53.093223Z", + "updated_at": "2026-07-09T23:07:53.093223Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "unknown": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-09T23:07:53.083299Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "be3cdcf8-250b-4bf0-801f-02ffd5f57e00", + "interface_key": "rest_api:GET /tasks/?workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-08T19:32:21.628513Z", + "replacement_ref": "/tasks/?workplan_id=", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-08T19:32:21.628513Z", + "updated_at": "2026-07-08T19:32:21.628513Z" + }, + "all_time": { + "calls": 577, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 577 + }, + "users": { + "unknown": 577 + }, + "components": { + "unknown": 577 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T21:38:17.035426Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "3e4a3d0b-08fa-45c3-91e2-4f514a914b97", + "interface_key": "rest_api:GET /workstreams/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T00:26:14.533764Z", + "replacement_ref": "/workplans/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T00:26:14.533764Z", + "updated_at": "2026-06-04T00:26:14.533764Z" + }, + "all_time": { + "calls": 144084, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 144084 + }, + "users": { + "unknown": 144084 + }, + "components": { + "unknown": 144084 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T21:14:39.652776Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "e4c140ec-7479-46f9-b185-4b533f19d639", + "interface_key": "rest_api:GET /workstreams/workplan-index", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T05:20:58.321869Z", + "replacement_ref": "/workplans/index", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T05:20:58.321869Z", + "updated_at": "2026-06-04T05:20:58.321869Z" + }, + "all_time": { + "calls": 30, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 30 + }, + "users": { + "unknown": 30 + }, + "components": { + "unknown": 30 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-03T05:20:34.534439Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "3a00b19e-e7db-4403-98aa-a77c9ab61ecc", + "interface_key": "rest_api:GET /workstreams/{workstream_id}", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T00:25:59.014966Z", + "replacement_ref": "/workplans/{workplan_id}", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T00:25:59.014966Z", + "updated_at": "2026-06-04T00:25:59.014966Z" + }, + "all_time": { + "calls": 511405, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 511405 + }, + "users": { + "unknown": 511405 + }, + "components": { + "unknown": 511405 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T21:14:20.781103Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "69e1a255-a8b9-4101-8a68-b1bed23abfda", + "interface_key": "rest_api:GET /workstreams/{workstream_id}/dependencies/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T00:25:59.291135Z", + "replacement_ref": "/workplans/{workplan_id}/dependencies/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T00:25:59.291135Z", + "updated_at": "2026-06-04T00:25:59.291135Z" + }, + "all_time": { + "calls": 255865, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 255865 + }, + "users": { + "unknown": 255865 + }, + "components": { + "unknown": 255865 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T21:14:12.747005Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "b1aae931-b51f-4a85-a723-865dd65f6132", + "interface_key": "rest_api:PATCH /workstreams/{workstream_id}", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T06:09:41.901035Z", + "replacement_ref": "/workplans/{workplan_id}", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T06:09:41.901035Z", + "updated_at": "2026-06-04T06:09:41.901035Z" + }, + "all_time": { + "calls": 571, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 571 + }, + "users": { + "unknown": 571 + }, + "components": { + "unknown": 571 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T20:49:12.335710Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "4aea9591-970b-4918-ac4a-4da95a0a1a8c", + "interface_key": "rest_api:POST /decisions/ workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-09T22:56:35.473130Z", + "replacement_ref": "POST /decisions/ with workplan_id", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-09T22:56:35.473130Z", + "updated_at": "2026-07-09T22:56:35.473130Z" + }, + "all_time": { + "calls": 3, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 3 + }, + "users": { + "unknown": 3 + }, + "components": { + "unknown": 3 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-09T22:57:05.912456Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "4a70583c-1e41-4ec1-99a9-728856a7d79c", + "interface_key": "rest_api:POST /progress/ workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-08T23:35:09.148333Z", + "replacement_ref": "POST /progress/ with workplan_id", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-08T23:35:09.148333Z", + "updated_at": "2026-07-08T23:35:09.148333Z" + }, + "all_time": { + "calls": 14, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 14 + }, + "users": { + "unknown": 14 + }, + "components": { + "unknown": 14 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T08:34:42.959724Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "6df4e443-7bf1-461a-923b-a6eb52fe7b07", + "interface_key": "rest_api:POST /tasks/ workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-08T22:38:29.405720Z", + "replacement_ref": "POST /tasks/ with workplan_id", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-08T22:38:29.405720Z", + "updated_at": "2026-07-08T22:38:29.405720Z" + }, + "all_time": { + "calls": 5, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 5 + }, + "users": { + "unknown": 5 + }, + "components": { + "state-hub.fix-consistency": 5 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T22:38:29.976918Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "ed6451c9-d2cc-4486-9afe-d3852782cabc", + "interface_key": "rest_api:POST /workstreams/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T07:40:24.168535Z", + "replacement_ref": "/workplans/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T07:40:24.168535Z", + "updated_at": "2026-06-04T07:40:24.168535Z" + }, + "all_time": { + "calls": 824, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 824 + }, + "users": { + "unknown": 824 + }, + "components": { + "unknown": 824 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T20:38:05.903144Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "281fd706-b192-4194-8055-2d8733c115f9", + "interface_key": "rest_api:POST /workstreams/{workstream_id}/dependencies/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T22:57:37.149207Z", + "replacement_ref": "/workplans/{workplan_id}/dependencies/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T22:57:37.149207Z", + "updated_at": "2026-06-04T22:57:37.149207Z" + }, + "all_time": { + "calls": 4971, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 4971 + }, + "users": { + "unknown": 4971 + }, + "components": { + "unknown": 4971 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-01T22:09:24.229011Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + } + ] + }, + "hours": 8 +} diff --git a/workplans/STATE-WP-0070-workplan-terminology-phase2-retirement.md b/workplans/archived/260714-STATE-WP-0070-workplan-terminology-phase2-retirement.md similarity index 100% rename from workplans/STATE-WP-0070-workplan-terminology-phase2-retirement.md rename to workplans/archived/260714-STATE-WP-0070-workplan-terminology-phase2-retirement.md From 4d2898e2e2d3ff51704f37aba4f0380d38d9ea0c Mon Sep 17 00:00:00 2001 From: custodian-sync Date: Mon, 13 Jul 2026 23:52:49 +0000 Subject: [PATCH 006/321] chore(consistency): sync task status from DB [auto] Updated by fix-consistency on 2026-07-13: - update .custodian-brief.md for state-hub --- .custodian-brief.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.custodian-brief.md b/.custodian-brief.md index a809a49..b8b72fe 100644 --- a/.custodian-brief.md +++ b/.custodian-brief.md @@ -2,7 +2,7 @@ # Custodian Brief — state-hub **Domain:** infotech -**Last synced:** 2026-07-13 22:27 UTC +**Last synced:** 2026-07-13 23:52 UTC **State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)* ## Active Workstreams @@ -11,8 +11,9 @@ ## Inbox Hygiene -**Stale unread:** 4 message(s) older than 3 day(s) — triage at session start. -**Missing thread_id:** 4 unread message(s) lack supersession chains. +**Stale unread:** 5 message(s) older than 3 day(s) — triage at session start. +**Missing thread_id:** 5 unread message(s) lack supersession chains. +- ! activity-core: Suggestion: scheduled-wait vs blocked lifecycle + dashboard colors `83a9e875` - ! activity-core: Optional pattern suggestion: standardized deployment evidence handoff `10d37752` - ! inter-hub: Suggestion: document cross-repo coordination in State Hub suggestions handling `6e8510b1` From 1906289997773cba7a8212983bb856f6d5fdda84 Mon Sep 17 00:00:00 2001 From: tegwick Date: Thu, 16 Jul 2026 14:45:43 +0200 Subject: [PATCH 007/321] docs(deploy): state-hub Forgejo image promote flow (CUST-WP-0059 T06) Co-Authored-By: Claude Opus 4.8 --- .../apps/charts/state-hub/PROMOTE.md | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 deploy/railiance/apps/charts/state-hub/PROMOTE.md diff --git a/deploy/railiance/apps/charts/state-hub/PROMOTE.md b/deploy/railiance/apps/charts/state-hub/PROMOTE.md new file mode 100644 index 0000000..0d8d213 --- /dev/null +++ b/deploy/railiance/apps/charts/state-hub/PROMOTE.md @@ -0,0 +1,39 @@ +# state-hub image promotion (Forgejo) + +The state-hub container image is built and published by CI +(`.forgejo/workflows/image.yaml`) to the **Forgejo** registry on every `main` +push that touches the app: + +- `forgejo.coulomb.social/coulomb/state-hub:main-` — immutable, promotable +- `forgejo.coulomb.social/coulomb/state-hub:latest` — moving pointer to newest main + +The chart's `image.repository` defaults to `forgejo.coulomb.social/coulomb/state-hub`. + +## Promote a build to the cluster + +1. Pick a CI-produced tag to promote — **use an immutable `main-`**, not + `latest` (avoid surprise upgrades). List candidates: + ``` + # requires a Forgejo read token + curl -s -u : \ + "https://forgejo.coulomb.social/api/v1/packages/coulomb?type=container&q=state-hub" + ``` +2. Upgrade the release (rolling, `maxUnavailable=0` → zero downtime; `--atomic` + auto-rolls-back if the pull fails): + ``` + helm -n state-hub upgrade state-hub \ + --reuse-values \ + --set image.tag=main- \ + --atomic --timeout 3m + ``` +3. Update this chart's `appVersion` to the promoted `main-` and commit, so the + declared version matches what's deployed. + +## History + +- **2026-07-16:** the deployed release pinned the gitea-era tag `f2e042a`, which + Forgejo CI never produced. The running image was preserved by a one-off + gitea→Forgejo copy of `f2e042a` and the release repointed to Forgejo + (CUST-WP-0059). Going forward, promotions use CI `main-` tags — no manual + image copies. The next promotion should move off `f2e042a` onto a `main-` + and update `appVersion` accordingly. From 451f02c1d553a6ff0c0a84e154dcfb43f5849b12 Mon Sep 17 00:00:00 2001 From: tegwick Date: Fri, 17 Jul 2026 19:26:12 +0200 Subject: [PATCH 008/321] feat(dashboard): expandable full text on decision cards Cards previously showed a hard 200-char snippet of description OR rationale with no way to read the rest, and rationale was hidden whenever a description existed. Long or dual-field cards now expand in place (native
) to labeled full Description and Rationale sections; short single-field cards render unchanged. Co-Authored-By: Claude Fable 5 --- dashboard/src/decisions.md | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/dashboard/src/decisions.md b/dashboard/src/decisions.md index e23c8f5..029addf 100644 --- a/dashboard/src/decisions.md +++ b/dashboard/src/decisions.md @@ -330,7 +330,13 @@ if (filtered.length === 0) { } else { display(html`
${filtered.map(d => { const border = STATUS_BORDER[d.status] ?? "#ccc"; - const snippet = (d.description || d.rationale || "").slice(0, 200); + const _desc = d.description || ""; + const _rat = d.rationale || ""; + const _primary = _desc || _rat; + const snippet = _primary.slice(0, 200); + // Expandable when text is truncated, or when both fields exist (the + // collapsed view only shows one of them). + const _needsExpand = _primary.length > 200 || (_desc && _rat && _desc !== _rat); const due = fmtDate(d.deadline); const decided = fmtDate(d.decided_at); const overdue = isOverdue(d.deadline); @@ -375,7 +381,18 @@ if (filtered.length === 0) { ${_isOpen ? html`` : ""}
${d.title}
- ${snippet ? html`
${snippet}${snippet.length < (d.description || d.rationale || "").length ? "…" : ""}
` : ""} + ${_primary ? (_needsExpand + ? html`
+ + ${snippet}${_primary.length > 200 ? "…" : ""} + + +
+ ${_desc ? html`
Description
${_desc}
` : ""} + ${_rat ? html`
Rationale
${_rat}
` : ""} +
+
` + : html`
${_primary}
`) : ""} ${d.decided_by ? html`
✓ ${d.decided_by}${decided ? " · " + decided : ""}
` : ""} ${d.escalation_note && !["resolved", "superseded"].includes(d.status) ? html`
${d.escalation_note}
` : ""} `; @@ -458,6 +475,18 @@ if (escalated.length > 0) { .dec-title { font-weight: 600; font-size: 0.95rem; margin-bottom: 0.2rem; } .dec-snippet { font-size: 0.82rem; color: var(--theme-foreground-muted); line-height: 1.45; white-space: pre-wrap; } .dec-resolved-by { font-size: 0.78rem; color: #22c55e; margin-top: 0.3rem; } + +/* ── Expandable full text ─────────────────────────────────────────────────── */ +.dec-expand summary { list-style: none; cursor: pointer; } +.dec-expand summary::-webkit-details-marker { display: none; } +.dec-expand[open] .dec-snippet-collapsed { display: none; } +.dec-more::after { content: "Show full text ▾"; } +.dec-expand[open] .dec-more::after { content: "Show less ▴"; } +.dec-more { display: inline-block; margin-left: 0.35rem; font-size: 0.72rem; font-weight: 600; color: steelblue; white-space: nowrap; } +.dec-more:hover { text-decoration: underline; } +.dec-expand[open] .dec-more { display: block; margin: 0.15rem 0 0.35rem; } +.dec-fulltext-section + .dec-fulltext-section { margin-top: 0.5rem; } +.dec-fulltext-label { font-size: 0.65rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--theme-foreground-faint, #aaa); margin-bottom: 0.1rem; } .dec-escalation-note { font-size: 0.78rem; color: #b45309; margin-top: 0.3rem; background: #fef3c7; border-radius: 4px; padding: 0.25rem 0.5rem; } /* ── Resolve button ───────────────────────────────────────────────────────── */ From 814595c317b42121d83c2cb72b5d59ef9a4d4652 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 18 Jul 2026 16:40:17 +0200 Subject: [PATCH 009/321] evidence: legacy-meter weekly reviews 2026-07-16..18 (STATE-WP-0070 automation) Co-Authored-By: Claude Fable 5 --- .../legacy-meter-weekly-review-20260716.json | 1716 ++++++++++++++++ .../legacy-meter-weekly-review-20260717.json | 1755 +++++++++++++++++ .../legacy-meter-weekly-review-20260718.json | 1755 +++++++++++++++++ 3 files changed, 5226 insertions(+) create mode 100644 docs/evidence/legacy-meter-weekly-review-20260716.json create mode 100644 docs/evidence/legacy-meter-weekly-review-20260717.json create mode 100644 docs/evidence/legacy-meter-weekly-review-20260718.json diff --git a/docs/evidence/legacy-meter-weekly-review-20260716.json b/docs/evidence/legacy-meter-weekly-review-20260716.json new file mode 100644 index 0000000..99c3b95 --- /dev/null +++ b/docs/evidence/legacy-meter-weekly-review-20260716.json @@ -0,0 +1,1716 @@ +{ + "captured_at": "2026-07-16T14:00:05.458231+00:00", + "api_base": "http://127.0.0.1:8000", + "workplan": "STATE-WP-0070", + "retired_interfaces": [], + "weekly_review": { + "generated_at": "2026-07-16T14:00:05.452247Z", + "window_start": "2026-07-16T06:00:05.399179Z", + "window_end": "2026-07-16T14:00:05.399179Z", + "cadence": "weekly", + "activity_core_handoff": { + "activity_id": "statehub-legacy-interface-review", + "cadence": "weekly", + "source_endpoint": "/legacy-meter/weekly-review", + "state_owner": "state-hub", + "scheduler_owner": "activity-core" + }, + "interfaces": [ + { + "interface": { + "id": "8051fb40-66c9-4dc4-a413-13e8573e04a3", + "interface_key": "event_subject:org.statehub.workstream.completed", + "interface_kind": "event_subject", + "legacy_since": "2026-06-04T06:09:42.198193Z", + "replacement_ref": "org.statehub.workplan.completed", + "owner_component": "state-hub.events", + "status": "retired", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Retired by STATE-WP-0069 closeout", + "retired_at": "2026-07-08T21:38:52.773601Z", + "created_at": "2026-06-04T06:09:42.198193Z", + "updated_at": "2026-07-08T21:38:52.761243Z" + }, + "all_time": { + "calls": 255, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 255 + }, + "users": { + "unknown": 255 + }, + "components": { + "state-hub.events": 255 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T20:49:12.839383Z", + "retirement_candidate": false, + "retirement_reason": "already retired" + }, + { + "interface": { + "id": "63ed90e3-42b4-431a-94db-e1254a52f9e2", + "interface_key": "mcp:create_workstream", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.205900Z", + "replacement_ref": "create_workplan", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.205900Z", + "updated_at": "2026-07-10T13:19:44.205900Z" + }, + "all_time": { + "calls": 2, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 2 + }, + "users": { + "unknown": 2 + }, + "components": { + "state-hub.mcp": 2 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T13:20:43.378546Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "b6a5fe44-fb85-4ef7-a273-78d09da777af", + "interface_key": "mcp:list_workstreams", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:26.075491Z", + "replacement_ref": "list_workplans", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-10T13:19:26.075491Z", + "updated_at": "2026-07-10T13:19:26.075491Z" + }, + "all_time": { + "calls": 2, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 2 + }, + "users": { + "unknown": 2 + }, + "components": { + "state-hub.mcp": 2 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T13:20:42.374940Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "53c3372b-307d-4d6a-8ea4-bc36cacdcaf2", + "interface_key": "mcp:update_workstream", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.255216Z", + "replacement_ref": "update_workplan", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.255216Z", + "updated_at": "2026-07-10T13:19:44.255216Z" + }, + "all_time": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": null, + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "bed4d08a-3471-48f5-9f0e-f9d550383a26", + "interface_key": "mcp:update_workstream_status", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.303612Z", + "replacement_ref": "update_workplan_status", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.303612Z", + "updated_at": "2026-07-10T13:19:44.303612Z" + }, + "all_time": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": null, + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "46900dc2-0893-405b-ba59-80f31fb6ebb1", + "interface_key": "state://workstreams/{topic_slug}", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.344493Z", + "replacement_ref": "state://workplans/{topic_slug}", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.344493Z", + "updated_at": "2026-07-10T13:19:44.344493Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "state-hub.mcp": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T13:20:34.491811Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "cd9342fd-27e7-4a46-a1be-d41499e106a3", + "interface_key": "rest_api:DELETE /workstreams/{workstream_id}", + "interface_kind": "rest_api", + "legacy_since": "2026-06-06T17:28:13.052813Z", + "replacement_ref": "/workplans/{workplan_id}", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-06T17:28:13.052813Z", + "updated_at": "2026-06-06T17:28:13.052813Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "unknown": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-06-06T17:28:13.047529Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "a4e0948c-cd4a-461e-8ae2-7fd7b51d04a7", + "interface_key": "rest_api:GET /decisions/?workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-09T23:07:53.093223Z", + "replacement_ref": "/decisions/?workplan_id=", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-09T23:07:53.093223Z", + "updated_at": "2026-07-09T23:07:53.093223Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "unknown": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-09T23:07:53.083299Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "70a9346e-e0ee-42ac-874f-f160580b54dd", + "interface_key": "rest_api:GET /progress/?workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-13T22:04:03.911449Z", + "replacement_ref": "/progress/?workplan_id=", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-13T22:04:03.911449Z", + "updated_at": "2026-07-13T22:04:03.911449Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "unknown": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-13T22:04:03.904998Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "be3cdcf8-250b-4bf0-801f-02ffd5f57e00", + "interface_key": "rest_api:GET /tasks/?workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-08T19:32:21.628513Z", + "replacement_ref": "/tasks/?workplan_id=", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-08T19:32:21.628513Z", + "updated_at": "2026-07-08T19:32:21.628513Z" + }, + "all_time": { + "calls": 583, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 583 + }, + "users": { + "unknown": 583 + }, + "components": { + "unknown": 583 + } + }, + "window": { + "calls": 5, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 5 + }, + "users": { + "unknown": 5 + }, + "components": { + "unknown": 5 + } + }, + "last_seen_at": "2026-07-16T12:36:43.901956Z", + "retirement_candidate": false, + "retirement_reason": "5 call(s) in review window" + }, + { + "interface": { + "id": "3e4a3d0b-08fa-45c3-91e2-4f514a914b97", + "interface_key": "rest_api:GET /workstreams/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T00:26:14.533764Z", + "replacement_ref": "/workplans/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T00:26:14.533764Z", + "updated_at": "2026-06-04T00:26:14.533764Z" + }, + "all_time": { + "calls": 144084, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 144084 + }, + "users": { + "unknown": 144084 + }, + "components": { + "unknown": 144084 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T21:14:39.652776Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "e4c140ec-7479-46f9-b185-4b533f19d639", + "interface_key": "rest_api:GET /workstreams/workplan-index", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T05:20:58.321869Z", + "replacement_ref": "/workplans/index", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T05:20:58.321869Z", + "updated_at": "2026-06-04T05:20:58.321869Z" + }, + "all_time": { + "calls": 30, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 30 + }, + "users": { + "unknown": 30 + }, + "components": { + "unknown": 30 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-03T05:20:34.534439Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "3a00b19e-e7db-4403-98aa-a77c9ab61ecc", + "interface_key": "rest_api:GET /workstreams/{workstream_id}", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T00:25:59.014966Z", + "replacement_ref": "/workplans/{workplan_id}", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T00:25:59.014966Z", + "updated_at": "2026-06-04T00:25:59.014966Z" + }, + "all_time": { + "calls": 511405, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 511405 + }, + "users": { + "unknown": 511405 + }, + "components": { + "unknown": 511405 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T21:14:20.781103Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "69e1a255-a8b9-4101-8a68-b1bed23abfda", + "interface_key": "rest_api:GET /workstreams/{workstream_id}/dependencies/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T00:25:59.291135Z", + "replacement_ref": "/workplans/{workplan_id}/dependencies/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T00:25:59.291135Z", + "updated_at": "2026-06-04T00:25:59.291135Z" + }, + "all_time": { + "calls": 255865, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 255865 + }, + "users": { + "unknown": 255865 + }, + "components": { + "unknown": 255865 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T21:14:12.747005Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "b1aae931-b51f-4a85-a723-865dd65f6132", + "interface_key": "rest_api:PATCH /workstreams/{workstream_id}", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T06:09:41.901035Z", + "replacement_ref": "/workplans/{workplan_id}", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T06:09:41.901035Z", + "updated_at": "2026-06-04T06:09:41.901035Z" + }, + "all_time": { + "calls": 571, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 571 + }, + "users": { + "unknown": 571 + }, + "components": { + "unknown": 571 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T20:49:12.335710Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "4aea9591-970b-4918-ac4a-4da95a0a1a8c", + "interface_key": "rest_api:POST /decisions/ workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-09T22:56:35.473130Z", + "replacement_ref": "POST /decisions/ with workplan_id", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-09T22:56:35.473130Z", + "updated_at": "2026-07-09T22:56:35.473130Z" + }, + "all_time": { + "calls": 3, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 3 + }, + "users": { + "unknown": 3 + }, + "components": { + "unknown": 3 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-09T22:57:05.912456Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "4a70583c-1e41-4ec1-99a9-728856a7d79c", + "interface_key": "rest_api:POST /progress/ workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-08T23:35:09.148333Z", + "replacement_ref": "POST /progress/ with workplan_id", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-08T23:35:09.148333Z", + "updated_at": "2026-07-08T23:35:09.148333Z" + }, + "all_time": { + "calls": 14, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 14 + }, + "users": { + "unknown": 14 + }, + "components": { + "unknown": 14 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T08:34:42.959724Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "6df4e443-7bf1-461a-923b-a6eb52fe7b07", + "interface_key": "rest_api:POST /tasks/ workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-08T22:38:29.405720Z", + "replacement_ref": "POST /tasks/ with workplan_id", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-08T22:38:29.405720Z", + "updated_at": "2026-07-08T22:38:29.405720Z" + }, + "all_time": { + "calls": 5, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 5 + }, + "users": { + "unknown": 5 + }, + "components": { + "state-hub.fix-consistency": 5 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T22:38:29.976918Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "ed6451c9-d2cc-4486-9afe-d3852782cabc", + "interface_key": "rest_api:POST /workstreams/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T07:40:24.168535Z", + "replacement_ref": "/workplans/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T07:40:24.168535Z", + "updated_at": "2026-06-04T07:40:24.168535Z" + }, + "all_time": { + "calls": 824, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 824 + }, + "users": { + "unknown": 824 + }, + "components": { + "unknown": 824 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T20:38:05.903144Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "281fd706-b192-4194-8055-2d8733c115f9", + "interface_key": "rest_api:POST /workstreams/{workstream_id}/dependencies/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T22:57:37.149207Z", + "replacement_ref": "/workplans/{workplan_id}/dependencies/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T22:57:37.149207Z", + "updated_at": "2026-06-04T22:57:37.149207Z" + }, + "all_time": { + "calls": 4971, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 4971 + }, + "users": { + "unknown": 4971 + }, + "components": { + "unknown": 4971 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-01T22:09:24.229011Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + } + ], + "retirement_candidates": [ + { + "interface": { + "id": "63ed90e3-42b4-431a-94db-e1254a52f9e2", + "interface_key": "mcp:create_workstream", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.205900Z", + "replacement_ref": "create_workplan", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.205900Z", + "updated_at": "2026-07-10T13:19:44.205900Z" + }, + "all_time": { + "calls": 2, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 2 + }, + "users": { + "unknown": 2 + }, + "components": { + "state-hub.mcp": 2 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T13:20:43.378546Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "b6a5fe44-fb85-4ef7-a273-78d09da777af", + "interface_key": "mcp:list_workstreams", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:26.075491Z", + "replacement_ref": "list_workplans", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-10T13:19:26.075491Z", + "updated_at": "2026-07-10T13:19:26.075491Z" + }, + "all_time": { + "calls": 2, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 2 + }, + "users": { + "unknown": 2 + }, + "components": { + "state-hub.mcp": 2 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T13:20:42.374940Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "53c3372b-307d-4d6a-8ea4-bc36cacdcaf2", + "interface_key": "mcp:update_workstream", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.255216Z", + "replacement_ref": "update_workplan", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.255216Z", + "updated_at": "2026-07-10T13:19:44.255216Z" + }, + "all_time": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": null, + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "bed4d08a-3471-48f5-9f0e-f9d550383a26", + "interface_key": "mcp:update_workstream_status", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.303612Z", + "replacement_ref": "update_workplan_status", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.303612Z", + "updated_at": "2026-07-10T13:19:44.303612Z" + }, + "all_time": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": null, + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "46900dc2-0893-405b-ba59-80f31fb6ebb1", + "interface_key": "state://workstreams/{topic_slug}", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.344493Z", + "replacement_ref": "state://workplans/{topic_slug}", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.344493Z", + "updated_at": "2026-07-10T13:19:44.344493Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "state-hub.mcp": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T13:20:34.491811Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "cd9342fd-27e7-4a46-a1be-d41499e106a3", + "interface_key": "rest_api:DELETE /workstreams/{workstream_id}", + "interface_kind": "rest_api", + "legacy_since": "2026-06-06T17:28:13.052813Z", + "replacement_ref": "/workplans/{workplan_id}", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-06T17:28:13.052813Z", + "updated_at": "2026-06-06T17:28:13.052813Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "unknown": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-06-06T17:28:13.047529Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "a4e0948c-cd4a-461e-8ae2-7fd7b51d04a7", + "interface_key": "rest_api:GET /decisions/?workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-09T23:07:53.093223Z", + "replacement_ref": "/decisions/?workplan_id=", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-09T23:07:53.093223Z", + "updated_at": "2026-07-09T23:07:53.093223Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "unknown": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-09T23:07:53.083299Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "70a9346e-e0ee-42ac-874f-f160580b54dd", + "interface_key": "rest_api:GET /progress/?workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-13T22:04:03.911449Z", + "replacement_ref": "/progress/?workplan_id=", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-13T22:04:03.911449Z", + "updated_at": "2026-07-13T22:04:03.911449Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "unknown": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-13T22:04:03.904998Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "3e4a3d0b-08fa-45c3-91e2-4f514a914b97", + "interface_key": "rest_api:GET /workstreams/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T00:26:14.533764Z", + "replacement_ref": "/workplans/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T00:26:14.533764Z", + "updated_at": "2026-06-04T00:26:14.533764Z" + }, + "all_time": { + "calls": 144084, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 144084 + }, + "users": { + "unknown": 144084 + }, + "components": { + "unknown": 144084 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T21:14:39.652776Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "e4c140ec-7479-46f9-b185-4b533f19d639", + "interface_key": "rest_api:GET /workstreams/workplan-index", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T05:20:58.321869Z", + "replacement_ref": "/workplans/index", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T05:20:58.321869Z", + "updated_at": "2026-06-04T05:20:58.321869Z" + }, + "all_time": { + "calls": 30, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 30 + }, + "users": { + "unknown": 30 + }, + "components": { + "unknown": 30 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-03T05:20:34.534439Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "3a00b19e-e7db-4403-98aa-a77c9ab61ecc", + "interface_key": "rest_api:GET /workstreams/{workstream_id}", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T00:25:59.014966Z", + "replacement_ref": "/workplans/{workplan_id}", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T00:25:59.014966Z", + "updated_at": "2026-06-04T00:25:59.014966Z" + }, + "all_time": { + "calls": 511405, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 511405 + }, + "users": { + "unknown": 511405 + }, + "components": { + "unknown": 511405 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T21:14:20.781103Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "69e1a255-a8b9-4101-8a68-b1bed23abfda", + "interface_key": "rest_api:GET /workstreams/{workstream_id}/dependencies/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T00:25:59.291135Z", + "replacement_ref": "/workplans/{workplan_id}/dependencies/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T00:25:59.291135Z", + "updated_at": "2026-06-04T00:25:59.291135Z" + }, + "all_time": { + "calls": 255865, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 255865 + }, + "users": { + "unknown": 255865 + }, + "components": { + "unknown": 255865 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T21:14:12.747005Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "b1aae931-b51f-4a85-a723-865dd65f6132", + "interface_key": "rest_api:PATCH /workstreams/{workstream_id}", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T06:09:41.901035Z", + "replacement_ref": "/workplans/{workplan_id}", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T06:09:41.901035Z", + "updated_at": "2026-06-04T06:09:41.901035Z" + }, + "all_time": { + "calls": 571, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 571 + }, + "users": { + "unknown": 571 + }, + "components": { + "unknown": 571 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T20:49:12.335710Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "4aea9591-970b-4918-ac4a-4da95a0a1a8c", + "interface_key": "rest_api:POST /decisions/ workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-09T22:56:35.473130Z", + "replacement_ref": "POST /decisions/ with workplan_id", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-09T22:56:35.473130Z", + "updated_at": "2026-07-09T22:56:35.473130Z" + }, + "all_time": { + "calls": 3, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 3 + }, + "users": { + "unknown": 3 + }, + "components": { + "unknown": 3 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-09T22:57:05.912456Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "4a70583c-1e41-4ec1-99a9-728856a7d79c", + "interface_key": "rest_api:POST /progress/ workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-08T23:35:09.148333Z", + "replacement_ref": "POST /progress/ with workplan_id", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-08T23:35:09.148333Z", + "updated_at": "2026-07-08T23:35:09.148333Z" + }, + "all_time": { + "calls": 14, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 14 + }, + "users": { + "unknown": 14 + }, + "components": { + "unknown": 14 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T08:34:42.959724Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "6df4e443-7bf1-461a-923b-a6eb52fe7b07", + "interface_key": "rest_api:POST /tasks/ workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-08T22:38:29.405720Z", + "replacement_ref": "POST /tasks/ with workplan_id", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-08T22:38:29.405720Z", + "updated_at": "2026-07-08T22:38:29.405720Z" + }, + "all_time": { + "calls": 5, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 5 + }, + "users": { + "unknown": 5 + }, + "components": { + "state-hub.fix-consistency": 5 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T22:38:29.976918Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "ed6451c9-d2cc-4486-9afe-d3852782cabc", + "interface_key": "rest_api:POST /workstreams/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T07:40:24.168535Z", + "replacement_ref": "/workplans/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T07:40:24.168535Z", + "updated_at": "2026-06-04T07:40:24.168535Z" + }, + "all_time": { + "calls": 824, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 824 + }, + "users": { + "unknown": 824 + }, + "components": { + "unknown": 824 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T20:38:05.903144Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "281fd706-b192-4194-8055-2d8733c115f9", + "interface_key": "rest_api:POST /workstreams/{workstream_id}/dependencies/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T22:57:37.149207Z", + "replacement_ref": "/workplans/{workplan_id}/dependencies/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T22:57:37.149207Z", + "updated_at": "2026-06-04T22:57:37.149207Z" + }, + "all_time": { + "calls": 4971, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 4971 + }, + "users": { + "unknown": 4971 + }, + "components": { + "unknown": 4971 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-01T22:09:24.229011Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + } + ] + }, + "hours": 8 +} diff --git a/docs/evidence/legacy-meter-weekly-review-20260717.json b/docs/evidence/legacy-meter-weekly-review-20260717.json new file mode 100644 index 0000000..5f02fc3 --- /dev/null +++ b/docs/evidence/legacy-meter-weekly-review-20260717.json @@ -0,0 +1,1755 @@ +{ + "captured_at": "2026-07-17T14:00:08.022696+00:00", + "api_base": "http://127.0.0.1:8000", + "workplan": "STATE-WP-0070", + "retired_interfaces": [], + "weekly_review": { + "generated_at": "2026-07-17T14:00:07.987975Z", + "window_start": "2026-07-17T06:00:07.495685Z", + "window_end": "2026-07-17T14:00:07.495685Z", + "cadence": "weekly", + "activity_core_handoff": { + "activity_id": "statehub-legacy-interface-review", + "cadence": "weekly", + "source_endpoint": "/legacy-meter/weekly-review", + "state_owner": "state-hub", + "scheduler_owner": "activity-core" + }, + "interfaces": [ + { + "interface": { + "id": "8051fb40-66c9-4dc4-a413-13e8573e04a3", + "interface_key": "event_subject:org.statehub.workstream.completed", + "interface_kind": "event_subject", + "legacy_since": "2026-06-04T06:09:42.198193Z", + "replacement_ref": "org.statehub.workplan.completed", + "owner_component": "state-hub.events", + "status": "retired", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Retired by STATE-WP-0069 closeout", + "retired_at": "2026-07-08T21:38:52.773601Z", + "created_at": "2026-06-04T06:09:42.198193Z", + "updated_at": "2026-07-08T21:38:52.761243Z" + }, + "all_time": { + "calls": 255, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 255 + }, + "users": { + "unknown": 255 + }, + "components": { + "state-hub.events": 255 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T20:49:12.839383Z", + "retirement_candidate": false, + "retirement_reason": "already retired" + }, + { + "interface": { + "id": "63ed90e3-42b4-431a-94db-e1254a52f9e2", + "interface_key": "mcp:create_workstream", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.205900Z", + "replacement_ref": "create_workplan", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.205900Z", + "updated_at": "2026-07-10T13:19:44.205900Z" + }, + "all_time": { + "calls": 2, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 2 + }, + "users": { + "unknown": 2 + }, + "components": { + "state-hub.mcp": 2 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T13:20:43.378546Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "b6a5fe44-fb85-4ef7-a273-78d09da777af", + "interface_key": "mcp:list_workstreams", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:26.075491Z", + "replacement_ref": "list_workplans", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-10T13:19:26.075491Z", + "updated_at": "2026-07-10T13:19:26.075491Z" + }, + "all_time": { + "calls": 2, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 2 + }, + "users": { + "unknown": 2 + }, + "components": { + "state-hub.mcp": 2 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T13:20:42.374940Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "53c3372b-307d-4d6a-8ea4-bc36cacdcaf2", + "interface_key": "mcp:update_workstream", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.255216Z", + "replacement_ref": "update_workplan", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.255216Z", + "updated_at": "2026-07-10T13:19:44.255216Z" + }, + "all_time": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": null, + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "bed4d08a-3471-48f5-9f0e-f9d550383a26", + "interface_key": "mcp:update_workstream_status", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.303612Z", + "replacement_ref": "update_workplan_status", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.303612Z", + "updated_at": "2026-07-10T13:19:44.303612Z" + }, + "all_time": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": null, + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "46900dc2-0893-405b-ba59-80f31fb6ebb1", + "interface_key": "state://workstreams/{topic_slug}", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.344493Z", + "replacement_ref": "state://workplans/{topic_slug}", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.344493Z", + "updated_at": "2026-07-10T13:19:44.344493Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "state-hub.mcp": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T13:20:34.491811Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "cd9342fd-27e7-4a46-a1be-d41499e106a3", + "interface_key": "rest_api:DELETE /workstreams/{workstream_id}", + "interface_kind": "rest_api", + "legacy_since": "2026-06-06T17:28:13.052813Z", + "replacement_ref": "/workplans/{workplan_id}", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-06T17:28:13.052813Z", + "updated_at": "2026-06-06T17:28:13.052813Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "unknown": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-06-06T17:28:13.047529Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "a4e0948c-cd4a-461e-8ae2-7fd7b51d04a7", + "interface_key": "rest_api:GET /decisions/?workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-09T23:07:53.093223Z", + "replacement_ref": "/decisions/?workplan_id=", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-09T23:07:53.093223Z", + "updated_at": "2026-07-09T23:07:53.093223Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "unknown": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-09T23:07:53.083299Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "70a9346e-e0ee-42ac-874f-f160580b54dd", + "interface_key": "rest_api:GET /progress/?workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-13T22:04:03.911449Z", + "replacement_ref": "/progress/?workplan_id=", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-13T22:04:03.911449Z", + "updated_at": "2026-07-13T22:04:03.911449Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "unknown": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-13T22:04:03.904998Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "be3cdcf8-250b-4bf0-801f-02ffd5f57e00", + "interface_key": "rest_api:GET /tasks/?workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-08T19:32:21.628513Z", + "replacement_ref": "/tasks/?workplan_id=", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-08T19:32:21.628513Z", + "updated_at": "2026-07-08T19:32:21.628513Z" + }, + "all_time": { + "calls": 583, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 583 + }, + "users": { + "unknown": 583 + }, + "components": { + "unknown": 583 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-16T12:36:43.901956Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "3e4a3d0b-08fa-45c3-91e2-4f514a914b97", + "interface_key": "rest_api:GET /workstreams/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T00:26:14.533764Z", + "replacement_ref": "/workplans/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T00:26:14.533764Z", + "updated_at": "2026-06-04T00:26:14.533764Z" + }, + "all_time": { + "calls": 144084, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 144084 + }, + "users": { + "unknown": 144084 + }, + "components": { + "unknown": 144084 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T21:14:39.652776Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "e4c140ec-7479-46f9-b185-4b533f19d639", + "interface_key": "rest_api:GET /workstreams/workplan-index", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T05:20:58.321869Z", + "replacement_ref": "/workplans/index", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T05:20:58.321869Z", + "updated_at": "2026-06-04T05:20:58.321869Z" + }, + "all_time": { + "calls": 30, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 30 + }, + "users": { + "unknown": 30 + }, + "components": { + "unknown": 30 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-03T05:20:34.534439Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "3a00b19e-e7db-4403-98aa-a77c9ab61ecc", + "interface_key": "rest_api:GET /workstreams/{workstream_id}", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T00:25:59.014966Z", + "replacement_ref": "/workplans/{workplan_id}", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T00:25:59.014966Z", + "updated_at": "2026-06-04T00:25:59.014966Z" + }, + "all_time": { + "calls": 511405, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 511405 + }, + "users": { + "unknown": 511405 + }, + "components": { + "unknown": 511405 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T21:14:20.781103Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "69e1a255-a8b9-4101-8a68-b1bed23abfda", + "interface_key": "rest_api:GET /workstreams/{workstream_id}/dependencies/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T00:25:59.291135Z", + "replacement_ref": "/workplans/{workplan_id}/dependencies/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T00:25:59.291135Z", + "updated_at": "2026-06-04T00:25:59.291135Z" + }, + "all_time": { + "calls": 255865, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 255865 + }, + "users": { + "unknown": 255865 + }, + "components": { + "unknown": 255865 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T21:14:12.747005Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "b1aae931-b51f-4a85-a723-865dd65f6132", + "interface_key": "rest_api:PATCH /workstreams/{workstream_id}", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T06:09:41.901035Z", + "replacement_ref": "/workplans/{workplan_id}", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T06:09:41.901035Z", + "updated_at": "2026-06-04T06:09:41.901035Z" + }, + "all_time": { + "calls": 571, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 571 + }, + "users": { + "unknown": 571 + }, + "components": { + "unknown": 571 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T20:49:12.335710Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "4aea9591-970b-4918-ac4a-4da95a0a1a8c", + "interface_key": "rest_api:POST /decisions/ workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-09T22:56:35.473130Z", + "replacement_ref": "POST /decisions/ with workplan_id", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-09T22:56:35.473130Z", + "updated_at": "2026-07-09T22:56:35.473130Z" + }, + "all_time": { + "calls": 3, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 3 + }, + "users": { + "unknown": 3 + }, + "components": { + "unknown": 3 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-09T22:57:05.912456Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "4a70583c-1e41-4ec1-99a9-728856a7d79c", + "interface_key": "rest_api:POST /progress/ workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-08T23:35:09.148333Z", + "replacement_ref": "POST /progress/ with workplan_id", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-08T23:35:09.148333Z", + "updated_at": "2026-07-08T23:35:09.148333Z" + }, + "all_time": { + "calls": 14, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 14 + }, + "users": { + "unknown": 14 + }, + "components": { + "unknown": 14 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T08:34:42.959724Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "6df4e443-7bf1-461a-923b-a6eb52fe7b07", + "interface_key": "rest_api:POST /tasks/ workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-08T22:38:29.405720Z", + "replacement_ref": "POST /tasks/ with workplan_id", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-08T22:38:29.405720Z", + "updated_at": "2026-07-08T22:38:29.405720Z" + }, + "all_time": { + "calls": 5, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 5 + }, + "users": { + "unknown": 5 + }, + "components": { + "state-hub.fix-consistency": 5 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T22:38:29.976918Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "ed6451c9-d2cc-4486-9afe-d3852782cabc", + "interface_key": "rest_api:POST /workstreams/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T07:40:24.168535Z", + "replacement_ref": "/workplans/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T07:40:24.168535Z", + "updated_at": "2026-06-04T07:40:24.168535Z" + }, + "all_time": { + "calls": 824, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 824 + }, + "users": { + "unknown": 824 + }, + "components": { + "unknown": 824 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T20:38:05.903144Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "281fd706-b192-4194-8055-2d8733c115f9", + "interface_key": "rest_api:POST /workstreams/{workstream_id}/dependencies/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T22:57:37.149207Z", + "replacement_ref": "/workplans/{workplan_id}/dependencies/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T22:57:37.149207Z", + "updated_at": "2026-06-04T22:57:37.149207Z" + }, + "all_time": { + "calls": 4971, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 4971 + }, + "users": { + "unknown": 4971 + }, + "components": { + "unknown": 4971 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-01T22:09:24.229011Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + } + ], + "retirement_candidates": [ + { + "interface": { + "id": "63ed90e3-42b4-431a-94db-e1254a52f9e2", + "interface_key": "mcp:create_workstream", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.205900Z", + "replacement_ref": "create_workplan", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.205900Z", + "updated_at": "2026-07-10T13:19:44.205900Z" + }, + "all_time": { + "calls": 2, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 2 + }, + "users": { + "unknown": 2 + }, + "components": { + "state-hub.mcp": 2 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T13:20:43.378546Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "b6a5fe44-fb85-4ef7-a273-78d09da777af", + "interface_key": "mcp:list_workstreams", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:26.075491Z", + "replacement_ref": "list_workplans", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-10T13:19:26.075491Z", + "updated_at": "2026-07-10T13:19:26.075491Z" + }, + "all_time": { + "calls": 2, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 2 + }, + "users": { + "unknown": 2 + }, + "components": { + "state-hub.mcp": 2 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T13:20:42.374940Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "53c3372b-307d-4d6a-8ea4-bc36cacdcaf2", + "interface_key": "mcp:update_workstream", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.255216Z", + "replacement_ref": "update_workplan", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.255216Z", + "updated_at": "2026-07-10T13:19:44.255216Z" + }, + "all_time": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": null, + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "bed4d08a-3471-48f5-9f0e-f9d550383a26", + "interface_key": "mcp:update_workstream_status", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.303612Z", + "replacement_ref": "update_workplan_status", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.303612Z", + "updated_at": "2026-07-10T13:19:44.303612Z" + }, + "all_time": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": null, + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "46900dc2-0893-405b-ba59-80f31fb6ebb1", + "interface_key": "state://workstreams/{topic_slug}", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.344493Z", + "replacement_ref": "state://workplans/{topic_slug}", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.344493Z", + "updated_at": "2026-07-10T13:19:44.344493Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "state-hub.mcp": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T13:20:34.491811Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "cd9342fd-27e7-4a46-a1be-d41499e106a3", + "interface_key": "rest_api:DELETE /workstreams/{workstream_id}", + "interface_kind": "rest_api", + "legacy_since": "2026-06-06T17:28:13.052813Z", + "replacement_ref": "/workplans/{workplan_id}", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-06T17:28:13.052813Z", + "updated_at": "2026-06-06T17:28:13.052813Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "unknown": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-06-06T17:28:13.047529Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "a4e0948c-cd4a-461e-8ae2-7fd7b51d04a7", + "interface_key": "rest_api:GET /decisions/?workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-09T23:07:53.093223Z", + "replacement_ref": "/decisions/?workplan_id=", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-09T23:07:53.093223Z", + "updated_at": "2026-07-09T23:07:53.093223Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "unknown": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-09T23:07:53.083299Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "70a9346e-e0ee-42ac-874f-f160580b54dd", + "interface_key": "rest_api:GET /progress/?workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-13T22:04:03.911449Z", + "replacement_ref": "/progress/?workplan_id=", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-13T22:04:03.911449Z", + "updated_at": "2026-07-13T22:04:03.911449Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "unknown": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-13T22:04:03.904998Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "be3cdcf8-250b-4bf0-801f-02ffd5f57e00", + "interface_key": "rest_api:GET /tasks/?workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-08T19:32:21.628513Z", + "replacement_ref": "/tasks/?workplan_id=", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-08T19:32:21.628513Z", + "updated_at": "2026-07-08T19:32:21.628513Z" + }, + "all_time": { + "calls": 583, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 583 + }, + "users": { + "unknown": 583 + }, + "components": { + "unknown": 583 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-16T12:36:43.901956Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "3e4a3d0b-08fa-45c3-91e2-4f514a914b97", + "interface_key": "rest_api:GET /workstreams/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T00:26:14.533764Z", + "replacement_ref": "/workplans/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T00:26:14.533764Z", + "updated_at": "2026-06-04T00:26:14.533764Z" + }, + "all_time": { + "calls": 144084, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 144084 + }, + "users": { + "unknown": 144084 + }, + "components": { + "unknown": 144084 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T21:14:39.652776Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "e4c140ec-7479-46f9-b185-4b533f19d639", + "interface_key": "rest_api:GET /workstreams/workplan-index", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T05:20:58.321869Z", + "replacement_ref": "/workplans/index", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T05:20:58.321869Z", + "updated_at": "2026-06-04T05:20:58.321869Z" + }, + "all_time": { + "calls": 30, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 30 + }, + "users": { + "unknown": 30 + }, + "components": { + "unknown": 30 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-03T05:20:34.534439Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "3a00b19e-e7db-4403-98aa-a77c9ab61ecc", + "interface_key": "rest_api:GET /workstreams/{workstream_id}", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T00:25:59.014966Z", + "replacement_ref": "/workplans/{workplan_id}", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T00:25:59.014966Z", + "updated_at": "2026-06-04T00:25:59.014966Z" + }, + "all_time": { + "calls": 511405, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 511405 + }, + "users": { + "unknown": 511405 + }, + "components": { + "unknown": 511405 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T21:14:20.781103Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "69e1a255-a8b9-4101-8a68-b1bed23abfda", + "interface_key": "rest_api:GET /workstreams/{workstream_id}/dependencies/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T00:25:59.291135Z", + "replacement_ref": "/workplans/{workplan_id}/dependencies/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T00:25:59.291135Z", + "updated_at": "2026-06-04T00:25:59.291135Z" + }, + "all_time": { + "calls": 255865, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 255865 + }, + "users": { + "unknown": 255865 + }, + "components": { + "unknown": 255865 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T21:14:12.747005Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "b1aae931-b51f-4a85-a723-865dd65f6132", + "interface_key": "rest_api:PATCH /workstreams/{workstream_id}", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T06:09:41.901035Z", + "replacement_ref": "/workplans/{workplan_id}", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T06:09:41.901035Z", + "updated_at": "2026-06-04T06:09:41.901035Z" + }, + "all_time": { + "calls": 571, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 571 + }, + "users": { + "unknown": 571 + }, + "components": { + "unknown": 571 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T20:49:12.335710Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "4aea9591-970b-4918-ac4a-4da95a0a1a8c", + "interface_key": "rest_api:POST /decisions/ workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-09T22:56:35.473130Z", + "replacement_ref": "POST /decisions/ with workplan_id", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-09T22:56:35.473130Z", + "updated_at": "2026-07-09T22:56:35.473130Z" + }, + "all_time": { + "calls": 3, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 3 + }, + "users": { + "unknown": 3 + }, + "components": { + "unknown": 3 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-09T22:57:05.912456Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "4a70583c-1e41-4ec1-99a9-728856a7d79c", + "interface_key": "rest_api:POST /progress/ workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-08T23:35:09.148333Z", + "replacement_ref": "POST /progress/ with workplan_id", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-08T23:35:09.148333Z", + "updated_at": "2026-07-08T23:35:09.148333Z" + }, + "all_time": { + "calls": 14, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 14 + }, + "users": { + "unknown": 14 + }, + "components": { + "unknown": 14 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T08:34:42.959724Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "6df4e443-7bf1-461a-923b-a6eb52fe7b07", + "interface_key": "rest_api:POST /tasks/ workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-08T22:38:29.405720Z", + "replacement_ref": "POST /tasks/ with workplan_id", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-08T22:38:29.405720Z", + "updated_at": "2026-07-08T22:38:29.405720Z" + }, + "all_time": { + "calls": 5, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 5 + }, + "users": { + "unknown": 5 + }, + "components": { + "state-hub.fix-consistency": 5 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T22:38:29.976918Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "ed6451c9-d2cc-4486-9afe-d3852782cabc", + "interface_key": "rest_api:POST /workstreams/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T07:40:24.168535Z", + "replacement_ref": "/workplans/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T07:40:24.168535Z", + "updated_at": "2026-06-04T07:40:24.168535Z" + }, + "all_time": { + "calls": 824, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 824 + }, + "users": { + "unknown": 824 + }, + "components": { + "unknown": 824 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T20:38:05.903144Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "281fd706-b192-4194-8055-2d8733c115f9", + "interface_key": "rest_api:POST /workstreams/{workstream_id}/dependencies/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T22:57:37.149207Z", + "replacement_ref": "/workplans/{workplan_id}/dependencies/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T22:57:37.149207Z", + "updated_at": "2026-06-04T22:57:37.149207Z" + }, + "all_time": { + "calls": 4971, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 4971 + }, + "users": { + "unknown": 4971 + }, + "components": { + "unknown": 4971 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-01T22:09:24.229011Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + } + ] + }, + "hours": 8 +} diff --git a/docs/evidence/legacy-meter-weekly-review-20260718.json b/docs/evidence/legacy-meter-weekly-review-20260718.json new file mode 100644 index 0000000..1e7679c --- /dev/null +++ b/docs/evidence/legacy-meter-weekly-review-20260718.json @@ -0,0 +1,1755 @@ +{ + "captured_at": "2026-07-18T14:00:04.817890+00:00", + "api_base": "http://127.0.0.1:8000", + "workplan": "STATE-WP-0070", + "retired_interfaces": [], + "weekly_review": { + "generated_at": "2026-07-18T14:00:04.812359Z", + "window_start": "2026-07-18T06:00:04.743344Z", + "window_end": "2026-07-18T14:00:04.743344Z", + "cadence": "weekly", + "activity_core_handoff": { + "activity_id": "statehub-legacy-interface-review", + "cadence": "weekly", + "source_endpoint": "/legacy-meter/weekly-review", + "state_owner": "state-hub", + "scheduler_owner": "activity-core" + }, + "interfaces": [ + { + "interface": { + "id": "8051fb40-66c9-4dc4-a413-13e8573e04a3", + "interface_key": "event_subject:org.statehub.workstream.completed", + "interface_kind": "event_subject", + "legacy_since": "2026-06-04T06:09:42.198193Z", + "replacement_ref": "org.statehub.workplan.completed", + "owner_component": "state-hub.events", + "status": "retired", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Retired by STATE-WP-0069 closeout", + "retired_at": "2026-07-08T21:38:52.773601Z", + "created_at": "2026-06-04T06:09:42.198193Z", + "updated_at": "2026-07-08T21:38:52.761243Z" + }, + "all_time": { + "calls": 255, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 255 + }, + "users": { + "unknown": 255 + }, + "components": { + "state-hub.events": 255 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T20:49:12.839383Z", + "retirement_candidate": false, + "retirement_reason": "already retired" + }, + { + "interface": { + "id": "63ed90e3-42b4-431a-94db-e1254a52f9e2", + "interface_key": "mcp:create_workstream", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.205900Z", + "replacement_ref": "create_workplan", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.205900Z", + "updated_at": "2026-07-10T13:19:44.205900Z" + }, + "all_time": { + "calls": 2, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 2 + }, + "users": { + "unknown": 2 + }, + "components": { + "state-hub.mcp": 2 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T13:20:43.378546Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "b6a5fe44-fb85-4ef7-a273-78d09da777af", + "interface_key": "mcp:list_workstreams", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:26.075491Z", + "replacement_ref": "list_workplans", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-10T13:19:26.075491Z", + "updated_at": "2026-07-10T13:19:26.075491Z" + }, + "all_time": { + "calls": 2, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 2 + }, + "users": { + "unknown": 2 + }, + "components": { + "state-hub.mcp": 2 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T13:20:42.374940Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "53c3372b-307d-4d6a-8ea4-bc36cacdcaf2", + "interface_key": "mcp:update_workstream", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.255216Z", + "replacement_ref": "update_workplan", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.255216Z", + "updated_at": "2026-07-10T13:19:44.255216Z" + }, + "all_time": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": null, + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "bed4d08a-3471-48f5-9f0e-f9d550383a26", + "interface_key": "mcp:update_workstream_status", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.303612Z", + "replacement_ref": "update_workplan_status", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.303612Z", + "updated_at": "2026-07-10T13:19:44.303612Z" + }, + "all_time": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": null, + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "46900dc2-0893-405b-ba59-80f31fb6ebb1", + "interface_key": "state://workstreams/{topic_slug}", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.344493Z", + "replacement_ref": "state://workplans/{topic_slug}", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.344493Z", + "updated_at": "2026-07-10T13:19:44.344493Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "state-hub.mcp": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T13:20:34.491811Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "cd9342fd-27e7-4a46-a1be-d41499e106a3", + "interface_key": "rest_api:DELETE /workstreams/{workstream_id}", + "interface_kind": "rest_api", + "legacy_since": "2026-06-06T17:28:13.052813Z", + "replacement_ref": "/workplans/{workplan_id}", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-06T17:28:13.052813Z", + "updated_at": "2026-06-06T17:28:13.052813Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "unknown": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-06-06T17:28:13.047529Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "a4e0948c-cd4a-461e-8ae2-7fd7b51d04a7", + "interface_key": "rest_api:GET /decisions/?workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-09T23:07:53.093223Z", + "replacement_ref": "/decisions/?workplan_id=", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-09T23:07:53.093223Z", + "updated_at": "2026-07-09T23:07:53.093223Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "unknown": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-09T23:07:53.083299Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "70a9346e-e0ee-42ac-874f-f160580b54dd", + "interface_key": "rest_api:GET /progress/?workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-13T22:04:03.911449Z", + "replacement_ref": "/progress/?workplan_id=", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-13T22:04:03.911449Z", + "updated_at": "2026-07-13T22:04:03.911449Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "unknown": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-13T22:04:03.904998Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "be3cdcf8-250b-4bf0-801f-02ffd5f57e00", + "interface_key": "rest_api:GET /tasks/?workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-08T19:32:21.628513Z", + "replacement_ref": "/tasks/?workplan_id=", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-08T19:32:21.628513Z", + "updated_at": "2026-07-08T19:32:21.628513Z" + }, + "all_time": { + "calls": 583, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 583 + }, + "users": { + "unknown": 583 + }, + "components": { + "unknown": 583 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-16T12:36:43.901956Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "3e4a3d0b-08fa-45c3-91e2-4f514a914b97", + "interface_key": "rest_api:GET /workstreams/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T00:26:14.533764Z", + "replacement_ref": "/workplans/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T00:26:14.533764Z", + "updated_at": "2026-06-04T00:26:14.533764Z" + }, + "all_time": { + "calls": 144084, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 144084 + }, + "users": { + "unknown": 144084 + }, + "components": { + "unknown": 144084 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T21:14:39.652776Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "e4c140ec-7479-46f9-b185-4b533f19d639", + "interface_key": "rest_api:GET /workstreams/workplan-index", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T05:20:58.321869Z", + "replacement_ref": "/workplans/index", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T05:20:58.321869Z", + "updated_at": "2026-06-04T05:20:58.321869Z" + }, + "all_time": { + "calls": 30, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 30 + }, + "users": { + "unknown": 30 + }, + "components": { + "unknown": 30 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-03T05:20:34.534439Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "3a00b19e-e7db-4403-98aa-a77c9ab61ecc", + "interface_key": "rest_api:GET /workstreams/{workstream_id}", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T00:25:59.014966Z", + "replacement_ref": "/workplans/{workplan_id}", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T00:25:59.014966Z", + "updated_at": "2026-06-04T00:25:59.014966Z" + }, + "all_time": { + "calls": 511405, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 511405 + }, + "users": { + "unknown": 511405 + }, + "components": { + "unknown": 511405 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T21:14:20.781103Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "69e1a255-a8b9-4101-8a68-b1bed23abfda", + "interface_key": "rest_api:GET /workstreams/{workstream_id}/dependencies/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T00:25:59.291135Z", + "replacement_ref": "/workplans/{workplan_id}/dependencies/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T00:25:59.291135Z", + "updated_at": "2026-06-04T00:25:59.291135Z" + }, + "all_time": { + "calls": 255865, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 255865 + }, + "users": { + "unknown": 255865 + }, + "components": { + "unknown": 255865 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T21:14:12.747005Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "b1aae931-b51f-4a85-a723-865dd65f6132", + "interface_key": "rest_api:PATCH /workstreams/{workstream_id}", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T06:09:41.901035Z", + "replacement_ref": "/workplans/{workplan_id}", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T06:09:41.901035Z", + "updated_at": "2026-06-04T06:09:41.901035Z" + }, + "all_time": { + "calls": 571, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 571 + }, + "users": { + "unknown": 571 + }, + "components": { + "unknown": 571 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T20:49:12.335710Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "4aea9591-970b-4918-ac4a-4da95a0a1a8c", + "interface_key": "rest_api:POST /decisions/ workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-09T22:56:35.473130Z", + "replacement_ref": "POST /decisions/ with workplan_id", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-09T22:56:35.473130Z", + "updated_at": "2026-07-09T22:56:35.473130Z" + }, + "all_time": { + "calls": 3, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 3 + }, + "users": { + "unknown": 3 + }, + "components": { + "unknown": 3 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-09T22:57:05.912456Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "4a70583c-1e41-4ec1-99a9-728856a7d79c", + "interface_key": "rest_api:POST /progress/ workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-08T23:35:09.148333Z", + "replacement_ref": "POST /progress/ with workplan_id", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-08T23:35:09.148333Z", + "updated_at": "2026-07-08T23:35:09.148333Z" + }, + "all_time": { + "calls": 14, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 14 + }, + "users": { + "unknown": 14 + }, + "components": { + "unknown": 14 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T08:34:42.959724Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "6df4e443-7bf1-461a-923b-a6eb52fe7b07", + "interface_key": "rest_api:POST /tasks/ workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-08T22:38:29.405720Z", + "replacement_ref": "POST /tasks/ with workplan_id", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-08T22:38:29.405720Z", + "updated_at": "2026-07-08T22:38:29.405720Z" + }, + "all_time": { + "calls": 5, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 5 + }, + "users": { + "unknown": 5 + }, + "components": { + "state-hub.fix-consistency": 5 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T22:38:29.976918Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "ed6451c9-d2cc-4486-9afe-d3852782cabc", + "interface_key": "rest_api:POST /workstreams/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T07:40:24.168535Z", + "replacement_ref": "/workplans/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T07:40:24.168535Z", + "updated_at": "2026-06-04T07:40:24.168535Z" + }, + "all_time": { + "calls": 824, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 824 + }, + "users": { + "unknown": 824 + }, + "components": { + "unknown": 824 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T20:38:05.903144Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "281fd706-b192-4194-8055-2d8733c115f9", + "interface_key": "rest_api:POST /workstreams/{workstream_id}/dependencies/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T22:57:37.149207Z", + "replacement_ref": "/workplans/{workplan_id}/dependencies/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T22:57:37.149207Z", + "updated_at": "2026-06-04T22:57:37.149207Z" + }, + "all_time": { + "calls": 4971, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 4971 + }, + "users": { + "unknown": 4971 + }, + "components": { + "unknown": 4971 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-01T22:09:24.229011Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + } + ], + "retirement_candidates": [ + { + "interface": { + "id": "63ed90e3-42b4-431a-94db-e1254a52f9e2", + "interface_key": "mcp:create_workstream", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.205900Z", + "replacement_ref": "create_workplan", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.205900Z", + "updated_at": "2026-07-10T13:19:44.205900Z" + }, + "all_time": { + "calls": 2, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 2 + }, + "users": { + "unknown": 2 + }, + "components": { + "state-hub.mcp": 2 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T13:20:43.378546Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "b6a5fe44-fb85-4ef7-a273-78d09da777af", + "interface_key": "mcp:list_workstreams", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:26.075491Z", + "replacement_ref": "list_workplans", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-10T13:19:26.075491Z", + "updated_at": "2026-07-10T13:19:26.075491Z" + }, + "all_time": { + "calls": 2, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 2 + }, + "users": { + "unknown": 2 + }, + "components": { + "state-hub.mcp": 2 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T13:20:42.374940Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "53c3372b-307d-4d6a-8ea4-bc36cacdcaf2", + "interface_key": "mcp:update_workstream", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.255216Z", + "replacement_ref": "update_workplan", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.255216Z", + "updated_at": "2026-07-10T13:19:44.255216Z" + }, + "all_time": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": null, + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "bed4d08a-3471-48f5-9f0e-f9d550383a26", + "interface_key": "mcp:update_workstream_status", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.303612Z", + "replacement_ref": "update_workplan_status", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.303612Z", + "updated_at": "2026-07-10T13:19:44.303612Z" + }, + "all_time": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": null, + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "46900dc2-0893-405b-ba59-80f31fb6ebb1", + "interface_key": "state://workstreams/{topic_slug}", + "interface_kind": "mcp_tool", + "legacy_since": "2026-07-10T13:19:44.344493Z", + "replacement_ref": "state://workplans/{topic_slug}", + "owner_component": "state-hub.mcp", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": "Registered by STATE-WP-0070 T03 \u2014 zero-window streak starts at registration", + "retired_at": null, + "created_at": "2026-07-10T13:19:44.344493Z", + "updated_at": "2026-07-10T13:19:44.344493Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "state-hub.mcp": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T13:20:34.491811Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "cd9342fd-27e7-4a46-a1be-d41499e106a3", + "interface_key": "rest_api:DELETE /workstreams/{workstream_id}", + "interface_kind": "rest_api", + "legacy_since": "2026-06-06T17:28:13.052813Z", + "replacement_ref": "/workplans/{workplan_id}", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-06T17:28:13.052813Z", + "updated_at": "2026-06-06T17:28:13.052813Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "unknown": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-06-06T17:28:13.047529Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "a4e0948c-cd4a-461e-8ae2-7fd7b51d04a7", + "interface_key": "rest_api:GET /decisions/?workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-09T23:07:53.093223Z", + "replacement_ref": "/decisions/?workplan_id=", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-09T23:07:53.093223Z", + "updated_at": "2026-07-09T23:07:53.093223Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "unknown": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-09T23:07:53.083299Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "70a9346e-e0ee-42ac-874f-f160580b54dd", + "interface_key": "rest_api:GET /progress/?workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-13T22:04:03.911449Z", + "replacement_ref": "/progress/?workplan_id=", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-13T22:04:03.911449Z", + "updated_at": "2026-07-13T22:04:03.911449Z" + }, + "all_time": { + "calls": 1, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 1 + }, + "users": { + "unknown": 1 + }, + "components": { + "unknown": 1 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-13T22:04:03.904998Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "be3cdcf8-250b-4bf0-801f-02ffd5f57e00", + "interface_key": "rest_api:GET /tasks/?workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-08T19:32:21.628513Z", + "replacement_ref": "/tasks/?workplan_id=", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-08T19:32:21.628513Z", + "updated_at": "2026-07-08T19:32:21.628513Z" + }, + "all_time": { + "calls": 583, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 583 + }, + "users": { + "unknown": 583 + }, + "components": { + "unknown": 583 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-16T12:36:43.901956Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "3e4a3d0b-08fa-45c3-91e2-4f514a914b97", + "interface_key": "rest_api:GET /workstreams/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T00:26:14.533764Z", + "replacement_ref": "/workplans/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T00:26:14.533764Z", + "updated_at": "2026-06-04T00:26:14.533764Z" + }, + "all_time": { + "calls": 144084, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 144084 + }, + "users": { + "unknown": 144084 + }, + "components": { + "unknown": 144084 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T21:14:39.652776Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "e4c140ec-7479-46f9-b185-4b533f19d639", + "interface_key": "rest_api:GET /workstreams/workplan-index", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T05:20:58.321869Z", + "replacement_ref": "/workplans/index", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T05:20:58.321869Z", + "updated_at": "2026-06-04T05:20:58.321869Z" + }, + "all_time": { + "calls": 30, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 30 + }, + "users": { + "unknown": 30 + }, + "components": { + "unknown": 30 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-03T05:20:34.534439Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "3a00b19e-e7db-4403-98aa-a77c9ab61ecc", + "interface_key": "rest_api:GET /workstreams/{workstream_id}", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T00:25:59.014966Z", + "replacement_ref": "/workplans/{workplan_id}", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T00:25:59.014966Z", + "updated_at": "2026-06-04T00:25:59.014966Z" + }, + "all_time": { + "calls": 511405, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 511405 + }, + "users": { + "unknown": 511405 + }, + "components": { + "unknown": 511405 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T21:14:20.781103Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "69e1a255-a8b9-4101-8a68-b1bed23abfda", + "interface_key": "rest_api:GET /workstreams/{workstream_id}/dependencies/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T00:25:59.291135Z", + "replacement_ref": "/workplans/{workplan_id}/dependencies/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T00:25:59.291135Z", + "updated_at": "2026-06-04T00:25:59.291135Z" + }, + "all_time": { + "calls": 255865, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 255865 + }, + "users": { + "unknown": 255865 + }, + "components": { + "unknown": 255865 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T21:14:12.747005Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "b1aae931-b51f-4a85-a723-865dd65f6132", + "interface_key": "rest_api:PATCH /workstreams/{workstream_id}", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T06:09:41.901035Z", + "replacement_ref": "/workplans/{workplan_id}", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T06:09:41.901035Z", + "updated_at": "2026-06-04T06:09:41.901035Z" + }, + "all_time": { + "calls": 571, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 571 + }, + "users": { + "unknown": 571 + }, + "components": { + "unknown": 571 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T20:49:12.335710Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "4aea9591-970b-4918-ac4a-4da95a0a1a8c", + "interface_key": "rest_api:POST /decisions/ workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-09T22:56:35.473130Z", + "replacement_ref": "POST /decisions/ with workplan_id", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-09T22:56:35.473130Z", + "updated_at": "2026-07-09T22:56:35.473130Z" + }, + "all_time": { + "calls": 3, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 3 + }, + "users": { + "unknown": 3 + }, + "components": { + "unknown": 3 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-09T22:57:05.912456Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "4a70583c-1e41-4ec1-99a9-728856a7d79c", + "interface_key": "rest_api:POST /progress/ workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-08T23:35:09.148333Z", + "replacement_ref": "POST /progress/ with workplan_id", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-08T23:35:09.148333Z", + "updated_at": "2026-07-08T23:35:09.148333Z" + }, + "all_time": { + "calls": 14, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 14 + }, + "users": { + "unknown": 14 + }, + "components": { + "unknown": 14 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-10T08:34:42.959724Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "6df4e443-7bf1-461a-923b-a6eb52fe7b07", + "interface_key": "rest_api:POST /tasks/ workstream_id", + "interface_kind": "rest_api", + "legacy_since": "2026-07-08T22:38:29.405720Z", + "replacement_ref": "POST /tasks/ with workplan_id", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-07-08T22:38:29.405720Z", + "updated_at": "2026-07-08T22:38:29.405720Z" + }, + "all_time": { + "calls": 5, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 5 + }, + "users": { + "unknown": 5 + }, + "components": { + "state-hub.fix-consistency": 5 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T22:38:29.976918Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "ed6451c9-d2cc-4486-9afe-d3852782cabc", + "interface_key": "rest_api:POST /workstreams/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T07:40:24.168535Z", + "replacement_ref": "/workplans/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T07:40:24.168535Z", + "updated_at": "2026-06-04T07:40:24.168535Z" + }, + "all_time": { + "calls": 824, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 824 + }, + "users": { + "unknown": 824 + }, + "components": { + "unknown": 824 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-08T20:38:05.903144Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + }, + { + "interface": { + "id": "281fd706-b192-4194-8055-2d8733c115f9", + "interface_key": "rest_api:POST /workstreams/{workstream_id}/dependencies/", + "interface_kind": "rest_api", + "legacy_since": "2026-06-04T22:57:37.149207Z", + "replacement_ref": "/workplans/{workplan_id}/dependencies/", + "owner_component": "state-hub.api", + "status": "legacy", + "replacement_verified": true, + "manual_hold": false, + "hold_reason": null, + "notes": null, + "retired_at": null, + "created_at": "2026-06-04T22:57:37.149207Z", + "updated_at": "2026-06-04T22:57:37.149207Z" + }, + "all_time": { + "calls": 4971, + "tenant_count": 1, + "user_count": 1, + "component_count": 1, + "tenants": { + "unknown": 4971 + }, + "users": { + "unknown": 4971 + }, + "components": { + "unknown": 4971 + } + }, + "window": { + "calls": 0, + "tenant_count": 0, + "user_count": 0, + "component_count": 0, + "tenants": {}, + "users": {}, + "components": {} + }, + "last_seen_at": "2026-07-01T22:09:24.229011Z", + "retirement_candidate": true, + "retirement_reason": "no measured usage in review window" + } + ] + }, + "hours": 8 +} From 59770145fd9477b0a7fa8b296f13109b7ef456ab Mon Sep 17 00:00:00 2001 From: tegwick Date: Mon, 20 Jul 2026 02:07:17 +0200 Subject: [PATCH 010/321] C-31 work-record sidetrack detector (CUST-WP-0060-T05) Warn on YAML-block ids matching no kind in the canon work-record type registry (~/the-custodian/canon/standards/work-record-types.yaml, override via WORK_RECORD_REGISTRY). Detection only; registration of non-workplan kinds is a later stage. Skips template placeholders and repos without the registry available. Co-Authored-By: Claude Fable 5 --- scripts/consistency_check.py | 80 ++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/scripts/consistency_check.py b/scripts/consistency_check.py index 84a5183..9ad670f 100644 --- a/scripts/consistency_check.py +++ b/scripts/consistency_check.py @@ -33,6 +33,7 @@ Checks: C-28 inbox-stale-unread WARN No Unread inbox messages older than INBOX_STALE_DAYS C-29 inbox-work-unpromoted WARN No Stale unread message looks like a multi-step work request without a workplan C-30 scope-current-state-stale WARN No SCOPE.md Current State contradicts live workplan statuses + C-31 work-record-unregistered WARN No YAML-block id matches no kind in the canon work-record type registry (sidetrack detector, CUST-WP-0060) Usage: python scripts/consistency_check.py --repo SLUG [--fix] [--no-writeback] [--json] [--api-base URL] @@ -772,6 +773,82 @@ def _infer_slug_from_path(api_base: str, path: str) -> "tuple[str, str] | None": return None +_WORK_RECORD_FENCE_RE = re.compile(r"```(?:yaml|task)\n(.*?)```", re.S) +_WORK_RECORD_ID_LINE_RE = re.compile(r"^id:\s*[\"']?([A-Za-z0-9_-]+)[\"']?\s*$", re.M) +_WORK_RECORD_ID_LIKE_RE = re.compile(r"^[A-Z][A-Z0-9]*(-[A-Z0-9]+)+$") +_WORK_RECORD_SKIP_DIRS = frozenset({".git", "node_modules", ".venv", "history"}) + + +def _load_work_record_patterns() -> list[re.Pattern] | None: + """Load id patterns from the canon work-record type registry. + + Location: $WORK_RECORD_REGISTRY, else ~/the-custodian/canon/standards/ + work-record-types.yaml. Returns None (check skipped) when unavailable. + """ + if not _HAS_YAML: + return None + candidates = [] + env = os.environ.get("WORK_RECORD_REGISTRY") + if env: + candidates.append(Path(env)) + candidates.append( + Path.home() / "the-custodian/canon/standards/work-record-types.yaml") + for path in candidates: + if not path.is_file(): + continue + try: + reg = _yaml.safe_load(path.read_text()) + patterns = [] + for kind in reg.get("kinds", []): + patterns += [re.compile(p) for p in kind.get("id_patterns", [])] + patterns += [re.compile(lp["pattern"]) + for lp in kind.get("legacy_patterns", [])] + return patterns + except Exception: + return None + return None + + +def _check_unregistered_work_records(repo_dir: Path, report: "ConsistencyReport") -> None: + """C-31: warn on YAML-block ids matching no registered work-record kind. + + Detection only (CUST-WP-0060-T05); registration of non-workplan kinds is + a later stage. An unregistered id scheme is a coordination sidetrack that + will need manual reintegration — surface it the day it appears. + """ + patterns = _load_work_record_patterns() + if patterns is None: + return + seen: set[str] = set() + for md in sorted(repo_dir.rglob("*.md")): + if any(part in _WORK_RECORD_SKIP_DIRS for part in md.parts): + continue + try: + text = md.read_text(errors="replace") + except OSError: + continue + for body in _WORK_RECORD_FENCE_RE.findall(text): + for rid in _WORK_RECORD_ID_LINE_RE.findall(body): + if rid in seen or "NNN" in rid or rid.endswith("-TNN"): + continue + if not _WORK_RECORD_ID_LIKE_RE.match(rid): + continue + if any(p.match(rid) for p in patterns): + continue + seen.add(rid) + report.add( + severity="WARN", + check_id="C-31", + message=( + f"[{md.relative_to(repo_dir)}] id '{rid}' matches no " + f"kind in the canon work-record type registry — " + f"unregistered species become sidetracks needing " + f"manual reintegration (work-record-types_v0.1)" + ), + fixable=False, + ) + + def check_repo(api_base: str, repo_slug: str, repo_path_override: str | None = None) -> ConsistencyReport: """Run all consistency checks for a registered repo.""" repo = _api_get(api_base, f"/repos/{repo_slug}", return_error=True) @@ -835,6 +912,9 @@ def check_repo(api_base: str, repo_slug: str, repo_path_override: str | None = N fixable=False, ) + # C-31: work-record sidetrack detector (canon work-record-types registry) + _check_unregistered_work_records(repo_dir, report) + # C-01: workplans/ directory missing if not workplans_dir.is_dir(): report.add( From 4541f1d6fcd48c40900d01b63dde9c2d5cd1949d Mon Sep 17 00:00:00 2001 From: tegwick Date: Mon, 20 Jul 2026 23:09:40 +0200 Subject: [PATCH 011/321] Test coverage for C-31 work-record sidetrack detector (CUST-WP-0060 review) 16 tests: registry loading (env override, missing registry, unparseable registry -> None, no crash), detector behavior against a synthetic registry fixture (independent of the-custodian's live state, so stable across canon changes) -- rogue ids flagged, all registered kinds incl. grandfathered legacy patterns (AWQ-/DEC-/OH-/single-digit-task) pass, template placeholders and .git/history dirs skipped, malformed yaml doesn't crash the scan, dedup of repeated ids within a repo, no-op when no registry is reachable. Full consistency_check + consistency_sweep suite still green (128 tests, no regressions from the C-31 addition). Co-Authored-By: Claude Fable 5 --- tests/test_work_record_check.py | 182 ++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 tests/test_work_record_check.py diff --git a/tests/test_work_record_check.py b/tests/test_work_record_check.py new file mode 100644 index 0000000..9e43c01 --- /dev/null +++ b/tests/test_work_record_check.py @@ -0,0 +1,182 @@ +"""Unit tests for C-31 (work-record sidetrack detector, CUST-WP-0060-T05). + +Covers: + - _load_work_record_patterns — registry discovery + parsing, missing/broken + registry handling + - _check_unregistered_work_records — rogue ids flagged, registered kinds + (incl. grandfathered legacy patterns) pass, template placeholders and + skip-dirs ignored, malformed yaml doesn't crash the scan + +No network calls, no DB, no live API — these tests run fully offline and use +a synthetic registry fixture rather than the live canon checkout, so they +are stable independent of the-custodian's on-disk state. +""" +from __future__ import annotations + +import sys +import textwrap +from pathlib import Path + +import pytest +import yaml + +sys.path.insert(0, str(Path(__file__).parent.parent / "scripts")) + +from consistency_check import ( # noqa: E402 + ConsistencyReport, + _check_unregistered_work_records, + _load_work_record_patterns, +) + +REGISTRY_YAML = textwrap.dedent( + """ + version: "0.1" + status: active + kinds: + - kind: workplan + id_patterns: ['^[A-Z]+-WP-[0-9]{4}$'] + - kind: task + id_patterns: ['^[A-Z]+-WP-[0-9]{4}-T[0-9]{2,3}$'] + legacy_patterns: + - pattern: '^[A-Z]+-WP-[0-9]{4}-T[0-9]$' + source: single-digit legacy + - kind: intake + id_patterns: ['^[A-Z]+-IN-[0-9]{4}$'] + legacy_patterns: + - pattern: '^AWQ-[0-9]{3}$' + source: binky AutopilotWorkQueue + - kind: decision + id_patterns: ['^[A-Z]+-DEC-[0-9]{4}-[0-9]{3}$'] + legacy_patterns: + - pattern: '^DEC-[0-9]{4}-[0-9]{3}$' + source: binky DecisionQueue + - kind: engagement + id_patterns: ['^[A-Z]+-ENG-[0-9]{4}-[0-9]{3}$'] + legacy_patterns: + - pattern: '^OH-[0-9]{4}-[0-9]{3}$' + source: binky OfficeHourQueue + """ +) + + +@pytest.fixture +def registry_file(tmp_path, monkeypatch): + reg = tmp_path / "work-record-types.yaml" + reg.write_text(REGISTRY_YAML, encoding="utf-8") + monkeypatch.setenv("WORK_RECORD_REGISTRY", str(reg)) + return reg + + +class TestLoadWorkRecordPatterns: + def test_loads_patterns_from_env_override(self, registry_file): + patterns = _load_work_record_patterns() + assert patterns is not None + assert any(p.match("AWQ-010") for p in patterns) + assert any(p.match("CUST-WP-0060") for p in patterns) + + def test_returns_none_when_no_registry_available(self, tmp_path, monkeypatch): + monkeypatch.setenv("WORK_RECORD_REGISTRY", str(tmp_path / "missing.yaml")) + monkeypatch.setattr(Path, "home", lambda: tmp_path / "nohome") + assert _load_work_record_patterns() is None + + def test_returns_none_on_unparseable_registry(self, tmp_path, monkeypatch): + bad = tmp_path / "bad.yaml" + bad.write_text("kinds: [unterminated", encoding="utf-8") + monkeypatch.setenv("WORK_RECORD_REGISTRY", str(bad)) + assert _load_work_record_patterns() is None + + +class TestCheckUnregisteredWorkRecords: + def _report(self, repo_dir: Path) -> ConsistencyReport: + report = ConsistencyReport(repo_slug="test", repo_path=str(repo_dir)) + _check_unregistered_work_records(repo_dir, report) + return report + + def test_flags_id_matching_no_registered_kind(self, tmp_path, registry_file): + (tmp_path / "notes.md").write_text( + "```yaml\nid: FOO-QX-001\ntitle: rogue species\n```\n", + encoding="utf-8", + ) + report = self._report(tmp_path) + assert [i.check_id for i in report.issues] == ["C-31"] + assert "FOO-QX-001" in report.issues[0].message + assert report.issues[0].severity == "WARN" + assert report.issues[0].fixable is False + + def test_passes_registered_kind_ids(self, tmp_path, registry_file): + (tmp_path / "notes.md").write_text( + "```yaml\nid: CUST-WP-0060\n```\n" + "```yaml\nid: CUST-WP-0060-T01\n```\n" + "```yaml\nid: BINKY-IN-0001\n```\n" + "```yaml\nid: BINKY-DEC-2026-001\n```\n" + "```yaml\nid: BINKY-ENG-2026-001\n```\n", + encoding="utf-8", + ) + report = self._report(tmp_path) + assert report.issues == [] + + @pytest.mark.parametrize( + "legacy_id", + ["AWQ-010", "DEC-2026-004", "OH-2026-003", "CUST-WP-0060-T1"], + ) + def test_passes_grandfathered_legacy_ids(self, tmp_path, registry_file, legacy_id): + (tmp_path / "queue.md").write_text( + f"```yaml\nid: {legacy_id}\ntitle: legacy item\n```\n", + encoding="utf-8", + ) + report = self._report(tmp_path) + assert report.issues == [], f"{legacy_id} should be grandfathered, got {report.issues}" + + @pytest.mark.parametrize("placeholder", ["BINKY-WP-NNNN", "CUST-WP-0060-TNN"]) + def test_ignores_template_placeholders(self, tmp_path, registry_file, placeholder): + (tmp_path / "AGENTS.md").write_text( + f"```yaml\nid: {placeholder}\n```\n", encoding="utf-8" + ) + report = self._report(tmp_path) + assert report.issues == [] + + def test_ignores_non_id_like_strings(self, tmp_path, registry_file): + (tmp_path / "prose.md").write_text( + "```yaml\nid: not-an-id-scheme\n```\n" + "```yaml\nid: 12345\n```\n", + encoding="utf-8", + ) + report = self._report(tmp_path) + assert report.issues == [] + + def test_deduplicates_repeated_ids_in_same_repo(self, tmp_path, registry_file): + (tmp_path / "a.md").write_text("```yaml\nid: FOO-QX-001\n```\n", encoding="utf-8") + (tmp_path / "b.md").write_text("```yaml\nid: FOO-QX-001\n```\n", encoding="utf-8") + report = self._report(tmp_path) + assert len(report.issues) == 1 + + def test_skips_git_and_history_directories(self, tmp_path, registry_file): + skipped = tmp_path / ".git" / "nested" + skipped.mkdir(parents=True) + (skipped / "notes.md").write_text( + "```yaml\nid: FOO-QX-001\n```\n", encoding="utf-8" + ) + report = self._report(tmp_path) + assert report.issues == [] + + def test_malformed_fenced_block_does_not_crash_scan(self, tmp_path, registry_file): + (tmp_path / "broken.md").write_text( + "```yaml\nid: [unterminated\n```\n" + "```yaml\nid: FOO-QX-001\n```\n", + encoding="utf-8", + ) + report = self._report(tmp_path) + # the malformed block is silently skipped by the id-line regex + # (it never matches `id: ` on its own line); the well-formed + # rogue id in the same file is still caught. + assert [i.check_id for i in report.issues] == ["C-31"] + assert "FOO-QX-001" in report.issues[0].message + + def test_no_op_when_registry_unavailable(self, tmp_path, monkeypatch): + monkeypatch.setenv("WORK_RECORD_REGISTRY", str(tmp_path / "missing.yaml")) + monkeypatch.setattr(Path, "home", lambda: tmp_path / "nohome") + (tmp_path / "notes.md").write_text( + "```yaml\nid: FOO-QX-001\n```\n", encoding="utf-8" + ) + report = self._report(tmp_path) + assert report.issues == [] From 88ba666c95b2a57b50a8bbcf7e3df72ccf167387 Mon Sep 17 00:00:00 2001 From: tegwick Date: Tue, 21 Jul 2026 00:27:45 +0200 Subject: [PATCH 012/321] CUST-WP-0061-T01: intake work-record entity (stage 3) Fresh hub entity per the founder-reviewed decision (not a suggestions rename-bridge): kind: intake per canon/standards/work-record-types_v0.1.md, lifecycle open -> vetted -> routed -> closed(promoted|declined|absorbed). - api/models/base.py::new_uuid7 -- dependency-free RFC 9562 UUIDv7 generator (48-bit ms timestamp, version/variant bits, random remainder); existing tables keep new_uuid (UUIDv4) unchanged, this is opt-in for new work-record entities per the identity-layering canon - api/models/intake.py: Intake + IntakeNote ORM models, mirroring Decision's shape (topic/workplan/repo scope, lane, status, outcome, promoted_to back-link); CHECK constraints enforce scope-required, closed-requires-outcome, promoted-requires-promoted_to at the DB level - migrations/a7c3e9f1b4d2: intakes + intake_notes tables, 3 enum types - api/routers/intake.py: list/create/get/patch + /route + /close + /notes actions, mirroring decisions.py's pattern (409 on invalid transitions, progress event on close) - api/schemas/intake.py: Pydantic create/update/route/close/note schemas - mcp_server/server.py: create_intake, list_intakes, route_intake, close_intake tool wrappers - tests/test_intake.py: 12 tests against the real Postgres test DB (create/list/scope-validation, full lifecycle incl. 409s and the promoted-requires-promoted_to constraint, notes, UUIDv7 verification) Verified live against the running dev API + DB (not just pytest): applied the migration, restarted the MCP server, and ran a full create -> route -> close cycle over the real REST endpoints. No regressions: full existing suite (test_routers_core, test_suggestions, test_mcp_smoke, test_mcp_write_tools, test_mcp_registration, test_consistency_check, test_consistency_sweep) all green. Co-Authored-By: Claude Fable 5 --- api/main.py | 3 +- api/models/__init__.py | 8 + api/models/base.py | 27 +++ api/models/intake.py | 126 +++++++++++++ api/routers/intake.py | 173 ++++++++++++++++++ api/schemas/intake.py | 88 +++++++++ mcp_server/server.py | 108 +++++++++++ .../a7c3e9f1b4d2_intake_work_record_entity.py | 100 ++++++++++ tests/test_intake.py | 157 ++++++++++++++++ 9 files changed, 789 insertions(+), 1 deletion(-) create mode 100644 api/models/intake.py create mode 100644 api/routers/intake.py create mode 100644 api/schemas/intake.py create mode 100644 migrations/versions/a7c3e9f1b4d2_intake_work_record_entity.py create mode 100644 tests/test_intake.py diff --git a/api/main.py b/api/main.py index db63efc..614085c 100644 --- a/api/main.py +++ b/api/main.py @@ -12,7 +12,7 @@ from starlette.responses import Response as StarletteResponse from api.database import engine from api.events import shutdown_publisher from api.services.write_idempotency import WriteIdempotencyMiddleware -from api.routers import decisions, extension_points, progress, state, suggestions, tasks, technical_debt, topics, workstreams, workstream_dependencies +from api.routers import decisions, extension_points, intake, progress, state, suggestions, tasks, technical_debt, topics, workstreams, workstream_dependencies from api.routers import domains, repos, contributions, sbom, policy, domain_goals, repo_goals, messages, capability_requests, tpsc, services from api.routers import token_events from api.routers import interface_changes @@ -114,6 +114,7 @@ app.include_router(workstream_dependencies.router) app.include_router(workstream_dependencies.workplan_router) app.include_router(tasks.router) app.include_router(decisions.router) +app.include_router(intake.router) app.include_router(extension_points.router) app.include_router(technical_debt.router) app.include_router(progress.router) diff --git a/api/models/__init__.py b/api/models/__init__.py index 522c3b0..f4d81ba 100644 --- a/api/models/__init__.py +++ b/api/models/__init__.py @@ -10,6 +10,13 @@ from api.models.workstream import Workstream from api.models.workstream_dependency import WorkstreamDependency from api.models.task import Task, TaskStatus, TaskPriority from api.models.decision import Decision, DecisionType, DecisionStatus +from api.models.intake import ( + Intake, + IntakeNote, + IntakeLane, + IntakeStatus, + IntakeOutcome, +) from api.models.progress_event import ProgressEvent from api.models.extension_point import ExtensionPoint, EPStatus from api.models.technical_debt import TechnicalDebt, TDStatus @@ -54,6 +61,7 @@ __all__ = [ "WorkstreamDependency", "Task", "TaskStatus", "TaskPriority", "Decision", "DecisionType", "DecisionStatus", + "Intake", "IntakeNote", "IntakeLane", "IntakeStatus", "IntakeOutcome", "ProgressEvent", "ExtensionPoint", "EPStatus", "TechnicalDebt", "TDStatus", diff --git a/api/models/base.py b/api/models/base.py index 4dfbd1c..578c4d5 100644 --- a/api/models/base.py +++ b/api/models/base.py @@ -1,3 +1,5 @@ +import os +import time import uuid from datetime import datetime @@ -24,3 +26,28 @@ class TimestampMixin: def new_uuid() -> uuid.UUID: return uuid.uuid4() + + +def new_uuid7() -> uuid.UUID: + """Generate a UUIDv7 (RFC 9562): 48-bit big-endian ms timestamp, version + and variant bits, remaining bits random. Time-sortable, so primary keys + generated with this helper order chronologically without a separate + created_at index lookup — the identity layering canon + (work-record-types_v0.1.md) calls this out as the primary internal key + for new work-record entities. + + Dependency-free (no uuid7 in stdlib before Python 3.14, no third-party + lib added for a ~15-line, non-cryptographic layout). + """ + unix_ts_ms = int(time.time() * 1000) + rand = int.from_bytes(os.urandom(10), "big") + rand_a = (rand >> 62) & 0x0FFF # top 12 bits of the 80 random bits + rand_b = rand & 0x3FFFFFFFFFFFFFFF # bottom 62 bits + value = ( + (unix_ts_ms << 80) + | (0x7 << 76) # version 7 + | (rand_a << 64) + | (0x2 << 62) # variant 10 + | rand_b + ) + return uuid.UUID(int=value) diff --git a/api/models/intake.py b/api/models/intake.py new file mode 100644 index 0000000..717cb7e --- /dev/null +++ b/api/models/intake.py @@ -0,0 +1,126 @@ +import enum +import uuid +from datetime import datetime + +from sqlalchemy import CheckConstraint, DateTime, Enum, ForeignKey, String, Text +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.orm import Mapped, mapped_column, relationship +from sqlalchemy.sql import func + +from api.models.base import Base, TimestampMixin, new_uuid7 + + +class IntakeLane(str, enum.Enum): + green = "green" + blue = "blue" + yellow = "yellow" + orange = "orange" + red = "red" + + +class IntakeStatus(str, enum.Enum): + open = "open" + vetted = "vetted" + routed = "routed" + closed = "closed" + + +class IntakeOutcome(str, enum.Enum): + promoted = "promoted" + declined = "declined" + absorbed = "absorbed" + + +OPEN_INTAKE_STATUSES = (IntakeStatus.open, IntakeStatus.vetted, IntakeStatus.routed) + + +class Intake(Base, TimestampMixin): + """A `kind: intake` work record — a spark: idea, finding, directive, or + request, per canon/standards/work-record-types_v0.1.md. Lifecycle: + open -> vetted -> routed -> closed(promoted|declined|absorbed). + + Fresh entity per the founder-reviewed architecture draft (2026-07-20, + WorkOrchestrationArchitectureDraft.md §8 item 6): not a rename/reuse of + the legacy `suggestions` table. + """ + + __tablename__ = "intakes" + __table_args__ = ( + CheckConstraint( + "topic_id IS NOT NULL OR workplan_id IS NOT NULL OR repo_id IS NOT NULL", + name="ck_intakes_topic_or_workplan_or_repo", + ), + CheckConstraint( + "(status != 'closed') OR (outcome IS NOT NULL)", + name="ck_intakes_closed_requires_outcome", + ), + CheckConstraint( + "(outcome != 'promoted') OR (promoted_to IS NOT NULL)", + name="ck_intakes_promoted_requires_promoted_to", + ), + ) + + id: Mapped[uuid.UUID] = mapped_column( + UUID(as_uuid=True), primary_key=True, default=new_uuid7 + ) + topic_id: Mapped[uuid.UUID | None] = mapped_column( + UUID(as_uuid=True), ForeignKey("topics.id", ondelete="SET NULL"), nullable=True, index=True + ) + workplan_id: Mapped[uuid.UUID | None] = mapped_column( + UUID(as_uuid=True), ForeignKey("workplans.id", ondelete="SET NULL"), nullable=True, index=True + ) + repo_id: Mapped[uuid.UUID | None] = mapped_column( + UUID(as_uuid=True), ForeignKey("managed_repos.id", ondelete="SET NULL"), nullable=True, index=True + ) + title: Mapped[str] = mapped_column(String(500), nullable=False) + description: Mapped[str | None] = mapped_column(Text, nullable=True) + lane: Mapped[IntakeLane] = mapped_column( + Enum(IntakeLane, name="intakelane"), nullable=False, default=IntakeLane.green + ) + status: Mapped[IntakeStatus] = mapped_column( + Enum(IntakeStatus, name="intakestatus"), + nullable=False, + default=IntakeStatus.open, + index=True, + ) + outcome: Mapped[IntakeOutcome | None] = mapped_column( + Enum(IntakeOutcome, name="intakeoutcome"), nullable=True + ) + origin: Mapped[str | None] = mapped_column(String(200), nullable=True) + origin_ref: Mapped[str | None] = mapped_column(String(200), nullable=True, index=True) + promoted_to: Mapped[str | None] = mapped_column(String(200), nullable=True) + source_repo_path: Mapped[str | None] = mapped_column( + Text, nullable=True, doc="Repo-relative path of the source file this record was authored in." + ) + routed_note: Mapped[str | None] = mapped_column(Text, nullable=True) + closed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) + + topic: Mapped["Topic | None"] = relationship("Topic", lazy="selectin") # noqa: F821 + workplan: Mapped["Workplan | None"] = relationship("Workplan", lazy="selectin") # noqa: F821 + repo: Mapped["ManagedRepo | None"] = relationship("ManagedRepo", lazy="selectin") # noqa: F821 + notes: Mapped[list["IntakeNote"]] = relationship( + "IntakeNote", + back_populates="intake", + lazy="selectin", + order_by="IntakeNote.created_at", + cascade="all, delete-orphan", + ) + + +class IntakeNote(Base): + __tablename__ = "intake_notes" + + id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=new_uuid7) + intake_id: Mapped[uuid.UUID] = mapped_column( + UUID(as_uuid=True), + ForeignKey("intakes.id", ondelete="CASCADE"), + nullable=False, + index=True, + ) + author: Mapped[str | None] = mapped_column(String(100), nullable=True) + content: Mapped[str] = mapped_column(Text, nullable=False) + created_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), server_default=func.now(), nullable=False + ) + + intake: Mapped["Intake"] = relationship("Intake", back_populates="notes") diff --git a/api/routers/intake.py b/api/routers/intake.py new file mode 100644 index 0000000..2301205 --- /dev/null +++ b/api/routers/intake.py @@ -0,0 +1,173 @@ +import uuid +from datetime import datetime, timezone + +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from api.database import get_session +from api.models.intake import Intake, IntakeNote, IntakeOutcome, IntakeStatus +from api.models.progress_event import ProgressEvent +from api.schemas.intake import ( + IntakeClose, + IntakeCreate, + IntakeNoteCreate, + IntakeRead, + IntakeRoute, + IntakeUpdate, +) + +router = APIRouter(prefix="/intakes", tags=["intakes"]) + +_ALLOWED_ROUTE_FROM = {IntakeStatus.open, IntakeStatus.vetted} +_ALLOWED_CLOSE_FROM = {IntakeStatus.open, IntakeStatus.vetted, IntakeStatus.routed} + + +def _reject_status(intake: Intake, allowed: set[IntakeStatus], action: str) -> None: + if intake.status not in allowed: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail=( + f"Cannot {action} intake in status '{intake.status.value}'; " + f"allowed from: {sorted(s.value for s in allowed)}" + ), + ) + + +@router.get("/", response_model=list[IntakeRead]) +async def list_intakes( + topic_id: uuid.UUID | None = None, + workplan_id: uuid.UUID | None = None, + repo_id: uuid.UUID | None = None, + status_: IntakeStatus | None = None, + session: AsyncSession = Depends(get_session), +) -> list[Intake]: + q = select(Intake) + if topic_id: + q = q.where(Intake.topic_id == topic_id) + if workplan_id: + q = q.where(Intake.workplan_id == workplan_id) + if repo_id: + q = q.where(Intake.repo_id == repo_id) + if status_: + q = q.where(Intake.status == status_) + q = q.order_by(Intake.created_at) + result = await session.execute(q) + return list(result.scalars().all()) + + +@router.post("/", response_model=IntakeRead, status_code=status.HTTP_201_CREATED) +async def create_intake( + body: IntakeCreate, + session: AsyncSession = Depends(get_session), +) -> Intake: + intake = Intake(**body.model_dump()) + session.add(intake) + await session.commit() + await session.refresh(intake) + return intake + + +@router.get("/{intake_id}", response_model=IntakeRead) +async def get_intake( + intake_id: uuid.UUID, + session: AsyncSession = Depends(get_session), +) -> Intake: + intake = await session.get(Intake, intake_id) + if intake is None: + raise HTTPException(status_code=404, detail="Intake not found") + return intake + + +@router.patch("/{intake_id}", response_model=IntakeRead) +async def update_intake( + intake_id: uuid.UUID, + body: IntakeUpdate, + session: AsyncSession = Depends(get_session), +) -> Intake: + intake = await session.get(Intake, intake_id) + if intake is None: + raise HTTPException(status_code=404, detail="Intake not found") + for field, value in body.model_dump(exclude_unset=True).items(): + setattr(intake, field, value) + await session.commit() + await session.refresh(intake) + return intake + + +@router.post("/{intake_id}/route", response_model=IntakeRead) +async def route_intake( + intake_id: uuid.UUID, + body: IntakeRoute, + session: AsyncSession = Depends(get_session), +) -> Intake: + """Move an intake into `routed` — eligible for the promotion transition.""" + intake = await session.get(Intake, intake_id) + if intake is None: + raise HTTPException(status_code=404, detail="Intake not found") + _reject_status(intake, _ALLOWED_ROUTE_FROM, "route") + + intake.status = IntakeStatus.routed + if body.routed_note: + intake.routed_note = body.routed_note + await session.commit() + await session.refresh(intake) + return intake + + +@router.post("/{intake_id}/close", response_model=IntakeRead) +async def close_intake( + intake_id: uuid.UUID, + body: IntakeClose, + session: AsyncSession = Depends(get_session), +) -> Intake: + """Close an intake with an outcome. `outcome=promoted` requires + `promoted_to` (the canonical id of the record it became) — this is + normally called by the promotion transition (CUST-WP-0061-T03), not by + hand, but a manual close (declined/absorbed, or a promotion recorded + after the fact) is supported directly.""" + intake = await session.get(Intake, intake_id) + if intake is None: + raise HTTPException(status_code=404, detail="Intake not found") + _reject_status(intake, _ALLOWED_CLOSE_FROM, "close") + + intake.status = IntakeStatus.closed + intake.outcome = body.outcome + intake.closed_at = datetime.now(tz=timezone.utc) + if body.promoted_to: + intake.promoted_to = body.promoted_to + await session.commit() + await session.refresh(intake) + + event = ProgressEvent( + topic_id=intake.topic_id, + workplan_id=intake.workplan_id, + event_type="intake_closed", + summary=f"Intake closed ({body.outcome.value}): {intake.title}", + detail={ + "intake_id": str(intake.id), + "outcome": body.outcome.value, + "promoted_to": body.promoted_to, + "note": body.note, + }, + ) + session.add(event) + await session.commit() + + return intake + + +@router.post("/{intake_id}/notes", response_model=IntakeRead, status_code=status.HTTP_201_CREATED) +async def add_intake_note( + intake_id: uuid.UUID, + body: IntakeNoteCreate, + session: AsyncSession = Depends(get_session), +) -> Intake: + intake = await session.get(Intake, intake_id) + if intake is None: + raise HTTPException(status_code=404, detail="Intake not found") + note = IntakeNote(intake_id=intake.id, author=body.author, content=body.content) + session.add(note) + await session.commit() + await session.refresh(intake) + return intake diff --git a/api/schemas/intake.py b/api/schemas/intake.py new file mode 100644 index 0000000..ae1470d --- /dev/null +++ b/api/schemas/intake.py @@ -0,0 +1,88 @@ +import uuid +from datetime import datetime + +from pydantic import BaseModel, ConfigDict, model_validator + +from api.models.intake import IntakeLane, IntakeOutcome, IntakeStatus + + +class IntakeCreate(BaseModel): + topic_id: uuid.UUID | None = None + workplan_id: uuid.UUID | None = None + repo_id: uuid.UUID | None = None + title: str + description: str | None = None + lane: IntakeLane = IntakeLane.green + origin: str | None = None + origin_ref: str | None = None + source_repo_path: str | None = None + + @model_validator(mode="after") + def scope_required(self) -> "IntakeCreate": + if self.topic_id is None and self.workplan_id is None and self.repo_id is None: + raise ValueError("At least one of topic_id, workplan_id, or repo_id must be set") + return self + + +class IntakeUpdate(BaseModel): + title: str | None = None + description: str | None = None + lane: IntakeLane | None = None + status: IntakeStatus | None = None + origin: str | None = None + origin_ref: str | None = None + routed_note: str | None = None + + +class IntakeRoute(BaseModel): + """Move an intake from open/vetted into routed — the state that makes + it eligible for the promotion transition (CUST-WP-0061-T03).""" + + routed_note: str | None = None + + +class IntakeClose(BaseModel): + outcome: IntakeOutcome + promoted_to: str | None = None + note: str | None = None + + @model_validator(mode="after") + def promoted_requires_target(self) -> "IntakeClose": + if self.outcome == IntakeOutcome.promoted and not self.promoted_to: + raise ValueError("outcome=promoted requires promoted_to") + return self + + +class IntakeNoteCreate(BaseModel): + content: str + author: str | None = None + + +class IntakeNoteRead(BaseModel): + model_config = ConfigDict(from_attributes=True) + id: uuid.UUID + author: str | None = None + content: str + created_at: datetime + + +class IntakeRead(BaseModel): + model_config = ConfigDict(from_attributes=True) + id: uuid.UUID + topic_id: uuid.UUID | None = None + workplan_id: uuid.UUID | None = None + repo_id: uuid.UUID | None = None + title: str + description: str | None = None + lane: IntakeLane + status: IntakeStatus + outcome: IntakeOutcome | None = None + origin: str | None = None + origin_ref: str | None = None + promoted_to: str | None = None + source_repo_path: str | None = None + routed_note: str | None = None + closed_at: datetime | None = None + created_at: datetime + updated_at: datetime + notes: list[IntakeNoteRead] = [] diff --git a/mcp_server/server.py b/mcp_server/server.py index e85416a..e95cca1 100644 --- a/mcp_server/server.py +++ b/mcp_server/server.py @@ -1013,6 +1013,114 @@ def resolve_decision( return _json_result(decision) +@mcp.tool() +def create_intake( + title: str, + topic_id: str | None = None, + workplan_id: str | None = None, + repo_id: str | None = None, + description: str | None = None, + lane: str = "green", + origin: str | None = None, + origin_ref: str | None = None, + source_repo_path: str | None = None, +) -> str: + """Record an intake item — a spark: idea, finding, directive, or + request (work-record kind `intake`, canon/standards/ + work-record-types_v0.1.md). Lifecycle: open -> vetted -> routed -> + closed(promoted|declined|absorbed). + + Args: + title: short description of the intake item + topic_id: optional topic UUID (at least one of topic_id/workplan_id/repo_id required) + workplan_id: optional workplan UUID to scope this to + repo_id: optional managed-repo UUID to scope this to + description: optional longer context + lane: autonomy lane — green | blue | yellow | orange | red + origin: free-text source of the finding (e.g. "mail-triage", "founder-directive") + origin_ref: stable reference into the origin (e.g. a mail-log row id) + source_repo_path: repo-relative path of the file this was authored in, if known + """ + intake = _post("/intakes", { + "title": title, + "topic_id": topic_id, + "workplan_id": workplan_id, + "repo_id": repo_id, + "description": description, + "lane": lane, + "origin": origin, + "origin_ref": origin_ref, + "source_repo_path": source_repo_path, + }) + if error := _response_error("create_intake", intake, ("id",)): + return _json_result(error) + return _json_result(intake) + + +@mcp.tool() +def list_intakes( + topic_id: str | None = None, + workplan_id: str | None = None, + repo_id: str | None = None, + status: str | None = None, +) -> str: + """List intake items, optionally filtered by scope and/or status. + + Args: + topic_id: optional topic UUID + workplan_id: optional workplan UUID + repo_id: optional managed-repo UUID + status: open | vetted | routed | closed + """ + return _json_result(_get("/intakes", { + "topic_id": topic_id, + "workplan_id": workplan_id, + "repo_id": repo_id, + "status_": status, + })) + + +@mcp.tool() +def route_intake(intake_id: str, routed_note: str | None = None) -> str: + """Move an intake from open/vetted into routed — the state that makes + it eligible for promotion into a workplan, task, decision, or + engagement. + + Args: + intake_id: UUID of the intake item + routed_note: optional note on where/how it should be routed + """ + result = _post(f"/intakes/{intake_id}/route", {"routed_note": routed_note}) + if error := _response_error("route_intake", result, ("id",)): + return _json_result(error) + return _json_result(result) + + +@mcp.tool() +def close_intake( + intake_id: str, + outcome: str, + promoted_to: str | None = None, + note: str | None = None, +) -> str: + """Close an intake item with an outcome. + + Args: + intake_id: UUID of the intake item + outcome: promoted | declined | absorbed + promoted_to: canonical id of the record it became (required if outcome=promoted) + note: optional closing note + """ + result = _post(f"/intakes/{intake_id}/close", { + "outcome": outcome, + "promoted_to": promoted_to, + "note": note, + }) + if error := _response_error("close_intake", result, ("id",)): + return _json_result(error) + return _json_result(result) + + @mcp.tool() def add_progress_event( summary: str, diff --git a/migrations/versions/a7c3e9f1b4d2_intake_work_record_entity.py b/migrations/versions/a7c3e9f1b4d2_intake_work_record_entity.py new file mode 100644 index 0000000..6d29be0 --- /dev/null +++ b/migrations/versions/a7c3e9f1b4d2_intake_work_record_entity.py @@ -0,0 +1,100 @@ +"""add intakes + intake_notes tables (CUST-WP-0061-T01, work-record stage 3) + +Revision ID: a7c3e9f1b4d2 +Revises: f0a1b2c3d4e5 +Create Date: 2026-07-21 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql +from sqlalchemy.dialects.postgresql import UUID + +revision = "a7c3e9f1b4d2" +down_revision = "f0a1b2c3d4e5" +branch_labels = None +depends_on = None + +intakelane = postgresql.ENUM( + "green", "blue", "yellow", "orange", "red", + name="intakelane", + create_type=False, +) +intakestatus = postgresql.ENUM( + "open", "vetted", "routed", "closed", + name="intakestatus", + create_type=False, +) +intakeoutcome = postgresql.ENUM( + "promoted", "declined", "absorbed", + name="intakeoutcome", + create_type=False, +) + + +def upgrade() -> None: + intakelane.create(op.get_bind(), checkfirst=True) + intakestatus.create(op.get_bind(), checkfirst=True) + intakeoutcome.create(op.get_bind(), checkfirst=True) + + op.create_table( + "intakes", + sa.Column("id", UUID(as_uuid=True), primary_key=True), + sa.Column("topic_id", UUID(as_uuid=True), sa.ForeignKey("topics.id", ondelete="SET NULL"), nullable=True), + sa.Column("workplan_id", UUID(as_uuid=True), sa.ForeignKey("workplans.id", ondelete="SET NULL"), nullable=True), + sa.Column("repo_id", UUID(as_uuid=True), sa.ForeignKey("managed_repos.id", ondelete="SET NULL"), nullable=True), + sa.Column("title", sa.String(length=500), nullable=False), + sa.Column("description", sa.Text(), nullable=True), + sa.Column("lane", intakelane, nullable=False, server_default="green"), + sa.Column("status", intakestatus, nullable=False, server_default="open"), + sa.Column("outcome", intakeoutcome, nullable=True), + sa.Column("origin", sa.String(length=200), nullable=True), + sa.Column("origin_ref", sa.String(length=200), nullable=True), + sa.Column("promoted_to", sa.String(length=200), nullable=True), + sa.Column("source_repo_path", sa.Text(), nullable=True), + sa.Column("routed_note", sa.Text(), nullable=True), + sa.Column("closed_at", sa.DateTime(timezone=True), nullable=True), + sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False), + sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False), + sa.CheckConstraint( + "topic_id IS NOT NULL OR workplan_id IS NOT NULL OR repo_id IS NOT NULL", + name="ck_intakes_topic_or_workplan_or_repo", + ), + sa.CheckConstraint( + "(status != 'closed') OR (outcome IS NOT NULL)", + name="ck_intakes_closed_requires_outcome", + ), + sa.CheckConstraint( + "(outcome != 'promoted') OR (promoted_to IS NOT NULL)", + name="ck_intakes_promoted_requires_promoted_to", + ), + ) + op.create_index("ix_intakes_topic_id", "intakes", ["topic_id"]) + op.create_index("ix_intakes_workplan_id", "intakes", ["workplan_id"]) + op.create_index("ix_intakes_repo_id", "intakes", ["repo_id"]) + op.create_index("ix_intakes_status", "intakes", ["status"]) + op.create_index("ix_intakes_origin_ref", "intakes", ["origin_ref"]) + + op.create_table( + "intake_notes", + sa.Column("id", UUID(as_uuid=True), primary_key=True), + sa.Column("intake_id", UUID(as_uuid=True), sa.ForeignKey("intakes.id", ondelete="CASCADE"), nullable=False), + sa.Column("author", sa.String(length=100), nullable=True), + sa.Column("content", sa.Text(), nullable=False), + sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False), + ) + op.create_index("ix_intake_notes_intake_id", "intake_notes", ["intake_id"]) + + +def downgrade() -> None: + op.drop_index("ix_intake_notes_intake_id", table_name="intake_notes") + op.drop_table("intake_notes") + op.drop_index("ix_intakes_origin_ref", table_name="intakes") + op.drop_index("ix_intakes_status", table_name="intakes") + op.drop_index("ix_intakes_repo_id", table_name="intakes") + op.drop_index("ix_intakes_workplan_id", table_name="intakes") + op.drop_index("ix_intakes_topic_id", table_name="intakes") + op.drop_table("intakes") + intakeoutcome.drop(op.get_bind(), checkfirst=True) + intakestatus.drop(op.get_bind(), checkfirst=True) + intakelane.drop(op.get_bind(), checkfirst=True) diff --git a/tests/test_intake.py b/tests/test_intake.py new file mode 100644 index 0000000..2eafc7a --- /dev/null +++ b/tests/test_intake.py @@ -0,0 +1,157 @@ +"""Tests for the `intake` work-record entity (CUST-WP-0061-T01, work-record +stage 3). Real PostgreSQL test database, no mocking — matches +tests/test_routers_core.py conventions. +""" +from __future__ import annotations + +import pytest + + +async def _create_domain(client, slug="testdomain", name="Test Domain"): + r = await client.post("/domains/", json={"slug": slug, "name": name}) + assert r.status_code == 201, r.text + return r.json() + + +async def _create_topic(client, domain_slug="testdomain", slug="testtopic", title="Test Topic"): + r = await client.post("/topics/", json={ + "slug": slug, "title": title, "domain": domain_slug, + }) + assert r.status_code == 201, r.text + return r.json() + + +async def _create_intake(client, topic_id=None, workplan_id=None, repo_id=None, + title="Qonto MCP mailing", lane="green", **extra): + payload = {"title": title, "lane": lane, **extra} + if topic_id is not None: + payload["topic_id"] = topic_id + if workplan_id is not None: + payload["workplan_id"] = workplan_id + if repo_id is not None: + payload["repo_id"] = repo_id + r = await client.post("/intakes/", json=payload) + assert r.status_code == 201, r.text + return r.json() + + +class TestIntakeCreateAndRead: + async def test_create_requires_a_scope(self, client): + r = await client.post("/intakes/", json={"title": "orphan intake", "lane": "green"}) + assert r.status_code == 422 + + async def test_create_with_topic_scope(self, client): + await _create_domain(client) + topic = await _create_topic(client) + body = await _create_intake(client, topic_id=topic["id"]) + assert body["status"] == "open" + assert body["lane"] == "green" + assert body["outcome"] is None + + async def test_get_unknown_intake_404s(self, client): + r = await client.get("/intakes/00000000-0000-0000-0000-000000000000") + assert r.status_code == 404 + + async def test_list_filters_by_topic(self, client): + await _create_domain(client) + topic = await _create_topic(client) + other_topic = await _create_topic(client, slug="other-topic", title="Other") + await _create_intake(client, topic_id=topic["id"], title="in scope") + await _create_intake(client, topic_id=other_topic["id"], title="out of scope") + + r = await client.get("/intakes/", params={"topic_id": topic["id"]}) + assert r.status_code == 200 + titles = [i["title"] for i in r.json()] + assert titles == ["in scope"] + + +class TestIntakeLifecycle: + async def test_route_moves_open_to_routed(self, client): + await _create_domain(client) + topic = await _create_topic(client) + intake = await _create_intake(client, topic_id=topic["id"]) + + r = await client.post(f"/intakes/{intake['id']}/route", json={"routed_note": "green lane, ready"}) + assert r.status_code == 200 + assert r.json()["status"] == "routed" + assert r.json()["routed_note"] == "green lane, ready" + + async def test_route_from_closed_is_rejected(self, client): + await _create_domain(client) + topic = await _create_topic(client) + intake = await _create_intake(client, topic_id=topic["id"]) + await client.post(f"/intakes/{intake['id']}/close", json={"outcome": "declined"}) + + r = await client.post(f"/intakes/{intake['id']}/route", json={}) + assert r.status_code == 409 + + async def test_close_declined_does_not_require_promoted_to(self, client): + await _create_domain(client) + topic = await _create_topic(client) + intake = await _create_intake(client, topic_id=topic["id"]) + + r = await client.post(f"/intakes/{intake['id']}/close", json={"outcome": "declined"}) + assert r.status_code == 200 + body = r.json() + assert body["status"] == "closed" + assert body["outcome"] == "declined" + assert body["closed_at"] is not None + + async def test_close_promoted_without_promoted_to_is_rejected(self, client): + await _create_domain(client) + topic = await _create_topic(client) + intake = await _create_intake(client, topic_id=topic["id"]) + + r = await client.post(f"/intakes/{intake['id']}/close", json={"outcome": "promoted"}) + assert r.status_code == 422 + + async def test_close_promoted_with_promoted_to_succeeds(self, client): + await _create_domain(client) + topic = await _create_topic(client) + intake = await _create_intake(client, topic_id=topic["id"], title="AWQ-010") + + r = await client.post( + f"/intakes/{intake['id']}/close", + json={"outcome": "promoted", "promoted_to": "BINKY-WP-0005"}, + ) + assert r.status_code == 200 + body = r.json() + assert body["outcome"] == "promoted" + assert body["promoted_to"] == "BINKY-WP-0005" + + async def test_close_already_closed_is_rejected(self, client): + await _create_domain(client) + topic = await _create_topic(client) + intake = await _create_intake(client, topic_id=topic["id"]) + await client.post(f"/intakes/{intake['id']}/close", json={"outcome": "declined"}) + + r = await client.post(f"/intakes/{intake['id']}/close", json={"outcome": "absorbed"}) + assert r.status_code == 409 + + +class TestIntakeNotes: + async def test_add_note_appears_on_intake(self, client): + await _create_domain(client) + topic = await _create_topic(client) + intake = await _create_intake(client, topic_id=topic["id"]) + + r = await client.post( + f"/intakes/{intake['id']}/notes", + json={"content": "vetted, looks real", "author": "agt-rhythm-bridge"}, + ) + assert r.status_code == 201 + notes = r.json()["notes"] + assert len(notes) == 1 + assert notes[0]["content"] == "vetted, looks real" + assert notes[0]["author"] == "agt-rhythm-bridge" + + +class TestIntakeUUIDv7: + async def test_id_is_uuidv7(self, client): + await _create_domain(client) + topic = await _create_topic(client) + intake = await _create_intake(client, topic_id=topic["id"]) + + import uuid + u = uuid.UUID(intake["id"]) + assert u.version == 7 From aade470f4addd3b476cecc1d9d351b752ecb1b95 Mon Sep 17 00:00:00 2001 From: tegwick Date: Tue, 21 Jul 2026 00:48:10 +0200 Subject: [PATCH 013/321] C-32: fix-consistency registration for intake/decision work records (CUST-WP-0061-T02) Extends fix-consistency to scan any file for kind: intake / kind: decision YAML blocks (not just workplans/, per canon: any file is a potential work-record source), create the corresponding hub entity when missing a state_hub_intake_id / state_hub_decision_id, and write the id back into the source block -- same write-back pattern as C-06 for workplans. kind: engagement is reported INFO (deferred, not fixable): no hub entity exists for it yet, a separate stage-3 follow-on. - _load_work_record_kind_registry(): kind-aware registry loader, factored out so C-31's existing flat _load_work_record_patterns() builds on it without duplication (verified: C-31's 16 tests still pass unmodified) - _check_work_record_registration(): detection, wired into check_repo right after C-31 - _inject_yaml_block_field(): write-back helper for fenced blocks, mirroring _inject_task_id_into_block's pattern for blocks - fix_repo C-32 dispatch: creates the intake (scoped to repo_id) or decision (scoped to resolved topic_id, reusing C-06's domain->topic resolution) via the REST API, then writes the id back - tests/test_work_record_registration.py: 15 tests (classification, detection incl. engagement-deferred and workplan-kind-exclusion, injection incl. idempotence and non-interference with sibling blocks) Live-verified end to end against a real registered repo (binky-control, not just synthetic fixtures): a real fix-consistency run found and registered 3 genuinely open, previously-unlinked intake items (AWQ-002/003/006) sitting in AutopilotWorkQueue.md, and correctly deferred 5 real OH- engagement items as INFO. No regressions: full consistency_check + consistency_sweep suite (128 tests) and C-31's own suite (16 tests) still green. Co-Authored-By: Claude Fable 5 --- scripts/consistency_check.py | 210 ++++++++++++++++++++++++- tests/test_work_record_registration.py | 201 +++++++++++++++++++++++ 2 files changed, 405 insertions(+), 6 deletions(-) create mode 100644 tests/test_work_record_registration.py diff --git a/scripts/consistency_check.py b/scripts/consistency_check.py index 9ad670f..b549464 100644 --- a/scripts/consistency_check.py +++ b/scripts/consistency_check.py @@ -34,6 +34,7 @@ Checks: C-29 inbox-work-unpromoted WARN No Stale unread message looks like a multi-step work request without a workplan C-30 scope-current-state-stale WARN No SCOPE.md Current State contradicts live workplan statuses C-31 work-record-unregistered WARN No YAML-block id matches no kind in the canon work-record type registry (sidetrack detector, CUST-WP-0060) + C-32 work-record-not-indexed WARN Yes kind: intake/decision YAML block has no hub id — not indexed in DB (registration, CUST-WP-0061-T02) Usage: python scripts/consistency_check.py --repo SLUG [--fix] [--no-writeback] [--json] [--api-base URL] @@ -779,11 +780,14 @@ _WORK_RECORD_ID_LIKE_RE = re.compile(r"^[A-Z][A-Z0-9]*(-[A-Z0-9]+)+$") _WORK_RECORD_SKIP_DIRS = frozenset({".git", "node_modules", ".venv", "history"}) -def _load_work_record_patterns() -> list[re.Pattern] | None: - """Load id patterns from the canon work-record type registry. +def _load_work_record_kind_registry() -> list[dict] | None: + """Load [{kind, patterns}] from the canon work-record type registry. Location: $WORK_RECORD_REGISTRY, else ~/the-custodian/canon/standards/ - work-record-types.yaml. Returns None (check skipped) when unavailable. + work-record-types.yaml. Returns None (checks skipped) when unavailable. + Kind-aware: shared by C-31 (flat sidetrack detection) and C-32 + (kind-aware registration — needs to know intake vs. decision vs. + engagement to pick the right hub entity). """ if not _HAS_YAML: return None @@ -798,17 +802,28 @@ def _load_work_record_patterns() -> list[re.Pattern] | None: continue try: reg = _yaml.safe_load(path.read_text()) - patterns = [] + kinds = [] for kind in reg.get("kinds", []): - patterns += [re.compile(p) for p in kind.get("id_patterns", [])] + patterns = [re.compile(p) for p in kind.get("id_patterns", [])] patterns += [re.compile(lp["pattern"]) for lp in kind.get("legacy_patterns", [])] - return patterns + kinds.append({"kind": kind["kind"], "patterns": patterns}) + return kinds except Exception: return None return None +def _load_work_record_patterns() -> list[re.Pattern] | None: + """Flat id-pattern list across all kinds — used by C-31's sidetrack + detector, which only needs to know "is this id registered at all", + not which kind it belongs to.""" + kinds = _load_work_record_kind_registry() + if kinds is None: + return None + return [p for k in kinds for p in k["patterns"]] + + def _check_unregistered_work_records(repo_dir: Path, report: "ConsistencyReport") -> None: """C-31: warn on YAML-block ids matching no registered work-record kind. @@ -849,6 +864,115 @@ def _check_unregistered_work_records(repo_dir: Path, report: "ConsistencyReport" ) +_YAML_ONLY_FENCE_RE = re.compile(r"```yaml\n(.*?)\n```", re.S) +_WORK_RECORD_ID_FIELD_BY_KIND = { + "intake": "state_hub_intake_id", + "decision": "state_hub_decision_id", + "engagement": "state_hub_engagement_id", +} +_WORK_RECORD_CREATE_ENDPOINT_BY_KIND = { + "intake": "/intakes", + "decision": "/decisions", +} + + +def _classify_work_record_kind(record_id: str, kind_registry: list[dict]) -> str | None: + for k in kind_registry: + if any(p.match(record_id) for p in k["patterns"]): + return k["kind"] + return None + + +def _inject_yaml_block_field( + file_path: Path, field_name: str, field_value: str, match_id: str +) -> bool: + """Inject a field into the ```yaml``` block whose id == match_id. + Mirrors _inject_task_id_into_block, adapted for fenced yaml blocks + (intake/decision/engagement records live as ```yaml, not ```task).""" + text = file_path.read_text(encoding="utf-8") + + def _replace(m: re.Match) -> str: + block_content = m.group(1) + meta = _parse_yaml_block(block_content.strip()) + if not isinstance(meta, dict) or str(meta.get("id", "")) != match_id: + return m.group(0) + existing_val = meta.get(field_name) + if existing_val is not None and str(existing_val).strip() not in ("", "~", "null", "None", "none"): + return m.group(0) + new_content = re.sub( + rf"^{re.escape(field_name)}:.*$", + f'{field_name}: "{field_value}"', + block_content, + flags=re.MULTILINE, + ) + if new_content == block_content: + new_content = block_content.rstrip() + f"\n{field_name}: \"{field_value}\"" + return f"```yaml\n{new_content}\n```" + + new_text = _YAML_ONLY_FENCE_RE.sub(_replace, text) + if new_text != text: + file_path.write_text(new_text, encoding="utf-8") + return True + return False + + +def _check_work_record_registration(repo_dir: Path, report: "ConsistencyReport") -> None: + """C-32: register kind: intake / kind: decision YAML blocks found + anywhere in the repo against the hub (CUST-WP-0061-T02, work-record + stage 3) — writes state_hub_intake_id / state_hub_decision_id back into + the source block, same write-back pattern as C-06 for workplans. + + kind: engagement has no hub entity yet (deferred; reported INFO, not + fixable) — building one is a separate stage-3 follow-on, not this task. + """ + kind_registry = _load_work_record_kind_registry() + if kind_registry is None: + return + for md in sorted(repo_dir.rglob("*.md")): + if any(part in _WORK_RECORD_SKIP_DIRS for part in md.parts): + continue + try: + text = md.read_text(errors="replace") + except OSError: + continue + for block in _YAML_ONLY_FENCE_RE.findall(text): + meta = _parse_yaml_block(block.strip()) + if not isinstance(meta, dict) or meta.get("_parse_error"): + continue + rid = str(meta.get("id", "")).strip() + if not rid or "NNN" in rid: + continue + kind = _classify_work_record_kind(rid, kind_registry) + if kind not in _WORK_RECORD_ID_FIELD_BY_KIND: + continue + + id_field = _WORK_RECORD_ID_FIELD_BY_KIND[kind] + existing = str(meta.get(id_field, "")).strip().strip('"') + if existing and existing not in ("~", "null", "None", "none"): + continue + + rel = md.relative_to(repo_dir) + if kind not in _WORK_RECORD_CREATE_ENDPOINT_BY_KIND: + report.add( + severity="INFO", check_id="C-32", + message=( + f"[{rel}] engagement '{rid}' has no hub entity yet — " + f"registration deferred (CUST-WP-0061 stage-3 seed)" + ), + fixable=False, + ) + continue + + report.add( + severity="WARN", check_id="C-32", + message=f"[{rel}] {kind} '{rid}' has no {id_field} — not indexed in DB", + file_path=str(rel), + file_value=rid, + fixable=True, + _fix_context={"md_path": md, "kind": kind, "rid": rid, "meta": meta}, + ) + + def check_repo(api_base: str, repo_slug: str, repo_path_override: str | None = None) -> ConsistencyReport: """Run all consistency checks for a registered repo.""" repo = _api_get(api_base, f"/repos/{repo_slug}", return_error=True) @@ -915,6 +1039,9 @@ def check_repo(api_base: str, repo_slug: str, repo_path_override: str | None = N # C-31: work-record sidetrack detector (canon work-record-types registry) _check_unregistered_work_records(repo_dir, report) + # C-32: intake/decision work-record registration (CUST-WP-0061-T02) + _check_work_record_registration(repo_dir, report) + # C-01: workplans/ directory missing if not workplans_dir.is_dir(): report.add( @@ -2588,6 +2715,77 @@ def fix_repo( f" ! task {t_id} not created: {t_data.get('_error', t_data)}" ) + elif issue.check_id == "C-32": + md_path = ctx["md_path"] + kind = ctx["kind"] + rid = ctx["rid"] + meta = ctx["meta"] + id_field = _WORK_RECORD_ID_FIELD_BY_KIND[kind] + title = str(meta.get("title") or rid) + + repo_record = _api_get(api_base, f"/repos/{repo_slug}") + if not repo_record: + report.fixes_applied.append( + f"C-32 SKIP {rid}: could not look up repo '{repo_slug}'" + ) + continue + created = None + if kind == "intake": + payload = { + "title": title, + "repo_id": repo_record.get("id"), + "description": meta.get("description"), + "lane": meta.get("lane", "green"), + "origin": meta.get("origin"), + "origin_ref": meta.get("origin_ref"), + "source_repo_path": str(md_path.relative_to(Path(report.repo_path))), + } + created = _api_post(api_base, "/intakes", payload) + elif kind == "decision": + repo_market_domain = str(repo_record.get("domain_slug") or "").strip() + topic_domain = resolve_topic_domain_slug( + repo_market_domain, repo_market_domain=repo_market_domain or None + ) + topics = _api_get(api_base, "/topics") + topic_id = None + if isinstance(topics, list): + for t in topics: + if t.get("domain_slug") == topic_domain: + topic_id = t["id"] + break + if topic_id is None: + report.fixes_applied.append( + f"C-32 SKIP {rid}: no topic found for domain '{topic_domain}'" + ) + continue + payload = { + "title": title, + "topic_id": topic_id, + "decision_type": meta.get("decision_type", "pending"), + "description": meta.get("description"), + "rationale": meta.get("agent_recommendation") or meta.get("rationale"), + } + created = _api_post(api_base, "/decisions", payload) + + if not created or "_error" in created: + report.fixes_applied.append( + f"C-32 FAIL {rid}: could not create {kind} in DB: " + f"{(created or {}).get('_error', 'no response')}" + ) + continue + + new_id = created["id"] + if _inject_yaml_block_field(md_path, id_field, new_id, rid): + report.fixes_applied.append( + f"C-32 fixed: created {kind} {new_id[:8]}… for {rid}, " + f"wrote {id_field} to {md_path.name}" + ) + else: + report.fixes_applied.append( + f"C-32 WARN {rid}: created {kind} {new_id[:8]}… but " + f"failed to write {id_field} back to {md_path.name}" + ) + elif issue.check_id == "C-09": ws_id = ctx["ws_id"] correct_repo_id = ctx["correct_repo_id"] diff --git a/tests/test_work_record_registration.py b/tests/test_work_record_registration.py new file mode 100644 index 0000000..5a03186 --- /dev/null +++ b/tests/test_work_record_registration.py @@ -0,0 +1,201 @@ +"""Unit tests for C-32 (work-record registration: intake/decision kinds, +CUST-WP-0061-T02) — detection and file write-back only. The live +create-in-DB path is covered by the fix_repo integration path, proven +manually against a real repo/API (see CUST-WP-0061 progress notes); these +tests stay offline and use a synthetic registry fixture, matching the +conventions in test_work_record_check.py. +""" +from __future__ import annotations + +import sys +import textwrap +from pathlib import Path + +import pytest +import yaml + +sys.path.insert(0, str(Path(__file__).parent.parent / "scripts")) + +from consistency_check import ( # noqa: E402 + ConsistencyReport, + _check_work_record_registration, + _classify_work_record_kind, + _inject_yaml_block_field, + _load_work_record_kind_registry, +) + +REGISTRY_YAML = textwrap.dedent( + """ + version: "0.1" + kinds: + - kind: intake + id_patterns: ['^[A-Z]+-IN-[0-9]{4}$'] + legacy_patterns: + - pattern: '^AWQ-[0-9]{3}$' + source: binky AutopilotWorkQueue + - kind: decision + id_patterns: ['^[A-Z]+-DEC-[0-9]{4}-[0-9]{3}$'] + legacy_patterns: + - pattern: '^DEC-[0-9]{4}-[0-9]{3}$' + source: binky DecisionQueue + - kind: engagement + id_patterns: ['^[A-Z]+-ENG-[0-9]{4}-[0-9]{3}$'] + legacy_patterns: + - pattern: '^OH-[0-9]{4}-[0-9]{3}$' + source: binky OfficeHourQueue + - kind: workplan + id_patterns: ['^[A-Z]+-WP-[0-9]{4}$'] + """ +) + + +@pytest.fixture +def registry_file(tmp_path, monkeypatch): + reg = tmp_path / "work-record-types.yaml" + reg.write_text(REGISTRY_YAML, encoding="utf-8") + monkeypatch.setenv("WORK_RECORD_REGISTRY", str(reg)) + return reg + + +class TestClassifyWorkRecordKind: + def test_classifies_intake(self, registry_file): + kinds = _load_work_record_kind_registry() + assert _classify_work_record_kind("BINKY-IN-0001", kinds) == "intake" + + def test_classifies_legacy_awq(self, registry_file): + kinds = _load_work_record_kind_registry() + assert _classify_work_record_kind("AWQ-010", kinds) == "intake" + + def test_classifies_decision(self, registry_file): + kinds = _load_work_record_kind_registry() + assert _classify_work_record_kind("DEC-2026-004", kinds) == "decision" + + def test_unregistered_returns_none(self, registry_file): + kinds = _load_work_record_kind_registry() + assert _classify_work_record_kind("FOO-QX-001", kinds) is None + + +class TestCheckWorkRecordRegistration: + def _report(self, repo_dir: Path) -> ConsistencyReport: + report = ConsistencyReport(repo_slug="test", repo_path=str(repo_dir)) + _check_work_record_registration(repo_dir, report) + return report + + def test_intake_without_hub_id_is_flagged_fixable(self, tmp_path, registry_file): + (tmp_path / "queue.md").write_text( + "```yaml\nid: BINKY-IN-0001\ntitle: needs registration\n```\n", + encoding="utf-8", + ) + report = self._report(tmp_path) + assert len(report.issues) == 1 + issue = report.issues[0] + assert issue.check_id == "C-32" + assert issue.fixable is True + assert issue._fix_context["kind"] == "intake" + assert issue._fix_context["rid"] == "BINKY-IN-0001" + + def test_intake_with_hub_id_is_skipped(self, tmp_path, registry_file): + (tmp_path / "queue.md").write_text( + '```yaml\nid: BINKY-IN-0001\ntitle: already linked\n' + 'state_hub_intake_id: "019f8000-0000-7000-8000-000000000000"\n```\n', + encoding="utf-8", + ) + report = self._report(tmp_path) + assert report.issues == [] + + def test_decision_without_hub_id_is_flagged_fixable(self, tmp_path, registry_file): + (tmp_path / "DecisionQueue.md").write_text( + "```yaml\nid: DEC-2026-005\ntitle: a new decision\n```\n", + encoding="utf-8", + ) + report = self._report(tmp_path) + assert len(report.issues) == 1 + assert report.issues[0]._fix_context["kind"] == "decision" + + def test_engagement_without_hub_id_is_info_not_fixable(self, tmp_path, registry_file): + (tmp_path / "OfficeHourQueue.md").write_text( + "```yaml\nid: OH-2026-001\ntitle: bank call\n```\n", + encoding="utf-8", + ) + report = self._report(tmp_path) + assert len(report.issues) == 1 + assert report.issues[0].severity == "INFO" + assert report.issues[0].fixable is False + + def test_workplan_kind_is_untouched_by_c32(self, tmp_path, registry_file): + """Workplans keep their own C-06 registration path; C-32 must not + double-register them.""" + (tmp_path / "workplans" / "CUST-WP-0099-x.md").parent.mkdir(parents=True) + (tmp_path / "workplans" / "CUST-WP-0099-x.md").write_text( + "---\nid: CUST-WP-0099\n---\n```yaml\nid: CUST-WP-0099\ntitle: x\n```\n", + encoding="utf-8", + ) + report = self._report(tmp_path) + assert report.issues == [] + + def test_template_placeholder_ignored(self, tmp_path, registry_file): + (tmp_path / "AGENTS.md").write_text( + "```yaml\nid: BINKY-IN-NNNN\ntitle: template\n```\n", encoding="utf-8" + ) + report = self._report(tmp_path) + assert report.issues == [] + + def test_no_op_when_registry_unavailable(self, tmp_path, monkeypatch): + monkeypatch.setenv("WORK_RECORD_REGISTRY", str(tmp_path / "missing.yaml")) + monkeypatch.setattr(Path, "home", lambda: tmp_path / "nohome") + (tmp_path / "queue.md").write_text( + "```yaml\nid: BINKY-IN-0001\ntitle: x\n```\n", encoding="utf-8" + ) + report = self._report(tmp_path) + assert report.issues == [] + + +class TestInjectYamlBlockField: + def test_injects_field_into_matching_block(self, tmp_path): + md = tmp_path / "queue.md" + md.write_text( + "```yaml\nid: BINKY-IN-0001\ntitle: needs registration\n```\n", + encoding="utf-8", + ) + changed = _inject_yaml_block_field( + md, "state_hub_intake_id", "019f8000-0000-7000-8000-000000000000", "BINKY-IN-0001" + ) + assert changed is True + text = md.read_text() + assert 'state_hub_intake_id: "019f8000-0000-7000-8000-000000000000"' in text + # round-trips as valid yaml + block = text.split("```yaml\n", 1)[1].rsplit("```", 1)[0] + parsed = yaml.safe_load(block) + assert parsed["state_hub_intake_id"] == "019f8000-0000-7000-8000-000000000000" + + def test_does_not_touch_other_blocks(self, tmp_path): + md = tmp_path / "queue.md" + md.write_text( + "```yaml\nid: BINKY-IN-0001\ntitle: first\n```\n" + "```yaml\nid: BINKY-IN-0002\ntitle: second\n```\n", + encoding="utf-8", + ) + _inject_yaml_block_field(md, "state_hub_intake_id", "abc-123", "BINKY-IN-0001") + text = md.read_text() + assert "abc-123" in text + second_block = text.split("BINKY-IN-0002")[1] + assert "state_hub_intake_id" not in second_block + + def test_no_op_when_id_not_found(self, tmp_path): + md = tmp_path / "queue.md" + original = "```yaml\nid: BINKY-IN-0001\ntitle: x\n```\n" + md.write_text(original, encoding="utf-8") + changed = _inject_yaml_block_field(md, "state_hub_intake_id", "abc-123", "BINKY-IN-9999") + assert changed is False + assert md.read_text() == original + + def test_idempotent_when_field_already_set(self, tmp_path): + md = tmp_path / "queue.md" + md.write_text( + '```yaml\nid: BINKY-IN-0001\nstate_hub_intake_id: "already-there"\n```\n', + encoding="utf-8", + ) + changed = _inject_yaml_block_field(md, "state_hub_intake_id", "new-value", "BINKY-IN-0001") + assert changed is False + assert "already-there" in md.read_text() + assert "new-value" not in md.read_text() From 3dbbc753bc721011e38bd62adba46aa106a0f58a Mon Sep 17 00:00:00 2001 From: tegwick Date: Tue, 21 Jul 2026 01:06:01 +0200 Subject: [PATCH 014/321] CUST-WP-0061-T03: the promotion transition (statehub promote-intake) The mechanism named in canon/standards/work-record-types_v0.1.md: "Promotion is a first-class transition... manual transcription of an intake item into other kinds is a process defect." This is what AWQ-010 needed and didn't have -- a human/agent had to notice, transcribe, and re-register it by hand. One call now does what that manual pass did. scripts/promote_intake.py: intake.routed -> workplan | task | decision | engagement. - workplan: new ADR-001 file at workplans/{ID}-{slug}.md, registered against the hub (repo+topic resolution, POST /workplans, frontmatter id write-back) - task: appended as a ```task``` block to an existing --workplan-file, registered via POST /tasks, reuses the existing _inject_task_id_into_block writeback helper - decision: appended as a ```yaml``` block with a fresh {PREFIX}-DEC-{YYYY}-{NNN} id to --target-file, registered the same way C-32 registers decisions (reuses _inject_yaml_block_field) - engagement: appended as a ```yaml``` block with a fresh {PREFIX}-ENG-{YYYY}-{NNN} id -- file-only, no hub entity exists yet (same honest deferral as C-32), reported not silently skipped In every case the intake is closed with outcome=promoted and promoted_to=; the new record carries an origin: "intake:" back-link. Wired as `statehub promote-intake --to --repo-slug --repo-path --domain [--target-file ...] [--workplan-file ...]`, matching the CLI shape named in the workplan text. 17 tests: pure helpers (_slugify, _next_number, _append_yaml_block, frontmatter injection) offline; full promote_intake() flow with the hub API mocked. Live-verified against the real running API/DB and a real repo (binky-control), not just mocks -- and the live proof caught a real bug: the first workplan-promotion run silently produced a false success (the intake was closed outcome=promoted, but /workplans/ actually 422'd on a missing repo_id that the code never resolved, so no workstream was ever created). Fixed to resolve repo_id via /repos/{slug} and to raise loudly on registration failure instead of writing a half-registered file silently; locked in as two regression tests. Re-verified clean: workplan promotion (CLI direct + through `statehub promote-intake` itself) and decision promotion both proven end-to-end against the live hub, with all scratch artefacts (files + hub rows) cleaned up afterward. No regressions: full state-hub suite (271 tests across test_promote_intake, test_intake, test_work_record_registration, test_work_record_check, test_routers_core, test_consistency_check, test_consistency_sweep, test_mcp_smoke, test_mcp_write_tools) green. Co-Authored-By: Claude Fable 5 --- custodian_cli.py | 40 ++++ scripts/promote_intake.py | 416 +++++++++++++++++++++++++++++++++++ tests/test_promote_intake.py | 238 ++++++++++++++++++++ 3 files changed, 694 insertions(+) create mode 100644 scripts/promote_intake.py create mode 100644 tests/test_promote_intake.py diff --git a/custodian_cli.py b/custodian_cli.py index f0638b9..be404bb 100644 --- a/custodian_cli.py +++ b/custodian_cli.py @@ -422,6 +422,30 @@ def cmd_fix_consistency(args: argparse.Namespace) -> None: sys.exit(exit_code) +def cmd_promote_intake(args: argparse.Namespace) -> None: + """Promote a routed intake into a workplan, task, decision, or engagement.""" + script = STATE_HUB_DIR / "scripts" / "promote_intake.py" + if not script.exists(): + print(f"ERROR: promote_intake.py not found at {script}") + sys.exit(1) + + cmd = [ + sys.executable, str(script), args.intake_id, + "--to", args.to, + "--repo-path", str(Path(args.repo_path).expanduser().resolve()), + "--repo-slug", args.repo_slug, + "--domain", args.domain, + "--api-base", args.api_base, + ] + if args.target_file: + cmd.extend(["--target-file", args.target_file]) + if args.workplan_file: + cmd.extend(["--workplan-file", args.workplan_file]) + + result = subprocess.run(cmd) + sys.exit(result.returncode) + + def cmd_create_workstream(args: argparse.Namespace) -> None: """Create a workstream under a domain's topic.""" _api_get("/state/health") @@ -684,6 +708,20 @@ def main() -> None: help="Preserve checker exit code 2 for warnings-only runs", ) + # promote-intake + promote = sub.add_parser( + "promote-intake", + help="Promote a routed intake into a workplan, task, decision, or engagement (CUST-WP-0061-T03)", + ) + promote.add_argument("intake_id", help="UUID of the routed intake") + promote.add_argument("--to", required=True, choices=["workplan", "task", "decision", "engagement"]) + promote.add_argument("--repo-path", default=os.getcwd(), help="Local checkout of the target repo") + promote.add_argument("--repo-slug", required=True, help="Registered repo slug") + promote.add_argument("--domain", required=True, help="Market domain slug, e.g. infotech") + promote.add_argument("--target-file", default=None, help="Repo-relative path (required for decision/engagement)") + promote.add_argument("--workplan-file", default=None, help="Repo-relative workplan file path (required for task)") + promote.add_argument("--api-base", default=API_BASE, help="State Hub API base URL") + # create-workstream cws = sub.add_parser("create-workstream", help="Create a workstream under a domain topic") cws.add_argument("--domain", required=True, help="Domain slug to create the workstream under") @@ -767,6 +805,8 @@ def main() -> None: cmd_ingest_sbom(args) elif args.command == "fix-consistency": cmd_fix_consistency(args) + elif args.command == "promote-intake": + cmd_promote_intake(args) elif args.command == "create-workstream": cmd_create_workstream(args) elif args.command == "create-task": diff --git a/scripts/promote_intake.py b/scripts/promote_intake.py new file mode 100644 index 0000000..b691c8e --- /dev/null +++ b/scripts/promote_intake.py @@ -0,0 +1,416 @@ +#!/usr/bin/env python3 +"""Promote a routed intake work record into a workplan, task, decision, or +engagement — CUST-WP-0061-T03, the promotion transition named in +canon/standards/work-record-types_v0.1.md: + + "Promotion is a first-class transition: intake.routed -> workplan | + task | decision | engagement, executed by one CLI/MCP call that writes + both artefacts, sets promoted_to/origin back-links, and re-syncs. + Manual transcription of an intake item into other kinds is a process + defect." + +This is the mechanism AWQ-010 needed and didn't have: a human/agent had to +notice, transcribe, and re-register it by hand (binky-control, 2026-07-19). +One call does what that manual pass did. + +Kinds: + workplan — new ADR-001 file at workplans/{ID}-{slug}.md, registered + against the hub the same way C-06 registers workplans. + task — appended as a ```task``` block to an existing workplan + file (--workplan-file), registered via POST /tasks. + decision — appended as a ```yaml``` block with a fresh {PREFIX}-DEC- + {YYYY}-{NNN} id to --target-file, registered the same way + C-32 registers decisions. + engagement — appended as a ```yaml``` block with a fresh {PREFIX}-ENG- + {YYYY}-{NNN} id to --target-file. File-only: no hub entity + exists for engagements yet (CUST-WP-0061 stage-3 seed) — + this is reported, not silently skipped. + +In every case: the intake is closed with outcome=promoted and +promoted_to=; the new record's file carries an +`origin: intake:` back-link. +""" +from __future__ import annotations + +import argparse +import re +import sys +from datetime import date, datetime, timezone +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent)) + +from consistency_check import ( # noqa: E402 + _api_get, + _api_patch, + _api_post, + _inject_task_id_into_block, + _inject_yaml_block_field, + infer_wp_prefix, + resolve_topic_domain_slug, +) + +_SKIP_DIRS = frozenset({".git", "node_modules", ".venv", "history", "agents_backup"}) + + +class PromotionError(Exception): + pass + + +def _slugify(title: str) -> str: + slug = re.sub(r"[^a-z0-9]+", "-", title.lower()).strip("-") + return slug[:60].rstrip("-") or "untitled" + + +def _next_number(repo_dir: Path, id_regex: re.Pattern, *, width: int = 4) -> int: + """Scan every .md file for ids matching id_regex (capture group 1 = the + numeric part); return the next free sequence number.""" + max_n = 0 + for md in repo_dir.rglob("*.md"): + if any(part in _SKIP_DIRS for part in md.parts): + continue + try: + text = md.read_text(errors="replace") + except OSError: + continue + for m in id_regex.finditer(text): + try: + max_n = max(max_n, int(m.group(1))) + except (ValueError, IndexError): + continue + return max_n + 1 + + +def _fetch_intake(api_base: str, intake_id: str) -> dict: + intake = _api_get(api_base, f"/intakes/{intake_id}") + if not intake or (isinstance(intake, dict) and "_error" in intake): + raise PromotionError(f"could not fetch intake {intake_id}: {intake}") + if intake.get("status") != "routed": + raise PromotionError( + f"intake {intake_id} is '{intake.get('status')}', not 'routed' — " + f"route it first (POST /intakes/{intake_id}/route)" + ) + return intake + + +def _close_intake(api_base: str, intake_id: str, canonical_id: str, to_kind: str) -> dict: + result = _api_post(api_base, f"/intakes/{intake_id}/close", { + "outcome": "promoted", + "promoted_to": canonical_id, + "note": f"promoted to {to_kind} {canonical_id}", + }) + if not result or (isinstance(result, dict) and "_error" in result): + raise PromotionError(f"created {canonical_id} but failed to close intake: {result}") + return result + + +def _origin_line(intake_id: str) -> str: + return f'origin: "intake:{intake_id}"' + + +# --------------------------------------------------------------------------- +# workplan +# --------------------------------------------------------------------------- + +def _promote_to_workplan( + api_base: str, repo_dir: Path, repo_slug: str, domain: str, intake: dict, +) -> tuple[str, Path]: + prefix = infer_wp_prefix(repo_dir, repo_slug) + id_re = re.compile(rf"\b{re.escape(prefix)}-(\d+)\b") + number = _next_number(repo_dir, id_re) + new_id = f"{prefix}-{str(number).zfill(4)}" + slug = _slugify(intake["title"]) + workplans_dir = repo_dir / "workplans" + workplans_dir.mkdir(exist_ok=True) + new_file = workplans_dir / f"{new_id}-{slug}.md" + if new_file.exists(): + raise PromotionError(f"target file already exists: {new_file}") + + today = date.today().isoformat() + body = intake.get("description") or intake["title"] + content = ( + f"---\n" + f'id: {new_id}\n' + f"type: workplan\n" + f'title: "{intake["title"]}"\n' + f"domain: {domain}\n" + f"repo: {repo_slug}\n" + f"status: proposed\n" + f"owner: codex\n" + f'created: "{today}"\n' + f'updated: "{today}"\n' + f"---\n\n" + f'# {intake["title"]}\n\n' + f"{body}\n\n" + f"**Promoted from intake** `{intake['id']}` — {_origin_line(intake['id'])}\n" + ) + new_file.write_text(content, encoding="utf-8") + + topics = _api_get(api_base, "/topics") + topic_domain = resolve_topic_domain_slug(domain, repo_market_domain=domain or None) + topic_id = None + if isinstance(topics, list): + for t in topics: + if t.get("domain_slug") == topic_domain: + topic_id = t["id"] + break + if topic_id is None: + raise PromotionError( + f"wrote {new_file.name} but no topic found for domain '{topic_domain}' " + f"— could not register the workstream; run fix-consistency to finish registration" + ) + + repo_record = _api_get(api_base, f"/repos/{repo_slug}") + if not repo_record or (isinstance(repo_record, dict) and "_error" in repo_record): + raise PromotionError( + f"wrote {new_file.name} but could not look up repo '{repo_slug}' " + f"({repo_record}) — could not register the workstream; " + f"run fix-consistency to finish registration" + ) + + ws = _api_post(api_base, "/workplans", { + "topic_id": topic_id, + "repo_id": repo_record["id"], + "title": intake["title"], + "slug": f"{repo_slug}-{slug}", + "status": "active", + "owner": "codex", + }) + if not ws or (isinstance(ws, dict) and "_error" in ws): + raise PromotionError( + f"wrote {new_file.name} but workstream registration failed: {ws} " + f"— run fix-consistency to finish registration (C-06 will pick it up)" + ) + _inject_field_in_frontmatter(new_file, "state_hub_workstream_id", ws["id"]) + + return new_id, new_file + + +def _inject_field_in_frontmatter(file_path: Path, field: str, value: str) -> None: + text = file_path.read_text(encoding="utf-8") + parts = text.split("---", 2) + if len(parts) < 3: + return + parts[1] = parts[1].rstrip() + f'\n{field}: "{value}"\n' + file_path.write_text("---".join(parts), encoding="utf-8") + + +# --------------------------------------------------------------------------- +# task +# --------------------------------------------------------------------------- + +def _promote_to_task(api_base: str, workplan_file: Path, intake: dict) -> str: + if not workplan_file.is_file(): + raise PromotionError(f"--workplan-file not found: {workplan_file}") + text = workplan_file.read_text(encoding="utf-8") + wp_id_match = re.search(r"^id:\s*(\S+)", text, re.MULTILINE) + if not wp_id_match: + raise PromotionError(f"could not find workplan id in {workplan_file}") + wp_id = wp_id_match.group(1).strip().strip('"') + ws_id_match = re.search(r'^state_hub_workstream_id:\s*"?([\w-]+)"?', text, re.MULTILINE) + if not ws_id_match: + raise PromotionError( + f"{workplan_file} has no state_hub_workstream_id — run fix-consistency on it first" + ) + ws_id = ws_id_match.group(1) + + existing_t_re = re.compile(rf"{re.escape(wp_id)}-T(\d+)") + number = _next_number(workplan_file.parent.parent, existing_t_re) + t_id = f"{wp_id}-T{str(number).zfill(2)}" + + body = intake.get("description") or intake["title"] + section = ( + f'\n## Task: {intake["title"]}\n\n' + f"{body}\n\n" + f"Promoted from intake `{intake['id']}`.\n\n" + f"```task\n" + f"id: {t_id}\n" + f"status: todo\n" + f"priority: medium\n" + f"```\n" + ) + workplan_file.write_text(text.rstrip() + "\n" + section, encoding="utf-8") + + task = _api_post(api_base, "/tasks", { + "workplan_id": ws_id, + "title": intake["title"], + "description": intake.get("description"), + "status": "todo", + "priority": "medium", + }) + if task and "_error" not in task: + _inject_task_id_into_block(workplan_file, "state_hub_task_id", task["id"], t_id) + + return t_id + + +# --------------------------------------------------------------------------- +# decision / engagement — share the "yaml block appended to a target file" +# shape; decision has a hub entity (C-32-style registration), engagement +# does not yet. +# --------------------------------------------------------------------------- + +def _next_dec_or_eng_id(repo_dir: Path, prefix: str, kind_infix: str) -> str: + year = date.today().year + id_re = re.compile(rf"\b{re.escape(prefix)}-{kind_infix}-{year}-(\d+)\b") + number = _next_number(repo_dir, id_re, width=3) + return f"{prefix}-{kind_infix}-{year}-{str(number).zfill(3)}" + + +def _promote_to_decision( + api_base: str, repo_dir: Path, repo_slug: str, domain: str, + target_file: Path, intake: dict, +) -> str: + prefix = infer_wp_prefix(repo_dir, repo_slug).replace("-WP", "") + new_id = _next_dec_or_eng_id(repo_dir, prefix, "DEC") + _append_yaml_block(target_file, { + "id": new_id, + "title": intake["title"], + "status": "prepared", + "lane": intake.get("lane", "yellow"), + "agent_recommendation": intake.get("description") or "(fill in before founder review)", + "fallback_if_no_response": "no degradation — remains pending", + "created_at": date.today().isoformat(), + }, origin_intake_id=intake["id"]) + + topics = _api_get(api_base, "/topics") + topic_domain = resolve_topic_domain_slug(domain, repo_market_domain=domain or None) + topic_id = None + if isinstance(topics, list): + for t in topics: + if t.get("domain_slug") == topic_domain: + topic_id = t["id"] + break + if topic_id is None: + raise PromotionError( + f"wrote {new_id} to {target_file.name} but no topic found for domain " + f"'{topic_domain}' — run fix-consistency to finish registration" + ) + + decision = _api_post(api_base, "/decisions", { + "title": intake["title"], + "topic_id": topic_id, + "decision_type": "pending", + "description": intake.get("description"), + }) + if decision and "_error" not in decision: + _inject_yaml_block_field(target_file, "state_hub_decision_id", decision["id"], new_id) + + return new_id + + +def _promote_to_engagement(repo_dir: Path, repo_slug: str, target_file: Path, intake: dict) -> str: + prefix = infer_wp_prefix(repo_dir, repo_slug).replace("-WP", "") + new_id = _next_dec_or_eng_id(repo_dir, prefix, "ENG") + _append_yaml_block(target_file, { + "id": new_id, + "title": intake["title"], + "status": "queued", + "counterparty": "(fill in)", + "prepared_material": [], + "deadline_pressure": "none", + }, origin_intake_id=intake["id"]) + return new_id + + +def _append_yaml_block(target_file: Path, fields: dict, *, origin_intake_id: str) -> None: + lines = ["```yaml"] + for k, v in fields.items(): + if isinstance(v, str): + lines.append(f'{k}: "{v}"') + elif isinstance(v, list): + lines.append(f"{k}: {v!r}") + else: + lines.append(f"{k}: {v}") + lines.append(f'origin: "intake:{origin_intake_id}"') + lines.append("```") + block = "\n".join(lines) + "\n" + + if target_file.exists(): + existing = target_file.read_text(encoding="utf-8") + target_file.write_text(existing.rstrip() + "\n\n" + block, encoding="utf-8") + else: + target_file.parent.mkdir(parents=True, exist_ok=True) + target_file.write_text(block, encoding="utf-8") + + +# --------------------------------------------------------------------------- +# entry point +# --------------------------------------------------------------------------- + +def promote_intake( + api_base: str, + intake_id: str, + to_kind: str, + repo_dir: Path, + repo_slug: str, + domain: str, + *, + target_file: str | None = None, + workplan_file: str | None = None, +) -> dict: + intake = _fetch_intake(api_base, intake_id) + + if to_kind == "workplan": + canonical_id, new_file = _promote_to_workplan(api_base, repo_dir, repo_slug, domain, intake) + location = str(new_file.relative_to(repo_dir)) + elif to_kind == "task": + if not workplan_file: + raise PromotionError("--to task requires --workplan-file") + wp_path = repo_dir / workplan_file + canonical_id = _promote_to_task(api_base, wp_path, intake) + location = workplan_file + elif to_kind == "decision": + if not target_file: + raise PromotionError("--to decision requires --target-file") + canonical_id = _promote_to_decision( + api_base, repo_dir, repo_slug, domain, repo_dir / target_file, intake + ) + location = target_file + elif to_kind == "engagement": + if not target_file: + raise PromotionError("--to engagement requires --target-file") + canonical_id = _promote_to_engagement(repo_dir, repo_slug, repo_dir / target_file, intake) + location = target_file + else: + raise PromotionError(f"unsupported --to kind: {to_kind!r} (workplan|task|decision|engagement)") + + _close_intake(api_base, intake_id, canonical_id, to_kind) + + return { + "intake_id": intake_id, + "to_kind": to_kind, + "canonical_id": canonical_id, + "location": location, + } + + +def main() -> None: + ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("intake_id") + ap.add_argument("--to", required=True, choices=["workplan", "task", "decision", "engagement"]) + ap.add_argument("--repo-path", default=".", help="local checkout path of the target repo") + ap.add_argument("--repo-slug", required=True) + ap.add_argument("--domain", required=True, help="market domain slug, e.g. infotech") + ap.add_argument("--target-file", default=None, help="repo-relative path (decision/engagement)") + ap.add_argument("--workplan-file", default=None, help="repo-relative workplan file path (task)") + ap.add_argument("--api-base", default="http://127.0.0.1:8000") + args = ap.parse_args() + + try: + result = promote_intake( + args.api_base, args.intake_id, args.to, + Path(args.repo_path).resolve(), args.repo_slug, args.domain, + target_file=args.target_file, workplan_file=args.workplan_file, + ) + except PromotionError as e: + print(f"ERROR: {e}", file=sys.stderr) + sys.exit(1) + + print(f"Promoted intake {result['intake_id']} -> {result['to_kind']} {result['canonical_id']}") + print(f" file: {result['location']}") + print(" Review, commit, and push the change — promotion writes files but does not commit.") + + +if __name__ == "__main__": + main() diff --git a/tests/test_promote_intake.py b/tests/test_promote_intake.py new file mode 100644 index 0000000..59fe2f1 --- /dev/null +++ b/tests/test_promote_intake.py @@ -0,0 +1,238 @@ +"""Tests for the promotion transition (CUST-WP-0061-T03, +scripts/promote_intake.py): intake.routed -> workplan | task | decision | +engagement. + +Pure file-manipulation logic (_slugify, _next_number, _append_yaml_block, +_inject_field_in_frontmatter) is tested offline. The full promote_intake() +flow is tested with the hub API layer mocked (monkeypatched _api_get/ +_api_post) so these run without a live server; the real end-to-end proof +(actual DB writes) is documented in CUST-WP-0061's progress notes, mirroring +the live-verification discipline used for T01/T02. +""" +from __future__ import annotations + +import sys +from pathlib import Path + +import pytest + +sys.path.insert(0, str(Path(__file__).parent.parent / "scripts")) + +import promote_intake as pi # noqa: E402 + + +class TestSlugify: + def test_lowercases_and_hyphenates(self): + assert pi._slugify("Qonto MCP integration: bank account") == "qonto-mcp-integration-bank-account" + + def test_truncates_long_titles(self): + long_title = "a " * 100 + assert len(pi._slugify(long_title)) <= 60 + + def test_empty_title_gets_placeholder(self): + assert pi._slugify("!!!") == "untitled" + + +class TestNextNumber: + def test_finds_max_and_increments(self, tmp_path): + (tmp_path / "a.md").write_text("id: CUST-WP-0005\nid: CUST-WP-0012\n") + (tmp_path / "b.md").write_text("id: CUST-WP-0003\n") + import re + n = pi._next_number(tmp_path, re.compile(r"CUST-WP-(\d+)")) + assert n == 13 + + def test_empty_repo_starts_at_one(self, tmp_path): + import re + n = pi._next_number(tmp_path, re.compile(r"CUST-WP-(\d+)")) + assert n == 1 + + def test_skips_skip_dirs(self, tmp_path): + import re + skipped = tmp_path / ".git" + skipped.mkdir() + (skipped / "a.md").write_text("id: CUST-WP-9999\n") + n = pi._next_number(tmp_path, re.compile(r"CUST-WP-(\d+)")) + assert n == 1 + + +class TestAppendYamlBlock: + def test_creates_new_file(self, tmp_path): + target = tmp_path / "DecisionQueue.md" + pi._append_yaml_block(target, {"id": "X-DEC-2026-001", "title": "test"}, origin_intake_id="abc-123") + text = target.read_text() + assert "id: \"X-DEC-2026-001\"" in text + assert 'origin: "intake:abc-123"' in text + + def test_appends_to_existing_file(self, tmp_path): + target = tmp_path / "DecisionQueue.md" + target.write_text("# Decision Queue\n\nSome existing content.\n") + pi._append_yaml_block(target, {"id": "X-DEC-2026-002", "title": "second"}, origin_intake_id="def-456") + text = target.read_text() + assert "Some existing content." in text + assert "X-DEC-2026-002" in text + + +class TestInjectFieldInFrontmatter: + def test_injects_field_into_frontmatter(self, tmp_path): + f = tmp_path / "wp.md" + f.write_text("---\nid: CUST-WP-0099\ntitle: x\n---\n\nBody.\n") + pi._inject_field_in_frontmatter(f, "state_hub_workstream_id", "019f0000-0000-7000-8000-000000000000") + text = f.read_text() + assert 'state_hub_workstream_id: "019f0000-0000-7000-8000-000000000000"' in text + assert "Body." in text + + +class TestPromoteIntakeMocked: + """Full promote_intake() flow with the hub API mocked.""" + + def _fake_routed_intake(self, **overrides): + base = { + "id": "019f0000-0000-7000-8000-000000000001", + "title": "Qonto MCP mailing — actionable", + "status": "routed", + "lane": "green", + "description": "found in mail triage", + } + base.update(overrides) + return base + + def test_rejects_intake_not_routed(self, tmp_path, monkeypatch): + monkeypatch.setattr(pi, "_api_get", lambda *a, **k: self._fake_routed_intake(status="open")) + with pytest.raises(pi.PromotionError, match="not 'routed'"): + pi.promote_intake("http://x", "019f0000", "workplan", tmp_path, "testrepo", "infotech") + + def test_promote_to_workplan_writes_file_and_closes_intake(self, tmp_path, monkeypatch): + calls = [] + + def fake_get(api_base, path, *a, **k): + if path.startswith("/intakes/"): + return self._fake_routed_intake() + if path == "/topics": + return [{"id": "019ftopic", "domain_slug": "infotech"}] + if path == "/repos/testrepo": + return {"id": "019frepo", "slug": "testrepo"} + return None + + def fake_post(api_base, path, body): + calls.append((path, body)) + if path == "/workplans": + return {"id": "019fworkplan", "title": body["title"]} + if path.startswith("/intakes/") and path.endswith("/close"): + return {"id": "019f0000", "status": "closed", "outcome": "promoted"} + return {"id": "019fgeneric"} + + monkeypatch.setattr(pi, "_api_get", fake_get) + monkeypatch.setattr(pi, "_api_post", fake_post) + + result = pi.promote_intake( + "http://x", "019f0000-0000-7000-8000-000000000001", "workplan", + tmp_path, "testrepo", "infotech", + ) + + assert result["to_kind"] == "workplan" + new_file = tmp_path / result["location"] + assert new_file.is_file() + text = new_file.read_text() + assert "Qonto MCP mailing" in text + assert 'state_hub_workstream_id: "019fworkplan"' in text + assert "intake:019f0000-0000-7000-8000-000000000001" in text + + close_calls = [c for c in calls if c[0].endswith("/close")] + assert len(close_calls) == 1 + assert close_calls[0][1]["outcome"] == "promoted" + assert close_calls[0][1]["promoted_to"] == result["canonical_id"] + + def test_promote_to_workplan_raises_loudly_when_repo_lookup_fails(self, tmp_path, monkeypatch): + """Regression: the live proof against binky-control (2026-07-21) found + /workplans/ requires repo_id, which promote_to_workplan originally + never resolved -- the POST 422'd and the failure was silently + swallowed (file written, no state_hub_workstream_id, no error). + Must raise, not write a half-registered file silently.""" + def fake_get(api_base, path, *a, **k): + if path.startswith("/intakes/"): + return self._fake_routed_intake() + if path == "/topics": + return [{"id": "019ftopic", "domain_slug": "infotech"}] + if path == "/repos/testrepo": + return {"_error": "404: not found"} + return None + + monkeypatch.setattr(pi, "_api_get", fake_get) + monkeypatch.setattr(pi, "_api_post", lambda *a, **k: {"id": "x"}) + + with pytest.raises(pi.PromotionError, match="could not look up repo"): + pi.promote_intake("http://x", "019f0000", "workplan", tmp_path, "testrepo", "infotech") + + def test_promote_to_workplan_raises_loudly_when_workstream_post_fails(self, tmp_path, monkeypatch): + def fake_get(api_base, path, *a, **k): + if path.startswith("/intakes/"): + return self._fake_routed_intake() + if path == "/topics": + return [{"id": "019ftopic", "domain_slug": "infotech"}] + if path == "/repos/testrepo": + return {"id": "019frepo"} + return None + + def fake_post(api_base, path, body): + if path == "/workplans": + return {"_error": "422: repo_id required"} + return {"id": "x"} + + monkeypatch.setattr(pi, "_api_get", fake_get) + monkeypatch.setattr(pi, "_api_post", fake_post) + + with pytest.raises(pi.PromotionError, match="workstream registration failed"): + pi.promote_intake("http://x", "019f0000", "workplan", tmp_path, "testrepo", "infotech") + + def test_promote_to_workplan_refuses_existing_file(self, tmp_path, monkeypatch): + monkeypatch.setattr(pi, "_api_get", lambda *a, **k: self._fake_routed_intake()) + wp_dir = tmp_path / "workplans" + wp_dir.mkdir() + # pre-create the file the promotion would try to write + prefix = pi.infer_wp_prefix(tmp_path, "testrepo") + (wp_dir / f"{prefix}-0001-qonto-mcp-mailing-actionable.md").write_text("already here") + + def fake_post(api_base, path, body): + return {"id": "x"} + + monkeypatch.setattr(pi, "_api_post", fake_post) + with pytest.raises(pi.PromotionError, match="already exists"): + pi.promote_intake("http://x", "019f0000", "workplan", tmp_path, "testrepo", "infotech") + + def test_promote_to_decision_requires_target_file(self, tmp_path, monkeypatch): + monkeypatch.setattr(pi, "_api_get", lambda *a, **k: self._fake_routed_intake()) + with pytest.raises(pi.PromotionError, match="--target-file"): + pi.promote_intake("http://x", "019f0000", "decision", tmp_path, "testrepo", "infotech") + + def test_promote_to_task_requires_workplan_file(self, tmp_path, monkeypatch): + monkeypatch.setattr(pi, "_api_get", lambda *a, **k: self._fake_routed_intake()) + with pytest.raises(pi.PromotionError, match="--workplan-file"): + pi.promote_intake("http://x", "019f0000", "task", tmp_path, "testrepo", "infotech") + + def test_promote_to_engagement_is_file_only_no_hub_call(self, tmp_path, monkeypatch): + post_calls = [] + + def fake_get(api_base, path, *a, **k): + return self._fake_routed_intake() + + def fake_post(api_base, path, body): + post_calls.append(path) + if path.startswith("/intakes/") and path.endswith("/close"): + return {"id": "x", "status": "closed"} + return {"id": "x"} + + monkeypatch.setattr(pi, "_api_get", fake_get) + monkeypatch.setattr(pi, "_api_post", fake_post) + + result = pi.promote_intake( + "http://x", "019f0000", "engagement", tmp_path, "testrepo", "infotech", + target_file="OfficeHourQueue.md", + ) + assert result["to_kind"] == "engagement" + target = tmp_path / "OfficeHourQueue.md" + assert target.is_file() + assert "queued" in target.read_text() + # only the intake-close call should have hit the API — no + # engagement-creation endpoint exists yet + non_close_calls = [c for c in post_calls if not c.endswith("/close")] + assert non_close_calls == [] From b564ac7046e30be99803da49f0e6300746e3836c Mon Sep 17 00:00:00 2001 From: tegwick Date: Tue, 21 Jul 2026 01:25:26 +0200 Subject: [PATCH 015/321] C-33: generated per-repo work-record index (CUST-WP-0061-T04) First cut of the transclusion-style index named in the stage-3 seed: a WORK-RECORDS.md at repo root listing kind/id/status/lane/source for every workplan, task, intake, decision, and engagement found in the repo -- derived purely from files (no hub query), matching the ADR-001 rebuild principle. Full markitect transclusion rendering is a follow-on, not this first cut. - _generate_work_record_index(): reuses iter_workplan_files/ parse_frontmatter/get_tasks_from_workplan for workplan+task rows, and the repo-wide yaml-block scan already proven by C-31/C-32 for intake/decision/engagement rows. Archived workplans excluded (index is for current orientation, not history); closed decisions/intakes/ engagements stay listed. Returns None (no file written) for repos with zero work records, to avoid clutter. - _check_work_record_index_freshness(): C-33, WARN+fixable when WORK-RECORDS.md is missing or its content differs from a fresh regeneration. - fix_repo C-33 dispatch: overwrites the file with the regenerated content. 13 new tests (generation across all kinds, sort order, archived exclusion, self-scan exclusion, freshness detection incl. idempotence). No regressions: full repo suite green (561 tests). Live-verified at two scales: binky-control (5 workplans, 31 tasks, 3 intake, 4 decisions, 5 engagements) and the-custodian (52 workplans, 332 tasks) -- both generated correctly on first run, confirmed idempotent (identical second run produces no C-33 fix), and the larger repo's scan added no material overhead to the existing ~1.5min fix-consistency run. Co-Authored-By: Claude Fable 5 --- scripts/consistency_check.py | 120 ++++++++++++++++++++ tests/test_work_record_index.py | 187 ++++++++++++++++++++++++++++++++ 2 files changed, 307 insertions(+) create mode 100644 tests/test_work_record_index.py diff --git a/scripts/consistency_check.py b/scripts/consistency_check.py index b549464..1e74b82 100644 --- a/scripts/consistency_check.py +++ b/scripts/consistency_check.py @@ -35,6 +35,7 @@ Checks: C-30 scope-current-state-stale WARN No SCOPE.md Current State contradicts live workplan statuses C-31 work-record-unregistered WARN No YAML-block id matches no kind in the canon work-record type registry (sidetrack detector, CUST-WP-0060) C-32 work-record-not-indexed WARN Yes kind: intake/decision YAML block has no hub id — not indexed in DB (registration, CUST-WP-0061-T02) + C-33 work-record-index-stale WARN Yes WORK-RECORDS.md missing or stale — generated per-repo index (CUST-WP-0061-T04) Usage: python scripts/consistency_check.py --repo SLUG [--fix] [--no-writeback] [--json] [--api-base URL] @@ -973,6 +974,116 @@ def _check_work_record_registration(repo_dir: Path, report: "ConsistencyReport") ) +_WORK_RECORD_INDEX_NAME = "WORK-RECORDS.md" +_WORK_RECORD_INDEX_KIND_ORDER = { + "workplan": 0, "task": 1, "intake": 2, "decision": 3, "engagement": 4, +} + + +def _generate_work_record_index(repo_dir: Path, repo_slug: str) -> str | None: + """CUST-WP-0061-T04: the generated per-repo work-record index — every + registered work record (workplan, task, intake, decision, engagement) + with kind/id/status/lane/source, derivable purely from repo files (no + hub query — matches the ADR-001 rebuild principle). Full transclusion + rendering (markitect integration) is a follow-on; this is the flat + first cut. Returns None (no file written) when the repo has no work + records at all, to avoid clutter in small repos. + """ + rows: list[tuple[str, str, str, str, str]] = [] # kind, id, status, lane, source + + workplans_dir = repo_dir / "workplans" + if workplans_dir.is_dir(): + for wp_file in iter_workplan_files(workplans_dir, include_archived=False): + try: + text = wp_file.read_text(encoding="utf-8", errors="replace") + except OSError: + continue + meta, body = parse_frontmatter(text) + wp_id = str(meta.get("id", "")).strip() + if not wp_id: + continue + source = str(wp_file.relative_to(repo_dir)) + rows.append(("workplan", wp_id, str(meta.get("status", "")).strip() or "—", "—", source)) + for task in get_tasks_from_workplan(meta, body): + if task.get("_parse_error"): + continue + t_id = str(task.get("id", "")).strip() + if not t_id: + continue + rows.append(("task", t_id, str(task.get("status", "")).strip() or "—", "—", source)) + + kind_registry = _load_work_record_kind_registry() + if kind_registry: + for md in sorted(repo_dir.rglob("*.md")): + if any(part in _WORK_RECORD_SKIP_DIRS for part in md.parts): + continue + if md.name == _WORK_RECORD_INDEX_NAME: + continue + try: + text = md.read_text(errors="replace") + except OSError: + continue + for block in _YAML_ONLY_FENCE_RE.findall(text): + meta = _parse_yaml_block(block.strip()) + if not isinstance(meta, dict) or meta.get("_parse_error"): + continue + rid = str(meta.get("id", "")).strip() + if not rid or "NNN" in rid: + continue + kind = _classify_work_record_kind(rid, kind_registry) + if kind not in ("intake", "decision", "engagement"): + continue + status = str(meta.get("status", "")).strip() or "—" + lane = str(meta.get("lane", "")).strip() or "—" + source = str(md.relative_to(repo_dir)) + rows.append((kind, rid, status, lane, source)) + + if not rows: + return None + + rows.sort(key=lambda r: (_WORK_RECORD_INDEX_KIND_ORDER.get(r[0], 99), r[4], r[1])) + + lines = [ + f"# Work Records — {repo_slug}", + "", + "> Generated by `statehub fix-consistency` (CUST-WP-0061-T04, work-record", + "> stage 3). Do not edit by hand — edit the source file/block listed for", + "> each record and re-run fix-consistency to refresh this index. Archived", + "> workplans are omitted; closed decisions/intakes/engagements stay listed", + "> so recently-resolved work is still visible. [auto]", + "", + "| Kind | ID | Status | Lane | Source |", + "| --- | --- | --- | --- | --- |", + ] + lines.extend(f"| {kind} | {rid} | {status} | {lane} | {source} |" for kind, rid, status, lane, source in rows) + lines.append("") + return "\n".join(lines) + + +def _check_work_record_index_freshness( + repo_dir: Path, repo_slug: str, report: "ConsistencyReport" +) -> None: + """C-33: WORK-RECORDS.md missing or stale relative to the current + file-derived work-record set.""" + content = _generate_work_record_index(repo_dir, repo_slug) + if content is None: + return + index_file = repo_dir / _WORK_RECORD_INDEX_NAME + existing = index_file.read_text(encoding="utf-8") if index_file.is_file() else None + if existing == content: + return + report.add( + severity="WARN", check_id="C-33", + message=( + f"{_WORK_RECORD_INDEX_NAME} is missing or stale — regenerate via " + f"fix-consistency (CUST-WP-0061-T04 generated work-record index)" + ), + file_path=_WORK_RECORD_INDEX_NAME, + fixable=True, + _fix_context={"index_file": index_file, "content": content}, + ) + + def check_repo(api_base: str, repo_slug: str, repo_path_override: str | None = None) -> ConsistencyReport: """Run all consistency checks for a registered repo.""" repo = _api_get(api_base, f"/repos/{repo_slug}", return_error=True) @@ -1042,6 +1153,9 @@ def check_repo(api_base: str, repo_slug: str, repo_path_override: str | None = N # C-32: intake/decision work-record registration (CUST-WP-0061-T02) _check_work_record_registration(repo_dir, report) + # C-33: generated per-repo work-record index (CUST-WP-0061-T04) + _check_work_record_index_freshness(repo_dir, repo_slug, report) + # C-01: workplans/ directory missing if not workplans_dir.is_dir(): report.add( @@ -2786,6 +2900,12 @@ def fix_repo( f"failed to write {id_field} back to {md_path.name}" ) + elif issue.check_id == "C-33": + ctx["index_file"].write_text(ctx["content"], encoding="utf-8") + report.fixes_applied.append( + f"C-33 fixed: regenerated {ctx['index_file'].name}" + ) + elif issue.check_id == "C-09": ws_id = ctx["ws_id"] correct_repo_id = ctx["correct_repo_id"] diff --git a/tests/test_work_record_index.py b/tests/test_work_record_index.py new file mode 100644 index 0000000..2f1febf --- /dev/null +++ b/tests/test_work_record_index.py @@ -0,0 +1,187 @@ +"""Tests for C-33 (generated per-repo work-record index, CUST-WP-0061-T04): +_generate_work_record_index() and _check_work_record_index_freshness(). +Offline, synthetic registry fixture — same conventions as +test_work_record_check.py / test_work_record_registration.py. +""" +from __future__ import annotations + +import sys +import textwrap +from pathlib import Path + +import pytest + +sys.path.insert(0, str(Path(__file__).parent.parent / "scripts")) + +from consistency_check import ( # noqa: E402 + ConsistencyReport, + _check_work_record_index_freshness, + _generate_work_record_index, +) + +REGISTRY_YAML = textwrap.dedent( + """ + version: "0.1" + kinds: + - kind: workplan + id_patterns: ['^[A-Z]+-WP-[0-9]{4}$'] + - kind: task + id_patterns: ['^[A-Z]+-WP-[0-9]{4}-T[0-9]{2,3}$'] + - kind: intake + id_patterns: ['^[A-Z]+-IN-[0-9]{4}$'] + legacy_patterns: + - pattern: '^AWQ-[0-9]{3}$' + source: binky AutopilotWorkQueue + - kind: decision + id_patterns: ['^[A-Z]+-DEC-[0-9]{4}-[0-9]{3}$'] + - kind: engagement + id_patterns: ['^[A-Z]+-ENG-[0-9]{4}-[0-9]{3}$'] + """ +) + + +@pytest.fixture +def registry_file(tmp_path, monkeypatch): + reg = tmp_path / "work-record-types.yaml" + reg.write_text(REGISTRY_YAML, encoding="utf-8") + monkeypatch.setenv("WORK_RECORD_REGISTRY", str(reg)) + return reg + + +class TestGenerateWorkRecordIndex: + def test_empty_repo_returns_none(self, tmp_path, registry_file): + assert _generate_work_record_index(tmp_path, "testrepo") is None + + def test_includes_workplan_and_tasks(self, tmp_path, registry_file): + wp_dir = tmp_path / "workplans" + wp_dir.mkdir() + (wp_dir / "CUST-WP-0001-demo.md").write_text( + textwrap.dedent( + """\ + --- + id: CUST-WP-0001 + status: active + --- + ```task + id: CUST-WP-0001-T01 + status: done + priority: high + ``` + """ + ), + encoding="utf-8", + ) + content = _generate_work_record_index(tmp_path, "testrepo") + assert content is not None + assert "| workplan | CUST-WP-0001 | active | — | workplans/CUST-WP-0001-demo.md |" in content + assert "| task | CUST-WP-0001-T01 | done | — |" in content + + def test_excludes_archived_workplans(self, tmp_path, registry_file): + wp_dir = tmp_path / "workplans" / "archived" + wp_dir.mkdir(parents=True) + (wp_dir / "260101-CUST-WP-0000-old.md").write_text( + "---\nid: CUST-WP-0000\nstatus: finished\n---\n", encoding="utf-8" + ) + assert _generate_work_record_index(tmp_path, "testrepo") is None + + def test_includes_intake_decision_engagement(self, tmp_path, registry_file): + (tmp_path / "queue.md").write_text( + textwrap.dedent( + """ + ```yaml + id: AWQ-010 + title: qonto mcp + status: open + lane: green + ``` + ```yaml + id: BINKY-DEC-2026-004 + title: qonto approval + status: resolved + lane: red + ``` + ```yaml + id: BINKY-ENG-2026-001 + title: bank call + status: queued + ``` + """ + ), + encoding="utf-8", + ) + content = _generate_work_record_index(tmp_path, "testrepo") + assert "| intake | AWQ-010 | open | green | queue.md |" in content + assert "| decision | BINKY-DEC-2026-004 | resolved | red | queue.md |" in content + assert "| engagement | BINKY-ENG-2026-001 | queued | — | queue.md |" in content + + def test_ignores_unregistered_ids(self, tmp_path, registry_file): + (tmp_path / "queue.md").write_text( + "```yaml\nid: FOO-QX-001\nstatus: open\n```\n", encoding="utf-8" + ) + assert _generate_work_record_index(tmp_path, "testrepo") is None + + def test_ignores_template_placeholders(self, tmp_path, registry_file): + (tmp_path / "AGENTS.md").write_text( + "```yaml\nid: BINKY-IN-NNNN\nstatus: open\n```\n", encoding="utf-8" + ) + assert _generate_work_record_index(tmp_path, "testrepo") is None + + def test_does_not_scan_its_own_output_file(self, tmp_path, registry_file): + (tmp_path / "WORK-RECORDS.md").write_text( + "| intake | AWQ-999 | open | green | ghost.md |\n", encoding="utf-8" + ) + assert _generate_work_record_index(tmp_path, "testrepo") is None + + def test_header_and_sort_order(self, tmp_path, registry_file): + (tmp_path / "b.md").write_text("```yaml\nid: AWQ-002\nstatus: open\n```\n", encoding="utf-8") + (tmp_path / "a.md").write_text("```yaml\nid: AWQ-001\nstatus: open\n```\n", encoding="utf-8") + content = _generate_work_record_index(tmp_path, "testrepo") + assert content.startswith("# Work Records — testrepo") + assert "[auto]" in content + # sorted by source path first (a.md before b.md) + assert content.index("AWQ-001") < content.index("AWQ-002") + + +class TestCheckWorkRecordIndexFreshness: + def _report(self, repo_dir: Path, repo_slug: str = "testrepo") -> ConsistencyReport: + report = ConsistencyReport(repo_slug=repo_slug, repo_path=str(repo_dir)) + _check_work_record_index_freshness(repo_dir, repo_slug, report) + return report + + def test_no_issue_when_no_records(self, tmp_path, registry_file): + report = self._report(tmp_path) + assert report.issues == [] + + def test_flags_missing_index(self, tmp_path, registry_file): + (tmp_path / "queue.md").write_text("```yaml\nid: AWQ-001\nstatus: open\n```\n", encoding="utf-8") + report = self._report(tmp_path) + assert len(report.issues) == 1 + assert report.issues[0].check_id == "C-33" + assert report.issues[0].fixable is True + + def test_no_issue_when_index_already_current(self, tmp_path, registry_file): + (tmp_path / "queue.md").write_text("```yaml\nid: AWQ-001\nstatus: open\n```\n", encoding="utf-8") + content = _generate_work_record_index(tmp_path, "testrepo") + (tmp_path / "WORK-RECORDS.md").write_text(content, encoding="utf-8") + report = self._report(tmp_path) + assert report.issues == [] + + def test_flags_stale_index_after_new_record_added(self, tmp_path, registry_file): + (tmp_path / "queue.md").write_text("```yaml\nid: AWQ-001\nstatus: open\n```\n", encoding="utf-8") + content = _generate_work_record_index(tmp_path, "testrepo") + (tmp_path / "WORK-RECORDS.md").write_text(content, encoding="utf-8") + # a new record appears + (tmp_path / "queue.md").write_text( + "```yaml\nid: AWQ-001\nstatus: open\n```\n```yaml\nid: AWQ-002\nstatus: open\n```\n", + encoding="utf-8", + ) + report = self._report(tmp_path) + assert len(report.issues) == 1 + assert report.issues[0].check_id == "C-33" + + def test_fix_context_carries_correct_content(self, tmp_path, registry_file): + (tmp_path / "queue.md").write_text("```yaml\nid: AWQ-001\nstatus: open\n```\n", encoding="utf-8") + report = self._report(tmp_path) + ctx = report.issues[0]._fix_context + assert ctx["index_file"] == tmp_path / "WORK-RECORDS.md" + assert "AWQ-001" in ctx["content"] From 6c4fc64ef3dd539cbaa38e38fddabbcfeb90c57f Mon Sep 17 00:00:00 2001 From: tegwick Date: Tue, 21 Jul 2026 01:51:59 +0200 Subject: [PATCH 016/321] CUST-WP-0061-T06: retire suggestions to read-only legacy Founder-reviewed decision (WorkOrchestrationArchitectureDraft.md v0.2 section 8 item 6): the fresh intake work-record entity replaces suggestions, not a rename-bridge. All 5 mutation endpoints (create, vet, decline, promote, bump-relevance) now 410 with a pointer to POST /intakes/ and the migration doc; GET/list stay live for the historical record (10 rows migrated to file-backed intake records in the-custodian, see that repo's intake-legacy-suggestions-migration.md and CUST-IN-0001 through CUST-IN-0010). Removed dead code the retirement makes unreachable: Task/TaskPriority/ TaskStatus/normalize_task_status imports (only used by the deleted promote body), the suggestion_relevance.bump_relevance import, and the _ALLOWED_*_FROM stage-guard sets + _reject_stage helper (only used by the deleted vet/decline/promote bodies). WSJF ranking (compute_wsjf, cost_of_delay, suggestion_sort_key) stays -- still exercised by the surviving GET /suggestions/?rank=wsjf read path. MCP tool docstrings (create_suggestion, vet_suggestion, decline_suggestion, promote_suggestion_to_task, bump_suggestion_relevance) updated to point at the replacement (create_intake/route_intake/close_intake) rather than silently 410ing with no guidance. tests/test_suggestions.py rewritten: verifies all 5 mutations 410, GET/list still work for historical rows (seeded directly via the DB session since creation is retired -- there's no other way to get historical data into the table anymore), 404 still behaves normally on unknown ids. Live-verified against the running dev API: POST 410s, GET with include_terminal=true still returns all 10 migrated-and-declined historical rows. No regressions: full repo suite green (563 tests). Co-Authored-By: Claude Fable 5 --- api/routers/suggestions.py | 140 +++++----------------- mcp_server/server.py | 23 +++- tests/test_suggestions.py | 230 ++++++++++++++----------------------- 3 files changed, 136 insertions(+), 257 deletions(-) diff --git a/api/routers/suggestions.py b/api/routers/suggestions.py index b04c044..b8b3f54 100644 --- a/api/routers/suggestions.py +++ b/api/routers/suggestions.py @@ -12,7 +12,6 @@ from api.models.suggestion import ( SuggestionNote, SuggestionStage, ) -from api.models.task import Task, TaskPriority, TaskStatus from api.schemas.suggestion import ( SuggestionBumpRelevance, SuggestionCreate, @@ -22,16 +21,25 @@ from api.schemas.suggestion import ( SuggestionRead, SuggestionVet, ) -from api.services.suggestion_relevance import bump_relevance from api.services.suggestion_wsjf import compute_wsjf, cost_of_delay, suggestion_sort_key -from api.task_status import normalize_task_status router = APIRouter(prefix="/suggestions", tags=["suggestions"]) -_ALLOWED_VET_FROM = {SuggestionStage.suggestion} -_ALLOWED_DECLINE_FROM = {SuggestionStage.suggestion, SuggestionStage.requirement} -_ALLOWED_PROMOTE_FROM = {SuggestionStage.requirement} +def _retired() -> HTTPException: + """CUST-WP-0061-T06 (2026-07-21): suggestions are read-only legacy. + Reads (GET) stay live for the historical record; every mutation is + retired. Use the intake work-record entity instead + (canon/standards/work-record-types_v0.1.md).""" + return HTTPException( + status_code=410, + detail=( + "suggestions are retired (read-only legacy, CUST-WP-0061-T06). " + "Use POST /intakes/ instead — see canon/standards/" + "work-record-types_v0.1.md and the-custodian/" + "intake-legacy-suggestions-migration.md." + ), + ) async def _resolve_domain_id(slug: str, session: AsyncSession) -> uuid.UUID: row = await session.execute( @@ -66,14 +74,6 @@ async def _get_suggestion_or_404( return suggestion -def _reject_stage(suggestion: Suggestion, allowed: set[SuggestionStage], action: str) -> None: - if suggestion.stage not in allowed: - raise HTTPException( - status_code=409, - detail=f"Cannot {action} suggestion in stage '{suggestion.stage.value}'", - ) - - @router.get("/", response_model=list[SuggestionRead]) async def list_suggestions( domain: str | None = None, @@ -105,23 +105,13 @@ async def create_suggestion( body: SuggestionCreate, session: AsyncSession = Depends(get_session), ) -> SuggestionRead: - domain_id = await _resolve_domain_id(body.domain, session) - suggestion = Suggestion( - domain_id=domain_id, - topic_id=body.topic_id, - workplan_id=body.workplan_id, - title=body.title, - description=body.description, - origin=body.origin, - origin_ref=body.origin_ref, - base_value=body.base_value, - job_size=body.job_size, - relevance_weight=body.relevance_weight, - ) - session.add(suggestion) - await session.commit() - await session.refresh(suggestion) - return _enrich_read(suggestion) + """Retired (CUST-WP-0061-T06, 2026-07-21): suggestions are read-only + legacy. Use `POST /intakes/` (kind: intake per + canon/standards/work-record-types_v0.1.md) instead — a fresh entity, + not a rename of this table. The 10 records that were still open at + retirement were migrated to file-backed intake records; see + the-custodian/intake-legacy-suggestions-migration.md.""" + raise _retired() @router.get("/{suggestion_id}", response_model=SuggestionRead) @@ -139,28 +129,8 @@ async def vet_suggestion( body: SuggestionVet, session: AsyncSession = Depends(get_session), ) -> SuggestionRead: - suggestion = await _get_suggestion_or_404(suggestion_id, session) - _reject_stage(suggestion, _ALLOWED_VET_FROM, "vet") - suggestion.stage = SuggestionStage.requirement - if body.base_value is not None: - suggestion.base_value = body.base_value - if body.job_size is not None: - suggestion.job_size = body.job_size - if body.relevance_weight is not None: - suggestion.relevance_weight = body.relevance_weight - if body.workplan_id is not None: - suggestion.workplan_id = body.workplan_id - session.add( - SuggestionNote( - suggestion_id=suggestion.id, - stage=SuggestionStage.requirement.value, - author=body.author, - content=body.note, - ) - ) - await session.commit() - await session.refresh(suggestion) - return _enrich_read(suggestion) + """Retired (CUST-WP-0061-T06). See _retired().""" + raise _retired() @router.post("/{suggestion_id}/decline", response_model=SuggestionRead) @@ -169,20 +139,8 @@ async def decline_suggestion( body: SuggestionDecline, session: AsyncSession = Depends(get_session), ) -> SuggestionRead: - suggestion = await _get_suggestion_or_404(suggestion_id, session) - _reject_stage(suggestion, _ALLOWED_DECLINE_FROM, "decline") - suggestion.stage = SuggestionStage.declined - session.add( - SuggestionNote( - suggestion_id=suggestion.id, - stage=SuggestionStage.declined.value, - author=body.author, - content=body.note, - ) - ) - await session.commit() - await session.refresh(suggestion) - return _enrich_read(suggestion) + """Retired (CUST-WP-0061-T06). See _retired().""" + raise _retired() @router.post("/{suggestion_id}/promote", response_model=SuggestionRead) @@ -191,38 +149,8 @@ async def promote_suggestion_to_task( body: SuggestionPromote, session: AsyncSession = Depends(get_session), ) -> SuggestionRead: - suggestion = await _get_suggestion_or_404(suggestion_id, session) - _reject_stage(suggestion, _ALLOWED_PROMOTE_FROM, "promote") - if suggestion.workplan_id is None: - raise HTTPException( - status_code=409, - detail="Suggestion must have workplan_id before promotion", - ) - - task = Task( - workplan_id=suggestion.workplan_id, - title=body.task_title or suggestion.title, - description=body.task_description or suggestion.description, - status=TaskStatus(normalize_task_status(body.task_status)), - priority=TaskPriority(body.task_priority), - ) - session.add(task) - await session.flush() - - suggestion.stage = SuggestionStage.promoted - suggestion.promoted_task_id = task.id - if body.note: - session.add( - SuggestionNote( - suggestion_id=suggestion.id, - stage=SuggestionStage.promoted.value, - author=body.author, - content=body.note, - ) - ) - await session.commit() - await session.refresh(suggestion) - return _enrich_read(suggestion) + """Retired (CUST-WP-0061-T06). See _retired().""" + raise _retired() @router.post("/{suggestion_id}/bump-relevance", response_model=SuggestionRead) @@ -231,18 +159,8 @@ async def bump_suggestion_relevance( body: SuggestionBumpRelevance, session: AsyncSession = Depends(get_session), ) -> SuggestionRead: - suggestion = await _get_suggestion_or_404(suggestion_id, session) - source_key = body.author or "explicit" - await bump_relevance( - session, - suggestion, - source="explicit", - source_key=source_key, - reason=body.reason, - ) - await session.commit() - await session.refresh(suggestion) - return _enrich_read(suggestion) + """Retired (CUST-WP-0061-T06). See _retired().""" + raise _retired() @router.get("/{suggestion_id}/notes", response_model=list[SuggestionNoteRead]) diff --git a/mcp_server/server.py b/mcp_server/server.py index e95cca1..f9a8367 100644 --- a/mcp_server/server.py +++ b/mcp_server/server.py @@ -1269,7 +1269,10 @@ def create_suggestion( base_value: float = 3.0, job_size: float = 3.0, ) -> str: - """Record a gated need as a relevance-accruing suggestion.""" + """Retired (CUST-WP-0061-T06, 2026-07-21): suggestions are read-only + legacy. Use create_intake instead (kind: intake per canon/standards/ + work-record-types_v0.1.md). This call now 410s server-side; kept only + so old callers get a clear error instead of a missing tool.""" return json.dumps(_post("/suggestions", { "domain": domain, "title": title, @@ -1283,7 +1286,9 @@ def create_suggestion( @mcp.tool() def vet_suggestion(suggestion_id: str, note: str, author: str | None = None) -> str: - """Promote a suggestion to a vetted requirement with an append-only note.""" + """Retired (CUST-WP-0061-T06, 2026-07-21): suggestions are read-only + legacy. There is no vet/route equivalent needed for intake — use + route_intake directly. This call now 410s server-side.""" return json.dumps(_post(f"/suggestions/{suggestion_id}/vet", { "note": note, "author": author, @@ -1292,7 +1297,9 @@ def vet_suggestion(suggestion_id: str, note: str, author: str | None = None) -> @mcp.tool() def decline_suggestion(suggestion_id: str, note: str, author: str | None = None) -> str: - """Decline a suggestion or requirement.""" + """Retired (CUST-WP-0061-T06, 2026-07-21): suggestions are read-only + legacy. Use close_intake(outcome="declined") instead. This call now + 410s server-side.""" return json.dumps(_post(f"/suggestions/{suggestion_id}/decline", { "note": note, "author": author, @@ -1306,7 +1313,10 @@ def promote_suggestion_to_task( task_title: str | None = None, author: str | None = None, ) -> str: - """Promote a vetted requirement into a real Task.""" + """Retired (CUST-WP-0061-T06, 2026-07-21): suggestions are read-only + legacy. Use route_intake then the promote-intake CLI + (scripts/promote_intake.py --to task) instead. This call now 410s + server-side.""" return json.dumps(_post(f"/suggestions/{suggestion_id}/promote", { "note": note, "task_title": task_title, @@ -1320,7 +1330,10 @@ def bump_suggestion_relevance( reason: str | None = None, author: str | None = None, ) -> str: - """Explicitly bump demand relevance when an agent hits an unmet gated need.""" + """Retired (CUST-WP-0061-T06, 2026-07-21): suggestions are read-only + legacy — relevance/WSJF scoring has no intake equivalent yet (ordering + is lane+priority+age per the founder-reviewed architecture draft). + This call now 410s server-side.""" return json.dumps(_post(f"/suggestions/{suggestion_id}/bump-relevance", { "reason": reason, "author": author, diff --git a/tests/test_suggestions.py b/tests/test_suggestions.py index 54a3b47..bb6f735 100644 --- a/tests/test_suggestions.py +++ b/tests/test_suggestions.py @@ -1,158 +1,106 @@ -"""Demand-weighted suggestion backlog tests (STATE-WP-0061).""" +"""Suggestion (legacy) tests — CUST-WP-0061-T06, 2026-07-21. + +The suggestions table/router is retired to read-only legacy: a fresh +`intake` work-record entity replaces it (canon/standards/ +work-record-types_v0.1.md), not a rename. The 10 records still open at +retirement were migrated to file-backed intake records (see +the-custodian/intake-legacy-suggestions-migration.md) and declined here. + +These tests verify the retirement itself: every mutation endpoint 410s; +GET/list still works for the historical record. Since creation is +retired, historical rows are seeded directly via the ORM/DB session +(bypassing the retired POST) rather than through the API. +""" from __future__ import annotations +import uuid + import pytest +from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker -from tests.conftest import create_test_repo, create_test_workplan -from tests.test_capability_requests import _create_domain, _create_topic +from tests.test_capability_requests import _create_domain -async def _create_suggestion(client, **kwargs): - payload = { - "domain": "custodian", - "title": "Issue-core ingestion API key path", - "description": "OpenBao KV path for issue-core ingestion", - "origin_ref": "issue-core-ingestion-api-key", - "base_value": 4.0, - "job_size": 2.0, - } - payload.update(kwargs) - r = await client.post("/suggestions/", json=payload) - assert r.status_code == 201, r.text - return r.json() +async def _seed_suggestion(test_engine, *, domain_slug: str, **overrides) -> str: + """Insert a Suggestion row directly, bypassing the retired POST + endpoint — this is now the only way historical rows get created.""" + from api.models.domain import Domain + from api.models.suggestion import Suggestion + from sqlalchemy import select + + factory = async_sessionmaker(test_engine, class_=AsyncSession, expire_on_commit=False) + async with factory() as session: + domain_id = ( + await session.execute(select(Domain.id).where(Domain.slug == domain_slug)) + ).scalar_one() + suggestion = Suggestion( + domain_id=domain_id, + title=overrides.get("title", "Legacy suggestion"), + description=overrides.get("description"), + origin=overrides.get("origin"), + origin_ref=overrides.get("origin_ref"), + base_value=overrides.get("base_value", 3.0), + job_size=overrides.get("job_size", 3.0), + ) + session.add(suggestion) + await session.commit() + await session.refresh(suggestion) + return str(suggestion.id) @pytest.mark.asyncio -async def test_create_list_and_wsjf_ranking(client): - await _create_domain(client, "custodian", "Custodian") - low = await _create_suggestion( - client, - title="Low priority path", - origin_ref="low-priority", - base_value=1.0, - job_size=5.0, - ) - high = await _create_suggestion( - client, - title="High priority path", - origin_ref="high-priority", - base_value=5.0, - job_size=1.0, - ) +class TestSuggestionsRetired: + async def test_create_returns_410(self, client): + await _create_domain(client, "custodian", "Custodian") + r = await client.post("/suggestions/", json={ + "domain": "custodian", + "title": "New suggestion attempt", + }) + assert r.status_code == 410 + assert "intake" in r.json()["detail"].lower() - r = await client.get("/suggestions/?rank=wsjf") - assert r.status_code == 200 - ranked = r.json() - assert ranked[0]["id"] == high["id"] - assert ranked[0]["wsjf"] > ranked[1]["wsjf"] + async def test_vet_returns_410(self, client, test_engine): + await _create_domain(client, "custodian", "Custodian") + sid = await _seed_suggestion(test_engine, domain_slug="custodian") + r = await client.post(f"/suggestions/{sid}/vet", json={"note": "x"}) + assert r.status_code == 410 - await client.post( - f"/suggestions/{low['id']}/bump-relevance", - json={"reason": "hit again", "author": "agent-a"}, - ) - await client.post( - f"/suggestions/{low['id']}/bump-relevance", - json={"reason": "hit again", "author": "agent-b"}, - ) - r2 = await client.get(f"/suggestions/{low['id']}") - assert r2.json()["relevance"] == 2 + async def test_decline_returns_410(self, client, test_engine): + await _create_domain(client, "custodian", "Custodian") + sid = await _seed_suggestion(test_engine, domain_slug="custodian") + r = await client.post(f"/suggestions/{sid}/decline", json={"note": "x"}) + assert r.status_code == 410 + async def test_promote_returns_410(self, client, test_engine): + await _create_domain(client, "custodian", "Custodian") + sid = await _seed_suggestion(test_engine, domain_slug="custodian") + r = await client.post(f"/suggestions/{sid}/promote", json={}) + assert r.status_code == 410 -@pytest.mark.asyncio -async def test_bump_relevance_debounces_duplicate_explicit_bumps(client): - await _create_domain(client, "custodian", "Custodian") - suggestion = await _create_suggestion(client) - first = await client.post( - f"/suggestions/{suggestion['id']}/bump-relevance", - json={"reason": "routing gap", "author": "codex"}, - ) - second = await client.post( - f"/suggestions/{suggestion['id']}/bump-relevance", - json={"reason": "routing gap", "author": "codex"}, - ) - assert first.status_code == 200 - assert second.status_code == 200 - refreshed = await client.get(f"/suggestions/{suggestion['id']}") - assert refreshed.json()["relevance"] == 1 + async def test_bump_relevance_returns_410(self, client, test_engine): + await _create_domain(client, "custodian", "Custodian") + sid = await _seed_suggestion(test_engine, domain_slug="custodian") + r = await client.post(f"/suggestions/{sid}/bump-relevance", json={}) + assert r.status_code == 410 + async def test_get_and_list_still_work_for_historical_rows(self, client, test_engine): + await _create_domain(client, "custodian", "Custodian") + sid = await _seed_suggestion( + test_engine, domain_slug="custodian", + title="Historical gated-need signal", + origin="WARDEN-WP-0012", origin_ref="example", + ) -@pytest.mark.asyncio -async def test_vet_decline_and_promote_flow(client): - await _create_domain(client, "custodian", "Custodian") - topic = await _create_topic(client, "custodian") - repo = await create_test_repo(client, domain_slug="custodian", slug="state-hub") - workplan = await create_test_workplan( - client, repo_id=repo["id"], topic_id=topic["id"], slug="state-wp-0061", title="WP-0061", - ) + r = await client.get(f"/suggestions/{sid}") + assert r.status_code == 200 + assert r.json()["title"] == "Historical gated-need signal" - suggestion = await _create_suggestion( - client, - workplan_id=workplan["id"], - title="Promotable gated need", - origin_ref="promote-me", - ) + r2 = await client.get("/suggestions/") + assert r2.status_code == 200 + assert any(s["id"] == sid for s in r2.json()) - vet = await client.post( - f"/suggestions/{suggestion['id']}/vet", - json={"note": "Vetted as requirement", "author": "codex"}, - ) - assert vet.status_code == 200 - assert vet.json()["stage"] == "requirement" - - bad_promote = await client.post( - f"/suggestions/{suggestion['id']}/promote", - json={"note": "too early"}, - ) - assert bad_promote.status_code == 200 - - promoted = bad_promote.json() - assert promoted["stage"] == "promoted" - assert promoted["promoted_task_id"] is not None - - task = await client.get(f"/tasks/{promoted['promoted_task_id']}") - assert task.status_code == 200 - assert task.json()["title"] == "Promotable gated need" - - fresh = await _create_suggestion(client, title="Decline me", origin_ref="decline-me") - declined = await client.post( - f"/suggestions/{fresh['id']}/decline", - json={"note": "Not needed", "author": "codex"}, - ) - assert declined.status_code == 200 - assert declined.json()["stage"] == "declined" - - illegal = await client.post( - f"/suggestions/{fresh['id']}/vet", - json={"note": "too late"}, - ) - assert illegal.status_code == 409 - - -@pytest.mark.asyncio -async def test_next_steps_surfaces_and_bumps_suggestions(client): - await _create_domain(client, "custodian", "Custodian") - await _create_suggestion(client, title="Surfaced need", origin_ref="surfaced-need") - before = await client.get("/suggestions/?origin_ref=surfaced-need") - # no filter by origin_ref on list - get all and find - all_items = await client.get("/suggestions/") - item = next(i for i in all_items.json() if i["origin_ref"] == "surfaced-need") - assert item["relevance"] == 0 - - steps = await client.get("/state/next_steps") - assert steps.status_code == 200 - payload = steps.json() - assert any(s["type"] == "open_suggestion" for s in payload) - - after = await client.get(f"/suggestions/{item['id']}") - assert after.json()["relevance"] >= 1 - - -@pytest.mark.asyncio -async def test_summary_includes_ranked_suggestions(client): - await _create_domain(client, "custodian", "Custodian") - await _create_suggestion(client, title="Summary ranked", origin_ref="summary-ranked") - summary = await client.get("/state/summary") - assert summary.status_code == 200 - data = summary.json() - assert "ranked_suggestions" in data - assert any(s["origin_ref"] == "summary-ranked" for s in data["ranked_suggestions"]) \ No newline at end of file + async def test_unknown_id_still_404s_not_410(self, client): + """Read paths keep their normal semantics; only mutations are + blanket-retired.""" + r = await client.get(f"/suggestions/{uuid.uuid4()}") + assert r.status_code == 404 From 224584d37932cbc05c41b1d5b3734704dce70934 Mon Sep 17 00:00:00 2001 From: tegwick Date: Tue, 21 Jul 2026 04:21:55 +0200 Subject: [PATCH 017/321] Include templates/ in image for RecentlyOnScope digests Fixes missing domain-digest.md at /app/templates on the railiance01 edge-relay image (activity-core ACTIVITY-WP-0021-T04). --- Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dockerfile b/Dockerfile index b1cfbd3..5833f0f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -39,6 +39,9 @@ COPY policies/ ./policies/ COPY prompts/ ./prompts/ COPY scripts/ ./scripts/ COPY task_flow_engine/ ./task_flow_engine/ +# ACTIVITY-WP-0021 / ROS hourly: domain-digest.md must exist at +# templates/recently-on-scope/ for /recently-on-scope/hourly rendering. +COPY templates/ ./templates/ EXPOSE 8000 From c363549388eeedaa6857e79c00166b7dc90155dc Mon Sep 17 00:00:00 2001 From: tegwick Date: Wed, 22 Jul 2026 01:22:22 +0200 Subject: [PATCH 018/321] fix(dashboard): restore shift-click suggestion submit Default domain was retired slug "custodian", so POST /technical-debt/ returned 422 and the modal failed after open. Use infotech, resolve apiBase like config.js, and surface API error detail in the toast. --- dashboard/observablehq.config.js | 4 +- dashboard/src/components/improvement-modal.js | 73 +++++++++++++++++-- dashboard/test/improvement-modal.test.mjs | 46 ++++++++++++ 3 files changed, 116 insertions(+), 7 deletions(-) create mode 100644 dashboard/test/improvement-modal.test.mjs diff --git a/dashboard/observablehq.config.js b/dashboard/observablehq.config.js index e30abbe..9fdcfb2 100644 --- a/dashboard/observablehq.config.js +++ b/dashboard/observablehq.config.js @@ -10,7 +10,9 @@ const _configDir = dirname(fileURLToPath(import.meta.url)); const _modalScript = readFileSync( join(_configDir, "src/components/improvement-modal.js"), "utf-8" ) - .replace(/^export function /gm, "function ") // strip ES module export + // Strip ES module exports so the file can run as a plain