Skip to content

feat(stack-encrypt): JSON operators Contains, ContainedBy and AtPath - #1209

Merged
coderdan merged 2 commits into
nextfrom
feat/stack-encrypt-json-operators
Oct 17, 2026
Merged

coderdan merged 2 commits into
nextfrom
feat/stack-encrypt-json-operators

Conversation

@coderdan

@coderdan coderdan commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Stacked on #1208. This PR declares the JSON operators of ADR-0009 decision 1 and pins what they require of a term. It is the part of #1187 that does not need a JSON index, and it says plainly what does.

  • op::Contains and op::ContainedBy, the containments (@> and <@). Exact: any query term answers them. They take one value, the document.
  • op::AtPath<Op>, written op::AtPath(op::Eq), applies another operator at a JSON path and inherits its fidelity through TermFor: an exact term answers AtPath<Eq> and AtPath<Lt>, and only a probabilistic one answers AtPath<Matches>. That is decision 4's "AtPath<Op> inherits the fidelity of Op" as a type rule, including through nested accessors.
  • Nothing answers them yet. This crate has no JSON index, and EQL v4 has no JSON type Stack Encrypt produces. A containment query has no default index and no Answers impl, and AtPath has a new arity, op::Pathed, a path and the leaf's input, with no op::Input until the JSON index exists, since the path becomes a selector only that index can derive. So a query with any of them does not compile on any plan, and the error says why.

What waits for the JSON index

The issue's second and third items, AtPath<Eq> as a value-selector term and the orderings at a path as a selector plus a leaf term, are the shapes of terms that only a JSON index can derive, so they land with it. The third item's verify at a path is moot under the revised ADR-0009: Probabilistic is Bloom-only and no probabilistic leaf exists, so AtPath<Op> over this crate's operators is exact.

Tests

  • Unit test: an exact term answers AtPath<Eq>, AtPath<Lt>, AtPath<Between> and a nested AtPath<AtPath<Eq>>; a MatchQuery answers AtPath<Matches>; the containments take any term.
  • Compile-fail: an EqualityTerm cannot answer AtPath<Matches>; op::Contains and op::AtPath(op::Eq) do not compile on a plan.

Test plan

  • cargo nextest run -p stack-encrypt --all-features: 584 passed.
  • Doctests, clippy with -D warnings with and without --all-features, rustdoc with warnings as errors, cargo fmt --check.
  • CI.

Closes #1187

https://claude.ai/code/session_01VYKAWbd361xDLmqBdLoUcB

Summary by CodeRabbit

  • New Features
    • Added JSON query operators for checking whether values contain or are contained by a term, and for applying an operator at a JSON path.
    • Path-based operators inherit the term requirements and fidelity of their underlying operator.
    • These operators are not yet supported by an index, so queries using them cannot currently be compiled.

@coderdan
coderdan requested a review from a team as a code owner October 14, 2026 07:10
@changeset-bot

changeset-bot Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 6eff76a

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai

coderabbitai Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 89e67ee9-8698-43dd-ba97-71a44ed7cf00

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: ed71cf2e-5e2c-4103-8601-14fc50215123
📥 Commits

Reviewing files that changed from the base of the PR and between 63331c7 and a63d2e7.

📒 Files selected for processing: 6
  • packages/stack-encrypt/CHANGELOG.md
  • packages/stack-encrypt/src/op/mod.rs
  • packages/stack-encrypt/tests/ui/op_at_path_inherits_fidelity.rs
  • packages/stack-encrypt/tests/ui/op_at_path_inherits_fidelity.stderr
  • packages/stack-encrypt/tests/ui/op_json_not_answered.rs
  • packages/stack-encrypt/tests/ui/op_json_not_answered.stderr

Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 1 remain after this review.

📝 Walkthrough

The change adds Contains, ContainedBy, and AtPath<Op> operator declarations, plus the Pathed arity and term requirements. Tests and changelog entries describe operator fidelity and current limits: no index answers these operators, and AtPath has no Input implementation.

Changes: JSON operators

Layer / File(s) Summary
Operator contracts and term requirements
packages/stack-encrypt/src/op/mod.rs, packages/stack-encrypt/CHANGELOG.md
Adds containment operators and AtPath<Op>, with Pathed arity and inherited leaf term requirements. Tests cover debug output and term fidelity. The changelog records that no index currently answers these operators.
Compile-time checks
packages/stack-encrypt/tests/ui/op_at_path_inherits_fidelity.rs, packages/stack-encrypt/tests/ui/op_at_path_inherits_fidelity.stderr, packages/stack-encrypt/tests/ui/op_json_not_answered.rs, packages/stack-encrypt/tests/ui/op_json_not_answered.stderr
UI fixtures capture the AtPath<Matches> fidelity requirement and report the missing index and Input<Pathed> support for the example JSON queries.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Feature

Suggested reviewers: freshtonic

Merge Risk: ⚪ Minimal · up to a63d2

No new merge-blocking issue is established for this PR. JSON queries remain unavailable until the planned index and input support are added.

Security architecture risk: ⚪ Minimal · up to a63d2

The new operators remain declarations rather than executable JSON queries on existing built-in plans. Existing input, index-selection, and term-fidelity requirements remain in place. No material security risk was found to be introduced or worsened by this change.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The introduced reachable surface is a library type contract. Caller-supplied JSON documents and paths do not gain a new executable route through existing built-in plans, so this PR does not establish new tenant, data-store, or credential exposure through these operators.

Trust Boundaries and Controls

  • observed — Index support remains tied to the input plaintext and required query-term type. Supports::query also retains a QueryToken whose constructor is crate-private. Adding operator markers does not grant callers authority to bypass the query chain or substitute an undeclared index.
Pre-merge checks: Passed 3 · Failed 2

Failed Failed checks: 2 warnings

Check name Status Explanation Resolution
Linked Issues check Warning Direct issue #1187 remains open and contains coding requirements beyond the implemented subset. packages/stack-encrypt/src/op/mod.rs adds Contains, ContainedBy, AtPath<Op>, Pathed, and the `… Implement the remaining #1187 selector term shapes and the required AtPath<Op> probabilistic and verify behavior, including automated tests. Alternatively, keep this PR partial and do not mark #1187 complete.
Docstring Coverage Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 7 functions across 3 files. (3 skipped: 3… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 3 passed
Check name Status Explanation
Out of Scope Changes check Passed The reviewed changes stay within #1187. The operator declarations, Pathed arity, fidelity bounds, compile-fail tests, and changelog entries document or test the JSON operator requirements. The no-in…
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely names the three JSON operators added by this pull request: Contains, ContainedBy, and AtPath.
Full details: Out of Scope Changes check

Explanation

The reviewed changes stay within #1187. The operator declarations, Pathed arity, fidelity bounds, compile-fail tests, and changelog entries document or test the JSON operator requirements. The no-index diagnostics directly support the issue's stated dependency.

Full details: Linked Issues check

Explanation

Direct issue #1187 remains open and contains coding requirements beyond the implemented subset. packages/stack-encrypt/src/op/mod.rs adds Contains, ContainedBy, AtPath&lt;Op&gt;, Pathed, and the TermFor fidelity bound. The UI tests cover the no-index diagnostics and the fidelity rejection. However, the PR does not add the required AtPath&lt;Eq&gt; value-selector term, the selector-plus-leaf terms for path orderings, or the Probabilistic path verification behavior required by #1187. The PR description confirms that these items are deferred to the JSON index.

Resolution

Implement the remaining #1187 selector term shapes and the required AtPath&lt;Op&gt; probabilistic and verify behavior, including automated tests. Alternatively, keep this PR partial and do not mark #1187 complete.

Full details: Docstring Coverage

Explanation

Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 7 functions across 3 files. (3 skipped: 3 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-14T07:14:06.575811Z 7489dba PR opened
🔒 Security Review ✅ Completed 2026-10-14T07:14:25.134969Z 7489dba PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Mutation testing (cargo-mutants, --in-diff, stack-auth + stack-encrypt)

caught missed unviable timeout
134 0 45 0

Every mutant in the changed lines was caught by a test.

@freshtonic freshtonic 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.

Review of this PR's diff against #1208.

AtPath<Op> gets its TermFor bound from Op, and that is correct. The compile-fail test pins AtPath<Matches> to a probabilistic term. No index answers the JSON operators yet, so this PR has no runtime effect today. No mutants (the changes are types only).

Blocking

  1. Contains and ContainedBy are declared exact. The only JSON index in this repository cannot answer them exactly. See the inline comment at src/op/mod.rs:515.

Suggested follow-up PR

  • Ordering at a path under an array. ADR-0009 shows AtPath<Gte> as col -> selector >= term. Under an array, the selector is a wildcard (ArrayWildcardItem), so several entries share it. Say which entry -> returns. If it returns one entry, an ordering at that path can miss a matching element, which is a false negative. Settle this before the JSON index PR.
  • null, [] and {}. Say whether SteVec emits a selector for a null leaf, an empty array and an empty object. If it does not, the needle {"a": []} or {"a": null} has no selector and matches every row under @>. Also, a stored {"a": null} is then contained by every needle under <@. Add these cases to the JSON index tests.
  • CI. The Test EQL PG17 shard failures come from the base. This PR does not cause them.

Comment thread packages/stack-encrypt/src/op/mod.rs
@coderdan
coderdan force-pushed the feat/stack-encrypt-ordering-operators branch from 69ef7b3 to 9895211 Compare October 16, 2026 11:16
@coderdan
coderdan force-pushed the feat/stack-encrypt-json-operators branch 2 times, most recently from 701fc22 to ca0af99 Compare October 16, 2026 11:17
@coderdan
coderdan force-pushed the feat/stack-encrypt-ordering-operators branch from 9895211 to 909eb50 Compare October 16, 2026 11:17
@coderdan
coderdan added this pull request to stack #1230 October 16, 2026 11:24
@coderdan
coderdan force-pushed the feat/stack-encrypt-json-operators branch from ca0af99 to e9e6336 Compare October 16, 2026 12:06
@coderdan
coderdan force-pushed the feat/stack-encrypt-ordering-operators branch from 909eb50 to 20513db Compare October 16, 2026 12:06
@blacksmith-sh

This comment has been minimized.

Copy link
Copy Markdown
Contributor
  • packages/stack-encrypt/src/op/mod.rs:181 and :203: Pathed takes no type parameter, so AtPath<Op> has arity Pathed whatever Op::Arity is. A later Input<Pathed> impl therefore can't tell AtPath<Eq> (a path and one value) from AtPath<Between> (a path and a pair). It can't choose Terms = T or (T, T), and the Pair handling (the InvertedRange check, deriving both bounds under one label) gets skipped or applied to the wrong operator. The unit test at :832 asserts AtPath<Between> works with RangeQuery. Something like Pathed<A: Arity> carrying Op::Arity is a public-API change, so it needs to land before the JSON index ships.
  • packages/stack-encrypt/src/op/mod.rs:549 and :833: the blanket TermFor<AtPath<Op>> (with Op: Operator) accepts AtPath<AtPath<Eq>> and AtPath<Contains>, and the test asserts the nested form. Neither is a single selector, and with the outer arity fixed at Pathed, the inner path has no slot in the input. A future JSON index would have to give these a meaning or reject them. Restricting Op to leaf operators prevents that.
  • packages/stack-encrypt/src/op/mod.rs:778: the DefaultIndex on_unimplemented note doesn't mention the JSON operators. It still tells the user to "name another index, such as Ope, with .via(index)" (snapshotted in tests/ui/op_json_not_answered.stderr), but no index answers Contains, ContainedBy or AtPath, so that advice only leads to a second Supports error. The AtPath refusal is also a bare E0599 on (&str, &str): Input<Pathed>, because Input has no curated diagnostic.
  • packages/stack-encrypt/src/op/mod.rs:45 and :97 (plus the module intro): the module doc and the Operator::Arity doc still describe only two arities ("[One] plaintext, or a [Pair]"), and the operator list in the intro leaves out Contains, ContainedBy and AtPath. Only the Arity trait doc was updated for Pathed.

Generated by Claude Code

… and AtPath

ADR-0009 decision 1 names three JSON operators. `op::Contains` and
`op::ContainedBy` are the containments (`@>` and `<@`), exact, and take
one value, the document. `op::AtPath<Op>`, written `op::AtPath(op::Eq)`,
applies another operator at a JSON path and inherits that operator's
fidelity through `TermFor`: an exact term answers `AtPath<Eq>` and
`AtPath<Lt>`, and only a probabilistic one answers `AtPath<Matches>`.

No index answers them. This crate has no JSON index, and EQL v4 has no
JSON type Stack Encrypt produces, so a containment query or a query at a
path does not compile on any plan, which the compile-fail tests pin.
`AtPath`'s arity is a new `op::Pathed`, a path and the leaf's input, and
no `op::Input` of it exists until the JSON index arrives, since the path
becomes a selector only that index can derive. The value-selector and
selector-plus-leaf term shapes the issue lists wait for the same index.

Closes #1187

Claude-Session: https://claude.ai/code/session_01VYKAWbd361xDLmqBdLoUcB
- `op::Pathed` carries the leaf operator's arity, `Pathed<A>`, so
  `AtPath<Eq>` takes `Pathed<One>` (a path and a value) and
  `AtPath<Between>` takes `Pathed<Pair>` (a path and a pair of bounds). A
  JSON index's `Input` impls can then derive one term or two as the leaf
  needs. A unit test pins the three arities.
- `AtPath<Op>` takes only a leaf operator, the new `op::Leaf`: `Eq`, `Lt`,
  `Lte`, `Gt`, `Gte`, `Between` and `Matches`. `AtPath<Contains>`,
  `AtPath<ContainedBy>` and `AtPath<AtPath<_>>` are no longer operators,
  since none of them is one selector and value; `op::LeafArity` (sealed:
  `One` and `Pair`) keeps an accessor from nesting.
  `tests/ui/op_at_path_not_a_leaf.rs` pins the diagnostic.
- The `DefaultIndex` diagnostic says no index answers the JSON operators
  yet, instead of only pointing at `.via(index)`, which led to a second
  error. `op::Input` gains a diagnostic naming what each arity takes.
- The module intro lists the JSON operators and the `Pathed` arity, and
  `Operator::Arity`'s doc names all three arities.
@coderdan
coderdan force-pushed the feat/stack-encrypt-ordering-operators branch from 63331c7 to 8daa5ae Compare October 17, 2026 02:29
@coderdan
coderdan force-pushed the feat/stack-encrypt-json-operators branch from a63d2e7 to 6eff76a Compare October 17, 2026 02:29
@coderdan

Copy link
Copy Markdown
Contributor Author

Fixes for the second review are in 6eff76a.

tobyhede

  1. Pathed ignored the leaf's arity. Fixed: the arity is now
    Pathed<A>, and AtPath<Op> has arity Pathed<Op::Arity>. So
    AtPath<Eq> takes Pathed<One> and AtPath<Between> takes
    Pathed<Pair>, and a JSON index's Input impls can tell a path and a
    value from a path and a pair. A unit test pins the three arities. This
    changes the public API before the JSON index ships, as you asked.
  2. AtPath<AtPath<Eq>> and AtPath<Contains> were accepted. Fixed:
    AtPath<Op> now needs Op: op::Leaf. Leaf is implemented for Eq,
    Lt, Lte, Gt, Gte, Between and Matches, and its arity must
    be One or Pair (op::LeafArity, sealed), so an accessor never
    nests. The unit test no longer asserts the nested form, and
    tests/ui/op_at_path_not_a_leaf.rs pins the new diagnostic for both
    cases.
  3. The DefaultIndex note pointed JSON queries at .via(index).
    Fixed: the note now ends "no index answers op::Contains,
    op::ContainedBy or op::AtPath yet, since this crate has no JSON
    index". op::Input also has a curated diagnostic now. Since
    feat(stack-encrypt): ordering operators Lt, Lte, Gt, Gte and Between, and remove query_index #1208's fix moved the F: Input<Op::Arity> bound from QueryBuilder's
    impl block onto query, the AtPath refusal in
    op_json_not_answered.stderr is now an E0277 at the value, with the
    Input message ("(&str, &str) is not what a query of arity
    Pathed<One> takes"), ahead of the E0599. The snapshot is re-recorded.
  4. The module and Operator::Arity docs were out of date. Fixed: the
    intro now lists Lte, Gt, Gte, Contains, ContainedBy and
    AtPath, and says no index answers the JSON operators yet. The arity
    paragraph and Operator::Arity now name all three arities.

freshtonic's suggested follow-ups

@coderdan
coderdan removed this pull request from stack #1230 October 17, 2026 05:57
Base automatically changed from feat/stack-encrypt-ordering-operators to next October 17, 2026 06:00
@coderdan
coderdan merged commit 40cf522 into next Oct 17, 2026
68 of 94 checks passed
@coderdan
coderdan deleted the feat/stack-encrypt-json-operators branch October 17, 2026 06:01
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.

stack-encrypt: JSON operators Contains, ContainedBy and AtPath

3 participants