fix(receipts): reject empty-quote receipts instead of verifying them#513
fix(receipts): reject empty-quote receipts instead of verifying them#513davion-knight wants to merge 2 commits into
Conversation
verify_receipt()'s guard only checked `quote is None`, not an empty
string. an Evidence with quote="" and byte_start == byte_end decodes
to "", trivially equals the empty quote, and returns VERIFIED --
despite carrying no actual quoted text. this contradicts the
function's own docstring ("no quote to compare" -> NO_RECEIPT) and the
sibling locate_span(), which already refuses to mint a receipt for an
empty quote on the propose path. the verify path had no equivalent
guard, so an empty-quote evidence landing on disk some other way
(bundle import, sync) would mechanically "verify" and clear the
auto-approval receipt gate.
`not quote` catches both None and "" with no other call sites needing
changes, since verify_evidence and evaluate_claim_receipts both
delegate to verify_receipt.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
Walkthrough
ChangesReceipt verification
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 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/receipts.py`:
- Line 61: Update the empty-quote guard in verify_evidence to use the same
falsy-quote handling as verify_receipt, so an empty evidence.quote with a
missing source returns the no-receipt result instead of forged. Preserve the
existing behavior for non-empty quotes.
🪄 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: aa88a531-b829-4529-b62f-3c4326e2f63a
📒 Files selected for processing (3)
CHANGELOG.mdsrc/vouch/receipts.pytests/test_receipts.py
verify_evidence carries its own copy of the empty-span guard ahead of the source read (a missing source must never mask "nothing to compare" as forged). it had the same quote is None gap verify_receipt did: quote="" paired with a missing source fell through to FORGED instead of NO_RECEIPT. same fix, one call site up. per coderabbit review on vouchdev#513.
|
fixed — verify_evidence carried its own copy of the same guard ahead of the source-read (so a missing source can't mask "nothing to compare" as forged), and had the identical quote is None gap. same fix applied there: quote="" paired with a missing source now correctly returns NO_RECEIPT instead of FORGED. added test_verify_evidence_no_receipt_for_empty_quote_and_missing_source to cover it. |
|
heads up — the |
summary
verify_receipt()'s null-guard only checkedquote is None, not an empty string. anEvidencewithquote=""andbyte_start == byte_end(a zero-length span) decodes to"", trivially equals the empty quote, and returnsVERIFIED— despite carrying no actual quoted text.this contradicts the function's own docstring: "Returns
NO_RECEIPTwhen the evidence carries no verifiable span (either offset missing, or no quote to compare)." the siblinglocate_span()already refuses to mint a receipt for an empty quote (if not needle: return None, with its own testtest_locate_span_returns_none_for_empty_quote) — butverify_receipthad no equivalent guard on the verify side.why it matters
Evidence.quotehas no min-length constraint at the model level. the normal propose path can't trigger this today (proposals.pyalways builds evidence viareceipt_for_quote, which delegates tolocate_span's guard) — butbundle.py'simport_applyandsync.py'ssync_applywriteevidence/*.yamlstraight to disk after only pydantic-schema validation, no receipt-content check. a hand-crafted bundle/sync source can land an empty-quote, zero-length-span evidence, and any claim citing it would mechanically clear the auto-approval receipt gate (evaluate_claim_receipts) despite proving nothing about the source.what changed
one line in
src/vouch/receipts.py::verify_receipt:quote is None→not quote, so bothNoneand""hit the sameNO_RECEIPTpath. no other call site needs a change —verify_evidenceandevaluate_claim_receiptsboth delegate toverify_receipt.test plan
new test
test_no_receipt_when_quote_is_empty_stringintests/test_receipts.py— assertsverify_receiptreturnsNO_RECEIPTforquote=""/byte_start == byte_end. fails on the old code (VERIFIED), passes with the fix.all clean. the full suite has 5 pre-existing order-dependent failures (
test_fsck_clean_kb_prints_clean_and_exits_zero,test_search_fts5_backend_label,test_deindex_removes_fts_and_prov, and 2 parametrized cases intest_covered_methods_attach_sidebar_when_kb_has_recent_claims) that reproduce identically on a cleantestcheckout with no changes — confirmed viagit stash+ rerun, unrelated to this PR.Summary by CodeRabbit
Bug Fixes
Tests