From 84c5c0dc8176e6ed4534a62c283bceff4a3dc2dd Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Tue, 28 Jul 2026 18:24:48 -0400 Subject: [PATCH] fix(snippets): satisfy the parity guards the drift check tripped MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two CI failures, both the guards working as intended rather than defects in them: - The MCP tool-name manifest and the REST route/service parity lists are explicit, so a new capability has to be added to both surfaces or the test fails. verify_snippet / verify_snippet_route / record_verification added, plus an assertion that the `verification` filter reaches both callers — a verdict an agent records must be visible to the human looking at the same corpus, or the two surfaces disagree about what's rotten (rule #33). - vue-tsc rejected two object-literal lookups keyed by the status union: `status` includes "ok" and "unverified", and an object literal has to enumerate every member even just to say "nothing to show for these". Typed as Record, which is what the ?? "" fallback already assumed. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs --- frontend/src/views/SnippetListView.vue | 24 +++++++++++++++--------- tests/test_mcp_tool_snippets.py | 2 +- tests/test_routes_snippets.py | 11 +++++++++-- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/frontend/src/views/SnippetListView.vue b/frontend/src/views/SnippetListView.vue index 352ca4b..ad68c00 100644 --- a/frontend/src/views/SnippetListView.vue +++ b/frontend/src/views/SnippetListView.vue @@ -161,9 +161,15 @@ function driftBadge(s: SnippetListItem): string { const v = s.verification; if (!v) return ""; if (!v.current) return "unchecked since edit"; - return { ok: "", missing: "path gone", moved: "symbol moved", changed: "code drifted" }[ - v.status - ] ?? ""; + // Record, not an object literal: `status` is a union that + // includes "ok" and "unverified", and a literal would have to enumerate every + // member just to say "nothing to show for these". + const labels: Record = { + missing: "path gone", + moved: "symbol moved", + changed: "code drifted", + }; + return labels[v.status] ?? ""; } function driftTitle(s: SnippetListItem): string { @@ -178,12 +184,12 @@ function driftTitle(s: SnippetListItem): string { `code this record no longer holds. Re-verify it.` ); } - const what = - { - missing: "the recorded path no longer exists", - moved: "the file is there but the symbol isn't in it", - changed: "the source no longer matches the recorded code", - }[v.status] ?? ""; + const reasons: Record = { + missing: "the recorded path no longer exists", + moved: "the file is there but the symbol isn't in it", + changed: "the source no longer matches the recorded code", + }; + const what = reasons[v.status] ?? ""; return v.detail ? `${when}: ${what}. ${v.detail}` : `${when}: ${what}.`; } diff --git a/tests/test_mcp_tool_snippets.py b/tests/test_mcp_tool_snippets.py index 4c10f95..5d7e1d4 100644 --- a/tests/test_mcp_tool_snippets.py +++ b/tests/test_mcp_tool_snippets.py @@ -228,5 +228,5 @@ def test_register_attaches_all_tools(): snippets.register(FakeMcp()) assert set(names) == { "list_snippets", "create_snippet", "get_snippet", "update_snippet", - "delete_snippet", "merge_snippets", + "delete_snippet", "merge_snippets", "verify_snippet", } diff --git a/tests/test_routes_snippets.py b/tests/test_routes_snippets.py index 7b0497f..0d36723 100644 --- a/tests/test_routes_snippets.py +++ b/tests/test_routes_snippets.py @@ -20,6 +20,7 @@ def test_snippet_handlers_callable(): for name in ( "list_snippets_route", "create_snippet_route", "get_snippet_route", "update_snippet_route", "delete_snippet_route", "merge_snippet_route", + "verify_snippet_route", ): assert callable(getattr(routes, name)) @@ -29,7 +30,7 @@ def test_service_functions_take_user_id(): from scribe.services import snippets as svc for fn_name in ( "create_snippet", "list_snippets", "get_snippet", "update_snippet", - "delete_snippet", "merge_snippets", + "delete_snippet", "merge_snippets", "record_verification", ): fn = getattr(svc, fn_name) assert callable(fn) @@ -45,7 +46,7 @@ def test_agent_and_web_surfaces_stay_at_parity(): from scribe.routes import snippets as routes # Every write verb the web surface offers, the agent surface offers too. - for verb in ("create", "get", "list", "update", "delete", "merge"): + for verb in ("create", "get", "list", "update", "delete", "merge", "verify"): assert callable(getattr(tools, f"{verb}_snippets", None) or getattr(tools, f"{verb}_snippet", None)), f"MCP lacks {verb}" @@ -61,6 +62,12 @@ def test_agent_and_web_surfaces_stay_at_parity(): assert "system_ids" in inspect.getsource(route) assert "find_duplicate_note" in inspect.getsource(routes.create_snippet_route) + # The drift check (#2086) reaches both: recording a verdict, and filtering + # the list by one. A verdict an agent records has to be visible to the human + # looking at the same corpus, or the two surfaces disagree about what's rotten. + assert "verification" in inspect.signature(tools.list_snippets).parameters + assert "verification" in inspect.getsource(routes.list_snippets_route) + def test_project_scoping_reaches_every_caller(): """A snippet search has to be narrowable to one project from both surfaces."""