Skip to content

test(v3): property tests for the empty-bloom needle guard (CIP-3665) - #431

Merged
freshtonic merged 3 commits into
mainfrom
james/cip-3665-property-test-for-the-empty-bloom-needle-guard-in
Aug 5, 2026
Merged

test(v3): property tests for the empty-bloom needle guard (CIP-3665)#431
freshtonic merged 3 commits into
mainfrom
james/cip-3665-property-test-for-the-empty-bloom-needle-guard-in

Conversation

@freshtonic

@freshtonic freshtonic commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Adds a property test for the empty-bloom needle guard in eql_v3.matches, closing the loop on the review follow-up from #421 (#421 (comment)). Linear: CIP-3665.

What

PR #421 fixed the empty-needle bug — an empty needle bloom ({}, from a sub-trigram search string) was @>-contained by every value, so a bare containment matched EVERY row — with a generated guard (empty_bloom_guard in crates/eql-codegen/templates/functions/wrapper.sql.j2) and pinned it with example-based tests over the v3_text_empty_bloom fixture. This PR generalises those examples into a property test over generated plaintexts, freshly encrypted through ZeroKMS each run.

New suite member: tests/sqlx/tests/encrypted_domain/property/empty_bloom_guard.rs (e2e suite, proptest-e2e-gated, listed in the test:sqlx:e2e task as the task's comment requires).

Properties

Each proptest case generates a batch of strings — sub-trigram ([a-z]{0,2}, including "") and full-length ([a-z]{3,8}) — plus deterministic seeds ("", "a", "pq", "aardvark", a duplicate of the first value, and a 3-char prefix needle), batch-encrypts them with the match index (one ZeroKMS round trip), and checks over every ordered pair (value, needle):

  1. Cross-emptiness never matches — a needle with a non-empty bloom never matches a value with an empty bloom, and vice-versa.
  2. The LIKE '' shape — an empty needle matches a value iff that value's bloom is also empty (never the pre-guard match-everything).
  3. Operator/function equivalence under the guardcol @@ needle agrees with the by-name eql_v3.matches(col, needle) and with the (domain, jsonb) convenience overload on every generated pair.
  4. The premise — every sub-trigram plaintext (< 3 chars) extracts an empty bloom, and every all-letter plaintext at/above the floor extracts a non-empty one: the generated-input generalisation of empty_bloom_needle_is_actually_empty, which pins this for two hand-picked values.

On top of the guard's truth table it also asserts the deterministic bloom hits (a bloom admits false positives, never false negatives): equal plaintexts match across independently encrypted ciphertexts, and a substring needle matches its superstring value. Pairs with two non-empty blooms and no substring relationship assert only property 3 (a hit there may be a legitimate bloom false positive — no plaintext oracle exists).

Why e2e, not fixture

Sub-trigram plaintexts exist in no generated fixture (eql-domains::TEXT_FIXTURES has none — its shortest value is 3 chars), and proptest can only generate plaintexts, not ciphertext, so the suite must pay for fresh encryption. Classification is by measured bloom cardinality (cardinality(eql_v3.match_term(...))), not by plaintext length, so properties 1–3 stay honest for any tokenizer behaviour; property 4 separately pins the length ⇒ cardinality premise for the all-letter generator.

The fixture suite's deliberate exclusion of the empty string (issue #262) is untouched — that is an _ord_ore CHECK concern (ob: []); this suite encrypts with the match index only, so "" is generated on purpose as the canonical LIKE '' needle.

Notes

  • Shrinking and failure persistence are disabled, matching the rest of the e2e suite (every shrink attempt is another ZeroKMS batch; fresh ciphertext can't be replayed).
  • No additional pathological cases surfaced: all generated batches (including "", single-char needles, duplicate plaintexts across independent ciphertexts, and prefix needles) behave exactly per the guard's truth table.

Testing

Ran locally against a freshly migrated database (this branch's built EQL) with real ZeroKMS credentials:

cargo test --features proptest-e2e --test encrypted_domain empty_bloom_guard
# test property::empty_bloom_guard::empty_bloom_guard_oracle ... ok
# test result: ok. 1 passed; 0 failed (8 proptest cases, one ZeroKMS batch each)

cargo fmt and cargo clippy --features proptest-e2e --tests are clean.

Summary by CodeRabbit

  • Tests

    • Added end-to-end property testing for empty-bloom safeguards in encrypted text matching.
    • Validated matching behavior across empty and non-empty bloom filters, including equality, substring, and overload scenarios.
    • Included generated sub-trigram and deterministic test cases in the SQL end-to-end test suite.
  • Documentation

    • Documented the new empty-bloom property test coverage and linked its example-based counterpart.

Follow-up from the PR #421 review: generalise the example-based
empty-bloom guard tests into a property test over generated plaintexts,
freshly encrypted through ZeroKMS each run.

New e2e suite member
tests/sqlx/tests/encrypted_domain/property/empty_bloom_guard.rs checks,
over every ordered (value, needle) pair in each generated batch:

1. cross-emptiness never matches (a non-empty needle never matches an
   empty-bloom value, and vice-versa);
2. an empty needle matches a value iff that value's bloom is also empty
   (the LIKE '' shape — never the pre-guard match-everything);
3. `@@` agrees with the by-name eql_v3.matches (and its (domain, jsonb)
   overload) on every pair — operator/function equivalence under the
   guard;
4. the premise: sub-trigram plaintexts (< 3 chars) extract an empty
   bloom and all-letter plaintexts at/above the floor extract a
   non-empty one — the generated-input generalisation of
   empty_bloom_needle_is_actually_empty.

Plus the deterministic bloom hits (equal plaintexts across
independently encrypted ciphertexts; substring needles), since a bloom
admits false positives but never false negatives. Pairs of two
non-empty, unrelated blooms assert only property 3 — a hit there may be
a legitimate false positive.

Sub-trigram inputs exist in no generated fixture (TEXT_FIXTURES has
none) and proptest can only generate plaintexts, so the suite is
e2e/proptest-e2e-gated and named in the test:sqlx:e2e task per that
task's comment. Classification is by measured bloom cardinality, not
plaintext length. The empty string is generated on purpose: issue #262
is an _ord_ore CHECK concern, and this suite encrypts with the match
index only.
@freshtonic
freshtonic requested a review from tobyhede August 4, 2026 05:21
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4d5dec0f-0a75-42aa-8ab0-f8146e4db972

📥 Commits

Reviewing files that changed from the base of the PR and between 2cfadb0 and ab6806d.

📒 Files selected for processing (3)
  • tests/sqlx/tests/encrypted_domain/property/README.md
  • tests/sqlx/tests/encrypted_domain/property/empty_bloom_guard.rs
  • tests/sqlx/tests/encrypted_domain/property/mod.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/sqlx/tests/encrypted_domain/property/mod.rs
  • tests/sqlx/tests/encrypted_domain/property/README.md

📝 Walkthrough

Walkthrough

The PR adds a feature-gated end-to-end property-test suite for empty-bloom matching. It validates bloom premises, matching equivalence, deterministic matches, permitted false positives, and integrates the suite into the SQLx e2e task.

Changes

Empty-bloom guard validation

Layer / File(s) Summary
Test model and input generation
tests/sqlx/tests/encrypted_domain/property/empty_bloom_guard.rs, tests/sqlx/tests/encrypted_domain/property/README.md
Defines match-row data, SQL helpers, generated and deterministic plaintext batches, and suite documentation.
Encryption and bloom premises
tests/sqlx/tests/encrypted_domain/property/empty_bloom_guard.rs
Batch-encrypts inputs, measures bloom cardinalities, and validates empty and non-empty bloom premises.
Guard assertions and e2e wiring
tests/sqlx/tests/encrypted_domain/property/empty_bloom_guard.rs, tests/sqlx/tests/encrypted_domain/property/mod.rs, mise.toml
Checks operator and overload equivalence, deterministic matches, and permitted false positives. Registers the suite and adds it to the e2e task.

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

Sequence Diagram(s)

sequenceDiagram
  participant Proptest
  participant ZeroKMS
  participant SQLx
  Proptest->>ZeroKMS: Batch-encrypt generated plaintexts
  ZeroKMS-->>Proptest: Return encrypted payloads
  Proptest->>SQLx: Measure bloom cardinalities
  SQLx-->>Proptest: Return bloom classifications
  Proptest->>SQLx: Execute @@ and eql_v3.matches assertions
  SQLx-->>Proptest: Return matching results
Loading

Possibly related PRs

Suggested reviewers: tobyhede

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the new property tests for the empty-bloom needle guard and identifies the affected version.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 james/cip-3665-property-test-for-the-empty-bloom-needle-guard-in

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.

@blacksmith-sh

This comment has been minimized.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 `@tests/sqlx/tests/encrypted_domain/property/empty_bloom_guard.rs`:
- Around line 269-285: Update seeded so the fixed full-length "aardvark" seed is
appended before computing prefix, ensuring prefix always selects a value when
the original batch contains only sub-trigram strings. Preserve the existing
duplicate and deterministic seed additions while keeping the substring-needle
pair guaranteed for every generated batch.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0ae8e8bc-b7a5-4cff-b611-3a7f66c0dcab

📥 Commits

Reviewing files that changed from the base of the PR and between f878f9a and 2cfadb0.

📒 Files selected for processing (4)
  • mise.toml
  • tests/sqlx/tests/encrypted_domain/property/README.md
  • tests/sqlx/tests/encrypted_domain/property/empty_bloom_guard.rs
  • tests/sqlx/tests/encrypted_domain/property/mod.rs

Comment thread tests/sqlx/tests/encrypted_domain/property/empty_bloom_guard.rs

@freshtonic freshtonic left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Comment-only review.\n\nFinding: the new tracked files contain the private identifier , which makes fail and in turn fails . CI identifies three occurrences:\n\n- \n- \n- \n\nPlease describe the public behavior directly (or link a public GitHub issue) in those locations. The property/e2e jobs themselves are otherwise green.

@freshtonic freshtonic left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Correction to my preceding review (shell formatting stripped identifiers): CIP-3665 causes test:public_identifiers and ci-required to fail. Occurrences are property/README.md line 86, property/empty_bloom_guard.rs line 2, and property/mod.rs line 39 under tests/sqlx/tests/encrypted_domain.

… (CIP-3665)

The SQL doc validation CI job (mise run test:public_identifiers) rejects
private issue-tracker identifiers in tracked files. Keep only the public
PR #421 reference in the property-test docs and comments.
seeded() computed `prefix` before appending the fixed seeds, so a batch of only
sub-trigram generated values left `prefix = None` and the substring-needle
deterministic pair never fired — despite the suite doc claiming it always does.
Append the fixed seeds (incl. the full-length "aardvark") first so a value at or
above the trigram floor always exists to prefix.

Addresses CodeRabbit review on PR #431.

CIP-3665
@freshtonic
freshtonic added this pull request to the merge queue Aug 5, 2026
Merged via the queue into main with commit aab1d12 Aug 5, 2026
20 checks passed
@freshtonic
freshtonic deleted the james/cip-3665-property-test-for-the-empty-bloom-needle-guard-in branch August 5, 2026 12:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants