From 578b569882a372db6d335b94534cde2606056930 Mon Sep 17 00:00:00 2001 From: Harsh Raj Singhania Date: Thu, 10 Sep 2026 19:18:27 +0530 Subject: [PATCH 1/3] fix: reject invalid ngram and threshold on ReceiptBuilder --- src/answerproof/builder.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/answerproof/builder.py b/src/answerproof/builder.py index abedb7a..71ab6ec 100644 --- a/src/answerproof/builder.py +++ b/src/answerproof/builder.py @@ -36,9 +36,15 @@ class ReceiptBuilder: """Collects the facts of one RAG request and produces a signed receipt.""" def __init__(self, signing_key: SigningKey, *, ngram: int = 3, threshold: float = 0.5): + if not isinstance(ngram, int) or isinstance(ngram, bool) or ngram < 1: + raise ValueError("ngram must be an integer >= 1") + if not isinstance(threshold, (int, float)) or isinstance(threshold, bool): + raise ValueError("threshold must be a number between 0 and 1 inclusive") + if threshold < 0 or threshold > 1: + raise ValueError("threshold must be a number between 0 and 1 inclusive") self._signing_key = signing_key self._ngram = ngram - self._threshold = threshold + self._threshold = float(threshold) self._query: str | None = None self._answer: str | None = None self._principal = Principal(id="anonymous") From 547484808efe20a905c08f8c31b3e975b465458d Mon Sep 17 00:00:00 2001 From: Harsh Raj Singhania Date: Thu, 10 Sep 2026 19:18:48 +0530 Subject: [PATCH 2/3] test: cover ReceiptBuilder ngram and threshold validation --- tests/test_builder.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/test_builder.py b/tests/test_builder.py index d52d327..aa47fa0 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -93,3 +93,28 @@ def test_source_order_is_preserved(sk): b.add_source(f"s{i}", content=f"content {i}") receipt = b.finalize() assert [s.id for s in receipt.payload.sources] == [f"s{i}" for i in range(5)] + + +def test_rejects_zero_ngram(sk): + with pytest.raises(ValueError, match="ngram"): + ReceiptBuilder(sk, ngram=0) + + +def test_rejects_negative_ngram(sk): + with pytest.raises(ValueError, match="ngram"): + ReceiptBuilder(sk, ngram=-1) + + +def test_rejects_threshold_above_one(sk): + with pytest.raises(ValueError, match="threshold"): + ReceiptBuilder(sk, threshold=1.5) + + +def test_rejects_threshold_below_zero(sk): + with pytest.raises(ValueError, match="threshold"): + ReceiptBuilder(sk, threshold=-0.1) + + +def test_accepts_boundary_threshold(sk): + ReceiptBuilder(sk, ngram=1, threshold=0.0) + ReceiptBuilder(sk, ngram=1, threshold=1.0) From 674853db758e4210eb457de4f22c0c2312841b89 Mon Sep 17 00:00:00 2001 From: Harsh Raj Singhania Date: Thu, 10 Sep 2026 19:19:09 +0530 Subject: [PATCH 3/3] docs: note ReceiptBuilder ngram/threshold validation --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65d0971..fa7505b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Fixed +- `ReceiptBuilder` now rejects `ngram < 1` and out-of-range `threshold` values at construction so degenerate n-gram sets cannot produce a perfect grounding score. + - Explicitly supplied source contents must now cover every source in the receipt; empty and partial mappings report the missing source IDs and fail verification.