Skip to content

Commit 79ff4c4

Browse files
committed
fix(receipts): apply the same falsy-quote guard in verify_evidence
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 #513.
1 parent 9e078fa commit 79ff4c4

3 files changed

Lines changed: 29 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,17 @@ All notable changes to vouch are documented here. Format follows
5252
the same resolution now backs `capture answer`, which previously left
5353
re-captured duplicates pending forever.
5454

55+
### Fixed
56+
- `receipts.verify_receipt()` and `receipts.verify_evidence()` no longer
57+
report `VERIFIED`/fall through past their guard for an empty-quote,
58+
zero-length byte span. both guards only checked `quote is None`, not an
59+
empty string, so `quote=""` with `byte_start == byte_end` decoded to
60+
`""`, trivially equaled the empty quote, and `verify_receipt` returned
61+
`VERIFIED` -- despite carrying no actual quoted text, contradicting the
62+
function's own docstring. `locate_span` already refused to mint such a
63+
receipt on the propose path; this closes the same gap on the verify
64+
path, reachable via bundle import or sync.
65+
5566
## [1.4.0] — 2026-07-17
5667

5768
### Added
@@ -111,14 +122,6 @@ All notable changes to vouch are documented here. Format follows
111122
in config.yaml (#476).
112123

113124
### Fixed
114-
- `receipts.verify_receipt()` no longer reports `VERIFIED` for an
115-
empty-quote, zero-length byte span. the guard only checked `quote is
116-
None`, not an empty string, so `quote=""` with `byte_start ==
117-
byte_end` decoded to `""`, trivially equaled the empty quote, and
118-
verified -- despite carrying no actual quoted text, contradicting the
119-
function's own docstring. `locate_span` already refused to mint such
120-
a receipt on the propose path; this closes the same gap on the
121-
verify path, reachable via bundle import or sync.
122125
- approve/reject/expire record the audit event *before* moving the
123126
proposal to decided/. a crash between the two used to leave a durable
124127
decision with no authoritative history; it now leaves a pending

src/vouch/receipts.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ def verify_evidence(store: KBStore, evidence: Evidence) -> ReceiptResult:
184184
"""
185185
from .storage import ArtifactNotFoundError
186186

187-
if evidence.byte_start is None or evidence.byte_end is None or evidence.quote is None:
187+
if evidence.byte_start is None or evidence.byte_end is None or not evidence.quote:
188188
return ReceiptResult(ReceiptStatus.NO_RECEIPT, "no byte-offset span")
189189
try:
190190
source_bytes = store.read_source_content(evidence.source_id)

tests/test_receipts.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,23 @@ def test_verify_evidence_not_verified_when_source_missing(store: KBStore) -> Non
173173
assert result.verified is False
174174

175175

176+
def test_verify_evidence_no_receipt_for_empty_quote_and_missing_source(
177+
store: KBStore,
178+
) -> None:
179+
# verify_evidence has its own pre-check ahead of the source-read (so a
180+
# missing source doesn't mask a plain "nothing to compare" case) --
181+
# it must apply the same falsy-quote rule verify_receipt does, or an
182+
# empty quote paired with a missing source reports FORGED instead of
183+
# NO_RECEIPT, same bug as verify_receipt's, one call site up.
184+
ev = Evidence(
185+
id="e4", source_id="does-not-exist",
186+
locator="x", quote="", byte_start=0, byte_end=0,
187+
)
188+
result = verify_evidence(store, ev)
189+
assert result.status is ReceiptStatus.NO_RECEIPT
190+
assert result.verified is False
191+
192+
176193
# ---- the quote step: locate a span, or drop what cannot be quoted ----
177194

178195

0 commit comments

Comments
 (0)