Skip to content

feat(review): receipt auto-approve on by default, duplicate-safe drain#522

Merged
plind-junior merged 1 commit into
testfrom
feat/receipt-auto-approve-default
Jul 17, 2026
Merged

feat(review): receipt auto-approve on by default, duplicate-safe drain#522
plind-junior merged 1 commit into
testfrom
feat/receipt-auto-approve-default

Conversation

@plind-junior

@plind-junior plind-junior commented Jul 17, 2026

Copy link
Copy Markdown
Member

a first user who wires claude code expects captured memory to just work. it doesn't: the starter config ships auto_approve_on_receipt: false, nothing names the flag, and even with it flipped on, real sessions strand claims in the pending queue. this pr makes the receipt gate the default and fixes the two holes that surfaced the moment it was exercised on a real project.

the default flip: a fresh kb now ships review.auto_approve_on_receipt: true (and require_human_approval: false — an advisory key no code path reads). claims whose byte-offset receipts verify against their source are approved mechanically; pages (session summaries included), entities, relations, and any claim that cannot quote its source still wait for a human. the review gate is structurally unchanged — the receipt check is the reviewer, exactly the narrowly-scoped condition phase d specified.

hole one: duplicates. a session that restates an already-durable fact re-derives the same claim id. auto_approve_receipts crashed on it ("cannot approve: claim … already exists"), and capture answer swallowed the same error and left the proposal pending forever — the queue grew every session, which reads as "auto-approve is broken". the new resolve_pending_receipt_claim closes identical-text duplicates mechanically (rejected, "duplicate: identical claim already durable") and leaves an id held by different text pending — that's a real conflict, a human call. both the drain and capture answer share it.

hole two: the backlog. nothing ever drained verifiable claims filed while the gate was off — the drain was only reachable through vouch ingest. finalize-all (the sessionstart hook) now runs it, so flipping the flag on an existing kb heals the queue on the next session start, and the no-captures-dir path drains too.

tests: new default pinned (fresh kb, receipt self-approval clears with no config edit), duplicate rejected not crashed, id-conflict left pending, recapture leaves no pending duplicates, sessionstart drain clears a backlog; the three gate-off tests now disable the gate explicitly. make check green (pytest, mypy, ruff).

note: readme step 4 here touches the same region as #521 (docs pr to main documenting the old opt-in); whichever lands second needs a trivial conflict resolution, and the #521 wording should flip to "on by default" once this releases.

Summary by CodeRabbit

  • New Features

    • Receipt-verified claims are now auto-approved by default when their source evidence is fully verifiable.
    • Pending receipt-backed claims are processed at session start and during answer capture.
    • Duplicate claims are automatically identified, while conflicting content remains pending for review.
    • Auto-approval can be disabled in configuration to require human review for all claims.
  • Documentation

    • Updated setup and approval guidance to explain the new defaults, review requirements, and configuration options.

the starter config now ships review.auto_approve_on_receipt: true (and
require_human_approval: false, an advisory key no code path reads): a
fresh kb auto-approves captured claims whose byte-offset receipts
verify, so recall works out of the box with no review pass. pages,
entities, relations, and claims that cannot quote their source still
wait for a human — the gate is structurally unchanged.

making the default real exposed two holes, both fixed here:

- a claim re-deriving text that is already durable crashed the drain
  ("cannot approve: claim … already exists") and, on the capture-answer
  path, was swallowed and left pending forever — every session that
  restated a known fact grew the queue. resolve_pending_receipt_claim
  now closes such duplicates mechanically (rejected, "duplicate:
  identical claim already durable"); an id held by different text is a
  real conflict and stays pending for a human. capture answer and the
  drain share this resolution.

- nothing ever drained a backlog of verifiable claims filed while the
  gate was off. finalize-all (the sessionstart hook) now runs the
  drain, so flipping the flag on an existing kb heals it on the next
  session start.

observed in the field: a fresh 1.4.0 install whose rollup and repeated
answers piled up pending despite the flag being on.
@github-actions github-actions Bot added docs documentation, specs, examples, and repo guidance cli command line interface adapters agent host adapters and install manifests storage kb storage, migrations, schemas, and proposals tests tests and fixtures size: M 200-499 changed non-doc lines labels Jul 17, 2026
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The starter configuration now enables receipt-verified auto-approval. Pending receipt claims resolve through centralized duplicate and conflict handling, while session finalization drains eligible backlogs. Capture, extraction, documentation, and tests reflect the updated behavior.

Changes

Receipt approval flow

Layer / File(s) Summary
Approval defaults and documentation
src/vouch/storage.py, docs/getting-started.md, README.md, CHANGELOG.md, adapters/claude-code/.claude/settings.json, src/vouch/cli.py
Receipt-verified claims auto-approve by default; unverifiable content remains pending, and configuration can disable the receipt gate.
Pending claim resolution
src/vouch/proposals.py, tests/test_receipt_auto_approve.py
Pending claims use centralized gate checks; identical durable claims are rejected as duplicates, while conflicting text remains pending.
Capture integration and validation
src/vouch/capture.py, tests/test_capture.py, tests/test_capture_answer.py, tests/test_extract.py
capture answer uses centralized resolution, finalize_all_except drains receipt backlogs, and tests cover approval counts, duplicates, conflicts, and disabled gates.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant capture_answer
  participant resolve_pending_receipt_claim
  participant KBStore
  capture_answer->>resolve_pending_receipt_claim: resolve receipt-filed proposal
  resolve_pending_receipt_claim->>KBStore: check durable claim by id
  KBStore-->>resolve_pending_receipt_claim: matching or conflicting claim
  resolve_pending_receipt_claim-->>capture_answer: Claim, duplicate rejection, or pending result
Loading

Possibly related PRs

Suggested reviewers: dripsmvcp

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.05% 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 accurately summarizes the main changes: default receipt auto-approval and duplicate-safe draining.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/receipt-auto-approve-default

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.

@github-actions github-actions Bot added the ci: passing ci is green label Jul 17, 2026

@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

🤖 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 `@src/vouch/proposals.py`:
- Around line 495-503: Update resolve_pending_receipt_claim to call
_claim_receipts_verify(store, proposal) before evaluating the approver_role
review gate, and return None immediately when verification fails. Keep the
existing trusted-agent and auto-approval checks only after this upfront
verification so the function processes exclusively verified claims.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3e3d4400-7899-4ffb-a441-74f8fed635eb

📥 Commits

Reviewing files that changed from the base of the PR and between 74abc8e and 42ebbcc.

📒 Files selected for processing (12)
  • CHANGELOG.md
  • README.md
  • adapters/claude-code/.claude/settings.json
  • docs/getting-started.md
  • src/vouch/capture.py
  • src/vouch/cli.py
  • src/vouch/proposals.py
  • src/vouch/storage.py
  • tests/test_capture.py
  • tests/test_capture_answer.py
  • tests/test_extract.py
  • tests/test_receipt_auto_approve.py

Comment thread src/vouch/proposals.py
Comment on lines +495 to +503
if proposal.kind != ProposalKind.CLAIM:
return None
review_cfg = _review_config(store)
trusted = review_cfg.get("approver_role") == "trusted-agent"
receipted = bool(
review_cfg.get("auto_approve_on_receipt")
) and _claim_receipts_verify(store, proposal)
if not (trusted or receipted):
return None

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Enforce receipt verification before checking the review gate.

If approver_role: trusted-agent is enabled, trusted will be True, allowing resolve_pending_receipt_claim to bypass the _claim_receipts_verify check. Because auto_approve_receipts iterates over all pending proposals, this bug causes the capture finalize-all session hook to silently approve all pending claims (including those without receipts or with forged receipts), violating the intended behavior of only draining receipt-verified claims.

Enforce the receipt verification check upfront so the function strictly operates on verified claims.

🐛 Proposed fix
     if proposal.kind != ProposalKind.CLAIM:
         return None
+    if not _claim_receipts_verify(store, proposal):
+        return None
     review_cfg = _review_config(store)
     trusted = review_cfg.get("approver_role") == "trusted-agent"
-    receipted = bool(
-        review_cfg.get("auto_approve_on_receipt")
-    ) and _claim_receipts_verify(store, proposal)
+    receipted = bool(review_cfg.get("auto_approve_on_receipt"))
     if not (trusted or receipted):
         return None
📝 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
if proposal.kind != ProposalKind.CLAIM:
return None
review_cfg = _review_config(store)
trusted = review_cfg.get("approver_role") == "trusted-agent"
receipted = bool(
review_cfg.get("auto_approve_on_receipt")
) and _claim_receipts_verify(store, proposal)
if not (trusted or receipted):
return None
if proposal.kind != ProposalKind.CLAIM:
return None
if not _claim_receipts_verify(store, proposal):
return None
review_cfg = _review_config(store)
trusted = review_cfg.get("approver_role") == "trusted-agent"
receipted = bool(review_cfg.get("auto_approve_on_receipt"))
if not (trusted or receipted):
return None
🤖 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/proposals.py` around lines 495 - 503, Update
resolve_pending_receipt_claim to call _claim_receipts_verify(store, proposal)
before evaluating the approver_role review gate, and return None immediately
when verification fails. Keep the existing trusted-agent and auto-approval
checks only after this upfront verification so the function processes
exclusively verified claims.

@plind-junior
plind-junior merged commit dec9e2b into test Jul 17, 2026
17 of 20 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Jul 18, 2026
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

adapters agent host adapters and install manifests ci: passing ci is green cli command line interface docs documentation, specs, examples, and repo guidance size: M 200-499 changed non-doc lines storage kb storage, migrations, schemas, and proposals tests tests and fixtures

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant