From 6c1ad8290127250b2b0336774243ff1af2158212 Mon Sep 17 00:00:00 2001 From: philluiz2323 Date: Fri, 31 Jul 2026 11:26:01 -0700 Subject: [PATCH 1/2] feat(health): expose fsck as kb.fsck on mcp/jsonl/capabilities health.fsck() (orphaned embeddings, dangling supersede/contradict chains, decided-proposal <-> artifact mismatches, index-vs-file drift) was called from exactly one place, cli.py's fsck command. kb.lint and kb.doctor already have full mcp/jsonl/capabilities registration - fsck was the one gap in that otherwise-complete trio, leaving an agent no way to run the deepest check without shelling out to the CLI. register kb_fsck on mcp and _h_fsck on jsonl, mirroring kb_doctor's registration exactly, plus the capabilities.METHODS entry and hot_memory.py's coverage registry (excluded with the same reason kb.lint/kb.doctor already carry: diagnostics, no claim payload). the CLI mirror needed no change - vouch fsck already exists and matches the default kb.foo -> vouch foo naming rule test_cli_commands_match_ methods checks against. Closes #738 --- CHANGELOG.md | 9 ++++++ src/vouch/capabilities.py | 1 + src/vouch/hot_memory.py | 1 + src/vouch/jsonl_server.py | 14 +++++++++ src/vouch/server.py | 19 +++++++++++++ tests/test_health.py | 60 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 104 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b0113b4..5c14f391 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ All notable changes to vouch are documented here. Format follows ## [Unreleased] ### Added +- **`kb.fsck` — the deepest health check, now agent-facing** (roadmap 1.5, + #738): `health.fsck()` (orphaned embeddings, dangling + supersede/contradict chains, decided-proposal ↔ artifact mismatches, + index-vs-file drift) was CLI-only — `kb.lint` and `kb.doctor` had full + MCP/JSONL/`capabilities.METHODS` registration, `fsck` didn't, so an + agent had no way to run the deepest check without shelling out. + Registered the same way `kb.doctor` already is; the CLI mirror + (`vouch fsck`) needed no change since it already matches the default + `kb.foo` → `vouch foo` naming rule. - **bench: composite guards** (#616): `efficiency`, `consistency` and `canary` as bounded multipliers over the composite, plus a `bench_version` stamp on every report. Reported **beside** the composite, never folded into it — diff --git a/src/vouch/capabilities.py b/src/vouch/capabilities.py index e7720687..2bedb087 100644 --- a/src/vouch/capabilities.py +++ b/src/vouch/capabilities.py @@ -87,6 +87,7 @@ "kb.index_rebuild", "kb.lint", "kb.doctor", + "kb.fsck", "kb.export", "kb.export_check", "kb.import_check", diff --git a/src/vouch/hot_memory.py b/src/vouch/hot_memory.py index e07a66ac..f1787483 100644 --- a/src/vouch/hot_memory.py +++ b/src/vouch/hot_memory.py @@ -197,6 +197,7 @@ def mark_volunteered(session_id: str, claim_id: str, *, pushed_at: float) -> Non "kb.index_rebuild": "maintenance — mutates derived index", "kb.lint": "diagnostics — no claim payload", "kb.doctor": "diagnostics — no claim payload", + "kb.fsck": "diagnostics — no claim payload", "kb.export": "bundle write — not a read response", "kb.export_check": "preflight — no claim sidebar needed", "kb.import_check": "preflight — no claim sidebar needed", diff --git a/src/vouch/jsonl_server.py b/src/vouch/jsonl_server.py index bdd4fd9f..da608017 100644 --- a/src/vouch/jsonl_server.py +++ b/src/vouch/jsonl_server.py @@ -786,6 +786,19 @@ def _h_doctor(_: dict) -> dict: } +def _h_fsck(_: dict) -> dict: + report = health.fsck(_store()) + return { + "ok": report.ok, + "findings": [ + {"severity": f.severity, "code": f.code, + "message": f.message, "object_ids": f.object_ids} + for f in report.findings + ], + "counts": report.counts, + } + + def _h_export(p: dict) -> dict: s = _store() exclude = tuple(p.get("exclude") or ()) @@ -1048,6 +1061,7 @@ def _h_propose_theme(p: dict) -> dict: "kb.index_rebuild": _h_index_rebuild, "kb.lint": _h_lint, "kb.doctor": _h_doctor, + "kb.fsck": _h_fsck, "kb.export": _h_export, "kb.export_check": _h_export_check, "kb.import_check": _h_import_check, diff --git a/src/vouch/server.py b/src/vouch/server.py index c2b3221a..e76dd884 100644 --- a/src/vouch/server.py +++ b/src/vouch/server.py @@ -1194,6 +1194,25 @@ def kb_doctor() -> dict[str, Any]: } +@mcp.tool() +def kb_fsck() -> dict[str, Any]: + """Deep consistency check: orphaned embeddings, dangling lifecycle + chains, decided-proposal <-> artifact mismatches, index-vs-file drift. + + Slower and stricter than `kb_doctor` — read-only, report findings only. + """ + report = health.fsck(_store()) + return { + "ok": report.ok, + "findings": [ + {"severity": f.severity, "code": f.code, + "message": f.message, "object_ids": f.object_ids} + for f in report.findings + ], + "counts": report.counts, + } + + @mcp.tool() def kb_export(out_path: str, exclude: list[str] | None = None) -> dict[str, Any]: # exclude: subdir/file names to omit (e.g. "decided", "sessions") for a diff --git a/tests/test_health.py b/tests/test_health.py index f81374c1..34377dfb 100644 --- a/tests/test_health.py +++ b/tests/test_health.py @@ -425,6 +425,66 @@ def test_fsck_without_state_db_reports_info(store: KBStore) -> None: assert report.ok is True +# --- kb.fsck: agent-facing surfaces (MCP/JSONL/CLI) ------------------------- +# +# health.fsck() itself is exercised throughout this file; these tests cover +# only the newly-registered kb.fsck surfaces (it was CLI-only before, with +# no MCP tool, JSONL handler, or capabilities.METHODS entry). + + +def test_jsonl_fsck_matches_direct_call(store: KBStore, monkeypatch) -> None: + from vouch.jsonl_server import handle_request + + src = store.put_source(b"e") + store.put_claim(Claim(id="c1", text="t", evidence=[src.id])) + monkeypatch.chdir(store.root) + + resp = handle_request({"id": "f1", "method": "kb.fsck", "params": {}}) + assert resp["ok"] is True + + direct = health.fsck(store) + assert resp["result"]["ok"] == direct.ok + assert {f["code"] for f in resp["result"]["findings"]} == { + f.code for f in direct.findings + } + assert resp["result"]["counts"]["claims"] == direct.counts["claims"] + + +def test_jsonl_fsck_surfaces_findings_not_just_ok_flag( + store: KBStore, monkeypatch, +) -> None: + """A real finding (not just the ok flag) must round-trip through the + JSONL envelope — confirms the handler forwards the full findings list, + not a summarized/truncated version.""" + from vouch.jsonl_server import handle_request + + src = store.put_source(b"e") + c = Claim(id="real", text="t", evidence=[src.id]) + store.put_claim(c) + _index_claim(store, c) + with index_db.open_db(store.kb_dir) as conn: + index_db.index_claim( + conn, id="ghost", text="x", type="fact", status="working", tags=[], + ) + monkeypatch.chdir(store.root) + + resp = handle_request({"id": "f2", "method": "kb.fsck", "params": {}}) + assert resp["ok"] is True + codes = {f["code"] for f in resp["result"]["findings"]} + assert "index_orphan_claim" in codes + + +def test_cli_fsck_registered_as_kb_fsck_method() -> None: + """kb.fsck's default CLI mirror rule (kb.foo -> vouch foo) must resolve + to the pre-existing `fsck` command rather than needing a new one — this + pins that `_CLI_MIRRORS` correctly has no entry for kb.fsck.""" + from tests.test_capabilities import _CLI_MIRRORS + from vouch import capabilities + + assert "kb.fsck" in capabilities.METHODS + assert "kb.fsck" not in _CLI_MIRRORS + + def test_receipt_coverage_fidelity_number(store: KBStore) -> None: from vouch.extract import ingest_source from vouch.models import Claim From f06b4a1fff2a938b5605f65a54fcf7c0194be402 Mon Sep 17 00:00:00 2001 From: philluiz2323 Date: Fri, 31 Jul 2026 12:24:39 -0700 Subject: [PATCH 2/2] test(health): cover kb_fsck's mcp tool body directly diff-coverage flagged server.py:1204-1205 (kb_fsck's report = health. fsck(_store()) / return {...}) as untested - the existing jsonl-level test exercises _h_fsck, never the mcp wrapper function itself. add a direct call matching the pattern newer kb.* methods already use (test_explain_ranking.py's test_mcp_surface_serves_explain_ranking): monkeypatch server._store to the test fixture, call server.kb_fsck() directly, and diff its result against a direct health.fsck() call. --- tests/test_health.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/test_health.py b/tests/test_health.py index 34377dfb..ea9a3861 100644 --- a/tests/test_health.py +++ b/tests/test_health.py @@ -474,6 +474,20 @@ def test_jsonl_fsck_surfaces_findings_not_just_ok_flag( assert "index_orphan_claim" in codes +def test_mcp_surface_serves_fsck(store: KBStore, monkeypatch) -> None: + from vouch import server + + src = store.put_source(b"e") + store.put_claim(Claim(id="c1", text="t", evidence=[src.id])) + monkeypatch.setattr(server, "_store", lambda: store) + + result = server.kb_fsck() + direct = health.fsck(store) + assert result["ok"] == direct.ok + assert {f["code"] for f in result["findings"]} == {f.code for f in direct.findings} + assert result["counts"]["claims"] == direct.counts["claims"] + + def test_cli_fsck_registered_as_kb_fsck_method() -> None: """kb.fsck's default CLI mirror rule (kb.foo -> vouch foo) must resolve to the pre-existing `fsck` command rather than needing a new one — this