Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ All notable changes to vouch are documented here. Format follows
artifact the caller could not already retrieve, and it touches no write path.

### Fixed
- **`vouch stats` / `kb.stats` no longer crash on one corrupt `decided/*.yaml`**:
`_list_decided` parsed every decided proposal strictly, so a single bad file
aborted `review_summary` / `collect_stats`. It now uses `_load_or_skip` —
same resilience as `list_proposals` / `list_pages`.
- **rerank / recency / triage quoted `"true"` stays off** (#658):
`retrieval.rerank.enabled`, `retrieval.recency.enabled` and
`triage.enabled` were the last three readers still on the
Expand Down
11 changes: 9 additions & 2 deletions src/vouch/stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from . import audit, health
from .models import Proposal, ProposalStatus
from .proposals import EXPIRE_REASON
from .storage import KBStore, _yaml_load
from .storage import KBStore, _load_or_skip

if TYPE_CHECKING:
from .scoping import ViewerContext
Expand Down Expand Up @@ -50,12 +50,19 @@ def _decision_bucket(proposal: Proposal) -> str:


def _list_decided(store: KBStore) -> list[Proposal]:
"""Load decided proposals for review_summary.

Uses ``_load_or_skip`` so one corrupt ``decided/*.yaml`` cannot take down
``vouch stats`` / ``kb.stats`` — same resilience as ``list_proposals``.
"""
ddir = store.kb_dir / "decided"
if not ddir.is_dir():
return []
out: list[Proposal] = []
for path in sorted(ddir.glob("*.yaml")):
out.append(Proposal.model_validate(_yaml_load(path.read_text(encoding="utf-8"))))
pr = _load_or_skip(path, Proposal, "proposal")
if pr is not None:
out.append(pr)
return out


Expand Down
14 changes: 14 additions & 0 deletions tests/test_stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,20 @@ def test_review_summary_counts_decisions(store: KBStore) -> None:
assert review["by_agent"]["a2"]["rejected"] == 1


def test_review_summary_skips_corrupt_decided_file(store: KBStore) -> None:
"""One bad decided/*.yaml must not crash vouch stats / kb.stats."""
src = store.put_source(b"x")
pr = propose_claim(store, text="ok", evidence=[src.id], proposed_by="a")
approve(store, pr.id, approved_by="human")
(store.kb_dir / "decided" / "corrupt.yaml").write_text(
"not: [valid\n", encoding="utf-8",
)
review = stats.review_summary(store, since_days=None)
assert review["approved"] == 1
body = stats.collect_stats(store, since_days=None)
assert body["review"]["approved"] == 1


def test_review_summary_respects_window(store: KBStore) -> None:
src = store.put_source(b"x")
pr = propose_claim(store, text="old", evidence=[src.id], proposed_by="a")
Expand Down
Loading