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."""