feat(review): receipt auto-approve on by default, duplicate-safe drain#522
Conversation
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.
WalkthroughThe 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. ChangesReceipt approval flow
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
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (12)
CHANGELOG.mdREADME.mdadapters/claude-code/.claude/settings.jsondocs/getting-started.mdsrc/vouch/capture.pysrc/vouch/cli.pysrc/vouch/proposals.pysrc/vouch/storage.pytests/test_capture.pytests/test_capture_answer.pytests/test_extract.pytests/test_receipt_auto_approve.py
| 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 |
There was a problem hiding this comment.
🎯 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.
| 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.
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(andrequire_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_receiptscrashed on it ("cannot approve: claim … already exists"), andcapture answerswallowed the same error and left the proposal pending forever — the queue grew every session, which reads as "auto-approve is broken". the newresolve_pending_receipt_claimcloses 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 checkgreen (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
Documentation