Skip to content

feat(cli): vouch metrics — first-class observability#193

Merged
plind-junior merged 3 commits into
vouchdev:testfrom
dripsmvcp:feat/192-metrics
Jun 10, 2026
Merged

feat(cli): vouch metrics — first-class observability#193
plind-junior merged 3 commits into
vouchdev:testfrom
dripsmvcp:feat/192-metrics

Conversation

@dripsmvcp

@dripsmvcp dripsmvcp commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds vouch metrics, a read-only observability surface for the review gate and corpus.
  • All numbers derive purely from .vouch/audit.log.jsonl + the artifact files — no new on-disk state, no schema migration (per ROADMAP 0.3 observability hooks).

What it emits

  • approval_rateapprove / (approve + reject) over the window, plus a per-ProposalKind breakdown (approval_rate_by_kind) and raw decisions_by_kind.
  • citation_coverage / citation_broken — fraction of claims whose every evidence id resolves to a live Source/Evidence.
  • stale_ratio — stale active claims over active claims; threshold matches vouch lint --stale-days (default 180); retired claims (superseded/archived/redacted) are exempt.
  • proposal_lag_secondsp50/p90/p99/mean/max from each proposal's *.create*.approve (nearest-rank percentiles, matching Prometheus histogram_quantile). A create older than --since still pairs with an in-window approve, so the window's left edge doesn't undercount.
  • actors — top-N leaderboard (proposed/approved/rejected/confirmed).
  • plus a claims_by_status histogram and audit event volume.

Surfaces

  • human table (default)
  • --json — stable, versioned schema (schema_version=1) documented in docs/metrics.md. Ratios are null (not 0) when the denominator is empty, so consumers can tell "no data" from a real zero.
  • --prometheus — textfile-collector exposition with # HELP/# TYPE headers; null gauges are omitted so a 0 never lies about the denominator.
  • --since / --until — durations (30d/12h/2w) or ISO dates; --stale-days and --top configurable.

Design notes

  • Approve/reject counts come from the audit log, not decided/ proposal files — the log is append-only and authoritative, and carries the timestamps the lag percentiles need.
  • Audit-derived metrics are windowed; corpus metrics reflect current on-disk state (a claim is stale now regardless of the window).

Test Plan

  • Regression tests added: tests/test_metrics.py (45 tests) — fixture KB with statistics known by construction; asserts every metric, the stable JSON schema, the Prometheus exposition, windowing, and the CLI surface.
  • Full suite passes: pytest -q201 passed
  • ruff check src/vouch tests → clean
  • mypy src/vouch/metrics.py src/vouch/cli.py → clean
  • Smoke-tested against a real KB: human/--json/--prometheus output, bad---since error, mutually-exclusive-flag guard.

Closes #192

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added vouch metrics command that computes observability metrics from audit logs and artifacts. Supports human-readable table, JSON, and Prometheus output formats. Metrics include approval rates, citation coverage, staleness tracking, proposal lag analysis, and actor activity leaderboards. Configurable time windows with --since and --until flags.
  • Documentation

    • Added comprehensive metrics command documentation covering schema, output formats, configuration, and usage examples.

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d8e7b10f-2419-4cd6-9bb9-1fe172a83b5e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR introduces vouch metrics, a read-only observability command that computes approval rates, citation coverage, staleness, proposal lag, and actor activity from the audit log and on-disk artifacts. It emits stable JSON and Prometheus textfile formats, supports time-window filtering, and includes comprehensive tests and documentation.

Changes

Metrics Observability

Layer / File(s) Summary
Metrics data model, parsing, and utilities
src/vouch/metrics.py (module intro, parsing, containers), tests/test_metrics.py (fixtures, parse/percentile tests)
Introduces LagStats, ActorStat, and Metrics dataclasses; parse_since() for time window parsing supporting durations and ISO-8601 formats; percentile() nearest-rank utility; and test fixtures for deterministic KB setup.
Core metrics computation from audit and artifacts
src/vouch/metrics.py (compute, _fill_corpus_metrics), tests/test_metrics.py (compute, lag, actor, validation, schema tests)
compute() streams the audit log once to derive review-gate counts/rates, pairs proposal creates with approvals for lag percentiles, and builds a deterministically sorted actor leaderboard; _fill_corpus_metrics() analyzes on-disk artifacts for citation coverage, staleness, and per-status counts. Tests validate all metrics against a fixture KB with known claims and audit events.
Prometheus textfile-collector rendering
src/vouch/metrics.py (render_prometheus), tests/test_metrics.py (prometheus tests)
render_prometheus() converts Metrics into textfile-collector exposition format with HELP/TYPE headers and labeled gauges, omitting null metrics. Tests verify header presence and null-gauge omission.
CLI command integration and output formatting
src/vouch/cli.py (import, helpers, metrics command), tests/test_metrics.py (CLI tests)
vouch metrics CLI command with options for mutually exclusive JSON/Prometheus/table output, --since/--until windowing, staleness threshold, and top-N actors; includes _fmt_pct() and _fmt_secs() helpers for human-readable table rendering. Tests cover all output modes, filtering, and error handling.
Command documentation and schema reference
docs/metrics.md
Documents vouch metrics usage, stable JSON schema (schema_version, window, review-gate, corpus, lag, actors, audit sections), Prometheus format conventions, null semantics, cron deployment example with atomic writes, and out-of-scope items.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • feat: vouch metrics — first-class observability #192: This PR directly implements the objectives from the linked issue: vouch metrics command with approval rate, citation coverage, stale ratio, proposal lag, actor leaderboard, stable JSON schema, Prometheus output, and time-window filtering.

Poem

🐰 Hop along the audit trail so long,
Metrics bloom where none belonged—
Approval rates and lag in view,
Schema stable, stale claims too.
Prometheus speaks truth at last! 📊

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.21% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: addition of a new metrics CLI command with observability features.
Linked Issues check ✅ Passed All core objectives from issue #192 are met: stable JSON schema in docs/metrics.md, metrics derived from audit.log.jsonl and artifacts only, approval_rate/citation_coverage/stale_ratio/proposal_lag/actors implemented, --since/--until windowing, and comprehensive tests validating fixture KBs.
Out of Scope Changes check ✅ Passed All changes directly support the metrics feature: documentation, CLI command implementation, core metrics module, and tests. No unrelated or out-of-scope modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/vouch/metrics.py (2)

556-611: 💤 Low value

Minor: variable emitted_headers defined after the closure that references it.

Line 579 defines emitted_headers: set[str] = set() after the gauge helper function (lines 566-577) that uses it. While this works correctly in Python (variables are looked up when the closure is called, not defined), it's slightly unconventional to define a variable after a function that references it. For clearer code flow, consider moving line 579 to just before line 566.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vouch/metrics.py` around lines 556 - 611, The closure gauge inside
render_prometheus references emitted_headers which is declared later; move the
emitted_headers: set[str] = set() declaration to just before the gauge function
definition so the helper and state are grouped together (keep the same variable
name and type annotation) and then leave the rest of render_prometheus
unchanged.

300-306: 💤 Low value

Consider simplifying line 306 for readability.

The current logic return not (until is not None and ts > until) is correct but requires careful parsing. A more direct expression would be:

return until is None or ts <= until

Both are equivalent, but the suggested form makes the intent clearer: "return True if there's no upper bound OR the timestamp is within the upper bound."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vouch/metrics.py` around lines 300 - 306, The _in_window function's final
return is hard to read; replace the expression that checks the upper bound in
_in_window (which currently uses `not (until is not None and ts > until)`) with
a clearer direct check using the until variable and ts (i.e., return True when
there is no upper bound or ts is <= until) so the logic reads as "no upper bound
OR timestamp within upper bound" while keeping the earlier null checks for ts
and since intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/metrics.md`:
- Around line 137-148: The fenced code block containing the Prometheus
exposition snippet (the block with metrics like vouch_approval_rate,
vouch_citation_coverage, and vouch_claims_by_status) should include a language
identifier; update the opening triple-backtick to use a tag such as "text" or
"prometheus" so the block reads ```text (or ```prometheus) to satisfy the linter
and clarify formatting.

---

Nitpick comments:
In `@src/vouch/metrics.py`:
- Around line 556-611: The closure gauge inside render_prometheus references
emitted_headers which is declared later; move the emitted_headers: set[str] =
set() declaration to just before the gauge function definition so the helper and
state are grouped together (keep the same variable name and type annotation) and
then leave the rest of render_prometheus unchanged.
- Around line 300-306: The _in_window function's final return is hard to read;
replace the expression that checks the upper bound in _in_window (which
currently uses `not (until is not None and ts > until)`) with a clearer direct
check using the until variable and ts (i.e., return True when there is no upper
bound or ts is <= until) so the logic reads as "no upper bound OR timestamp
within upper bound" while keeping the earlier null checks for ts and since
intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 35104da4-f5b1-474e-a7af-3c7a5a4ad8b2

📥 Commits

Reviewing files that changed from the base of the PR and between 3beb821 and ce66b0d.

📒 Files selected for processing (4)
  • docs/metrics.md
  • src/vouch/cli.py
  • src/vouch/metrics.py
  • tests/test_metrics.py

Comment thread docs/metrics.md Outdated
Comment on lines +137 to +148
```
# HELP vouch_approval_rate approve / (approve + reject) over window.
# TYPE vouch_approval_rate gauge
vouch_approval_rate 0.75
# HELP vouch_citation_coverage Fraction of claims fully cited.
# TYPE vouch_citation_coverage gauge
vouch_citation_coverage 0.8
# HELP vouch_claims_by_status Claim count per status.
# TYPE vouch_claims_by_status gauge
vouch_claims_by_status{status="archived"} 1
vouch_claims_by_status{status="working"} 4
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Specify language for fenced code block.

The fenced code block starting at line 137 should specify a language identifier. Since Prometheus exposition format doesn't have standard syntax highlighting, mark it as text or prometheus.

📝 Proposed fix
-```
+```text
 # HELP vouch_approval_rate approve / (approve + reject) over window.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```
# HELP vouch_approval_rate approve / (approve + reject) over window.
# TYPE vouch_approval_rate gauge
vouch_approval_rate 0.75
# HELP vouch_citation_coverage Fraction of claims fully cited.
# TYPE vouch_citation_coverage gauge
vouch_citation_coverage 0.8
# HELP vouch_claims_by_status Claim count per status.
# TYPE vouch_claims_by_status gauge
vouch_claims_by_status{status="archived"} 1
vouch_claims_by_status{status="working"} 4
```
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 137-137: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/metrics.md` around lines 137 - 148, The fenced code block containing the
Prometheus exposition snippet (the block with metrics like vouch_approval_rate,
vouch_citation_coverage, and vouch_claims_by_status) should include a language
identifier; update the opening triple-backtick to use a tag such as "text" or
"prometheus" so the block reads ```text (or ```prometheus) to satisfy the linter
and clarify formatting.

Source: Linters/SAST tools

@dripsmvcp
dripsmvcp changed the base branch from main to test June 10, 2026 13:47
dripsmvcp and others added 3 commits June 10, 2026 23:01
Add `vouch metrics`, a read-only observability surface for the review gate
and corpus, derived purely from .vouch/audit.log.jsonl + the artifact files
(no new on-disk state, no schema migration).

Emits:
- approval_rate (+ per-kind breakdown and decisions_by_kind)
- citation_coverage / citation_broken (every evidence ref must resolve)
- stale_ratio against an active-claim denominator (retired claims exempt),
  threshold matching `vouch lint --stale-days`
- proposal_lag_seconds p50/p90/p99/mean/max (nearest-rank, matching
  Prometheus histogram_quantile); a create older than --since still pairs
  with an in-window approve so the window's left edge doesn't undercount
- actors leaderboard (proposed/approved/rejected/confirmed), top-N
- claims_by_status histogram, audit event volume

Surfaces:
- human table (default)
- --json: stable, versioned schema (schema_version=1) documented in
  docs/metrics.md; ratios are null (not 0) when their denominator is empty
- --prometheus: textfile-collector exposition with HELP/TYPE headers;
  null gauges omitted so a 0 never lies about the denominator
- --since / --until accept durations (30d/12h/2w) or ISO dates; --stale-days
  and --top are configurable

Closes vouchdev#192.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@plind-junior

Copy link
Copy Markdown
Member

LGTM!

@plind-junior
plind-junior merged commit 1c4ccea into vouchdev:test Jun 10, 2026
5 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Jun 17, 2026
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: vouch metrics — first-class observability

2 participants