From a705f2fee16f425f715ab2ab4dd5091bbbfeef22 Mon Sep 17 00:00:00 2001 From: RealDiligent Date: Mon, 13 Jul 2026 20:04:04 +0800 Subject: [PATCH 1/2] fix(stats): use lint source collector in citation_summary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit citation_summary called list_sources(), which skips unreadable meta via _load_or_skip. Claims citing those sources were misreported as broken_citation in kb.stats / vouch stats — lint already treats the source as present (unreadable_source finding instead). Co-authored-by: Cursor --- src/vouch/stats.py | 5 ++++- tests/test_stats.py | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/vouch/stats.py b/src/vouch/stats.py index db30754f..cdd0cc9b 100644 --- a/src/vouch/stats.py +++ b/src/vouch/stats.py @@ -91,7 +91,10 @@ def _rate(numerator: int, denominator: int) -> float | None: def citation_summary(store: KBStore) -> dict[str, Any]: claims, findings = health._load_claims_for_lint(store) - sources_present = {s.id for s in store.list_sources()} + # Use the lint source collector, not list_sources(): a corrupt meta.yaml + # is still present on disk (id = directory name) and must not be counted + # as a broken citation — same invariant as health.lint (#81 follow-up). + sources_present, _ = health._collect_sources_for_lint(store) evidence_present = {e.id for e in store.list_evidence()} invalid_claim = sum( diff --git a/tests/test_stats.py b/tests/test_stats.py index fdda64f4..16b26938 100644 --- a/tests/test_stats.py +++ b/tests/test_stats.py @@ -57,6 +57,20 @@ def test_citation_summary_flags_broken_and_invalid(store: KBStore) -> None: assert summary["claims_with_valid_citation"] == 1 +def test_citation_summary_honest_when_source_meta_unreadable(store: KBStore) -> None: + """Corrupt source meta must not inflate broken_citation — mirrors lint.""" + src = store.put_source(b"e") + store.put_claim(Claim(id="c1", text="t", evidence=[src.id])) + meta = store.kb_dir / "sources" / src.id / "meta.yaml" + meta.write_text( + meta.read_text(encoding="utf-8") + "title: conversation \u00e2\u0080\u0094 vouch\n", + encoding="utf-8", + ) + summary = stats.citation_summary(store) + assert summary["broken_citation"] == 0 + assert summary["claims_with_valid_citation"] == 1 + + def test_pending_summary_by_agent(store: KBStore) -> None: src = store.put_source(b"x") propose_claim(store, text="a", evidence=[src.id], proposed_by="agent-a") From a9ac5804d364a46c1e58acd9a25c8d449f56769a Mon Sep 17 00:00:00 2001 From: RealDiligent Date: Mon, 13 Jul 2026 20:07:08 +0800 Subject: [PATCH 2/2] ci: retrigger checks after flaky websocket timing test Co-authored-by: Cursor