Skip to content

feat(stack-encrypt)!: derive every order term over its ADR-0002 order key, and order floats - #1242

Merged
coderdan merged 1 commit into
nextfrom
feat/stack-encrypt-order-keys
Oct 19, 2026
Merged

coderdan merged 1 commit into
nextfrom
feat/stack-encrypt-order-keys

Conversation

@coderdan

@coderdan coderdan commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Stack Encrypt (our Rust encryption engine, which the Go SDK runs as a WebAssembly guest) can now answer <, >, BETWEEN and ORDER BY on floats. An order term is a small encrypted value stored next to the ciphertext so the database can compare rows without decrypting them. There are two schemes: ORE (order-revealing encryption) and OPE (order-preserving encryption, compared as plain bytes).

Floats now order the way PostgreSQL's float8 does: -0.0 ties 0.0, and every NaN is one value that sorts after +Infinity.

This builds on #1235, which made every order term cllw-ore's own encryption through vitaminc-ore. Stack Encrypt passes a float through at the F64 target, and cllw-ore encodes it. Stacked on #1235: review that PR first.

Changes

Engine (packages/stack-encrypt)

  • op::Ordered covers f32 and f64.
  • f32 is ordered as the f64 it widens to. Its Orderable target is F64, and vitaminc widens it exactly, so an f32 and its f64 are one term. The first draft refused a typed f32 term with TermError::NarrowFloat. That variant is gone: the crate's widening settles the question.
  • Between on floats. As on every other type since feat(stack-encrypt): order text over ADR-0002's folded order key #1235, an inverted range is not refused: a range from NaN, which sorts last, matches nothing, as SQL's BETWEEN would.
  • Data-plan path. The orderings are answered on float32 and float64 fields.
  • Docs. CHANGELOG, ADR-0009's list of ordered kinds, and the trybuild UI snapshots, whose diagnostic now names the floats.

Go (languages/golang)

  • Generated methods. stashgen writes Lt, Lte, Gt, Gte and Between on a float32 or float64 field with ore or ope. The "no generated method queries this index" notice now appears only for order indexes on bytes. The fake engine (enginetest) follows.
  • Test models. QueryProfile gains Score float64 with ORE, matching the Rust query fixture. Probe gains F64 and F32 fields.
  • Docs. README, doc.go, the stashgen README and the CHANGELOG are updated.

Verification

  • mise x --env test -- cargo nextest run --workspace --all-features: 1188 passed. That includes new tests:
    • pins for float ORE and OPE term bytes;
    • -0.0 ties 0.0, NaN payloads and signs are one term, and the order runs -Inf < MIN < -1.5 < … < MAX < +Inf < NaN, for both ORE and OPE;
    • an f32 gives the same ORE and OPE terms as the f64 it widens to, including -0.0, NaN and infinity;
    • every integer width from u8 to i128 has the expected term length and orders min < mid < max;
    • on the typed path, Between on floats: NaN-last and -0.0 tie cases;
    • on the data path, the -0.0 tie.
  • The trybuild UI binary passes with re-recorded snapshots. mise run test:doc passes (stack-encrypt: 40 doctests).
  • cargo clippy --workspace --all-targets --all-features -- -D warnings, cargo fmt and mise run doc: clean.
  • cargo mutants --in-diff on stack-encrypt against feat(stack-encrypt): order text over ADR-0002's folded order key #1235: 2 mutants, 2 caught.
  • The query fixture (tests/fixtures/query_operators.json) has a float64 field score, queried with lt and between. The data path derives the typed chain's bytes for both. Both fixtures are re-recorded.
  • EQL: eql-bindings with all features (197 passed) and packages/eql/tests/encryption (37 passed, 3 ignored; those need a database).
  • All five guests build, and mise run wasm:guest:test passes (68 and 71 tests).
  • mise run go:test passes, including the float ordering property tests and the generated Score.Lt/Score.Between checked byte for byte against the Rust fixture.
  • mise run go:lint reports 0 issues, and go generate ./... leaves no diff.
  • Not run locally: the live PostgreSQL test (needs DATABASE_URL).

Related

Review notes

https://claude.ai/code/session_01BZhkvKLpNtKuzj3HCfMXoL

@changeset-bot

changeset-bot Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 36271cb

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 17, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 9392ff63-d773-4c88-a397-037f283b7203
📥 Commits

Reviewing files that changed from the base of the PR and between 3004048 and 36271cb.

📒 Files selected for processing: 28
  • languages/golang/CHANGELOG.md
  • languages/golang/cmd/stashgen/README.md
  • languages/golang/encrypt/README.md
  • languages/golang/encrypt/doc.go
  • languages/golang/encrypt/internal/testusers/kinds_stash.go
  • languages/golang/encrypt/internal/testusers/probe_stash.go
  • languages/golang/encrypt/internal/testusers/queryprofile_stash.go
  • languages/golang/encrypt/internal/testusers/users.go
  • languages/golang/encrypt/order_test.go
  • languages/golang/encrypt/postgres_test.go
  • languages/golang/encrypt/query_test.go
  • languages/golang/stashgen/emit.go
  • languages/golang/stashgen/enginetest/enginetest.go
  • languages/golang/stashgen/refusal_test.go
  • packages/stack-encrypt/CHANGELOG.md
  • packages/stack-encrypt/docs/adr/0009-operators-select-the-index-and-a-query-term-is-not-the-stored-term.md
  • packages/stack-encrypt/src/dynamic/query.rs
  • packages/stack-encrypt/src/dynamic/term.rs
  • packages/stack-encrypt/src/op/mod.rs
  • packages/stack-encrypt/tests/fixtures/README.md
  • packages/stack-encrypt/tests/fixtures/query_operators.json
  • packages/stack-encrypt/tests/fixtures/record_lowering.json
  • packages/stack-encrypt/tests/op.rs
  • packages/stack-encrypt/tests/query_operators.rs
  • packages/stack-encrypt/tests/term_bytes.rs
  • packages/stack-encrypt/tests/ui/op_between_takes_a_pair.stderr
  • packages/stack-encrypt/tests/ui/op_order_on_bytes.rs
  • packages/stack-encrypt/tests/ui/op_order_on_bytes.stderr
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Don't think outside the box - find the box. When faced with an impossible problem, identify the real constraints. Ask yourself: 'Does it have to be done this way? Does it have to be done at all?' |
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
  \
   \     /)/)
        ( '')
        /  |
      o(___)
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@github-actions

github-actions Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

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

caught missed unviable timeout
425 0 342 0

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

@blacksmith-sh

This comment has been minimized.

@coderdan

Copy link
Copy Markdown
Contributor Author

Parked until new cllw-ore and ore-rs releases.

This PR takes #1235's workaround further. It builds every kind's vitaminc-ore order key inside Stack Encrypt and encrypts that key with cllw-ore 0.5's unsigned-integer and byte impls, through Any downcasts. That's the wrong layer. The ORE crates own type handling (integer width, the canonical float, text's fold), and the plan is for them to implement vitaminc-ore 0.6's OrderScheme and do it internally. Stack Encrypt should pass each value to their encrypt, and let the crate's type decide the encoding.

No released or built version of either crate does that yet. Once one does, this PR gets reworked:

  • Bump the crates.
  • Delete OrderSource, float_order_key and the Cllw dispatch.
  • Pass values through, and judge range inversion in the crate's order.
  • Settle f32's handling with whatever the crates do.

Most of the tests carry over: the float order checks, the score field in the cross-path fixture, the Go float property tests and the live Postgres test. #1236's proposal is updated to say so.

https://claude.ai/code/session_01BZhkvKLpNtKuzj3HCfMXoL

@coderdan

Copy link
Copy Markdown
Contributor Author

Unparked, and reworked on top of #1235.

#1235 now encrypts every order term through cllw-ore 0.5.2-rc.1's vitaminc schemes. So this PR no longer builds float order keys in Stack Encrypt. What changed (0f13138, 2e6be7c):

The float tests, the score fixture field and the Go property tests carry over. The description is rewritten to match. This PR stays a draft.

@coderdan
coderdan force-pushed the feat/stack-encrypt-order-keys branch 2 times, most recently from a33c4ba to 2dbdf38 Compare October 18, 2026 05:03
@coderdan

coderdan commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor Author

Rebased onto #1235, which removed the inverted-range check from Between. The float inversion cases this PR added are no longer refused: a range from NaN, which sorts last, derives its terms and matches nothing, as in SQL. The test now asserts that, and the (2.5, 1.5) case is dropped. The -0.0 tie and NaN-last order tests are unchanged.

https://claude.ai/code/session_01BZhkvKLpNtKuzj3HCfMXoL

@coderdan
coderdan marked this pull request as ready for review October 18, 2026 05:11
@coderdan
coderdan requested a review from a team as a code owner October 18, 2026 05:11
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 18, 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-18T05:14:22.240760Z 2dbdf38 Draft marked ready
🔒 Security Review ✅ Completed 2026-10-18T05:13:50.935386Z 2dbdf38 Draft marked ready
ℹ️ 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.

@cipherstash-agent cipherstash-agent 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 attestation: /code-review medium + /cross-check-review-findings

Reviewed head 2dbdf38 against base ccd4431, covering the engine (op::Ordered, the data-plan ordered()), the stashgen and enginetest changes, the test models and fixtures, and the docs.

Critical / blocking: none.

Non-blocking (1):

  • The older packages/stack-encrypt/CHANGELOG.md entry still says f64 only and that an f32 ordering does not compile. The new entry says f32 works (inline comment).

Cross-check:

  • The ordered! list, the data-plan ordered() and the stashgen/enginetest KindFloat handling agree.
  • The f32 path (f32 widens to the f64 it widens to, -0.0 ties 0.0, NaN sorts last) is covered by new tests on the typed path, the data path and the Rust and Go fixtures.
  • The diagnostics and UI snapshots are updated to name the floats.

CI at review time:

  • Passed: Rust fmt, clippy, nextest and doctests, Mutants gate, CRAP, Go lint, fuzz regressions, and the TS and e2e suites.
  • Still running: "WASI check, guests and Go (Linux)" and the Postgres shards. I could not check those results.

Source: automated review of the PR diff.


Generated by Claude Code

Comment thread packages/stack-encrypt/CHANGELOG.md Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2dbdf386d9

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/stack-encrypt/CHANGELOG.md Outdated
@coderdan
coderdan force-pushed the feat/stack-encrypt-text-ordering branch from ccd4431 to ec45e28 Compare October 18, 2026 12:34
@coderdan
coderdan force-pushed the feat/stack-encrypt-order-keys branch from 2dbdf38 to fa4652a Compare October 18, 2026 12:46
@coderdan

Copy link
Copy Markdown
Contributor Author

Reworked onto the reworked #1235 (which is now on the current next).

This PR is now one commit, fa4652ada feat(stack-encrypt)!: order floats through cllw-ore's F64 target. The live-PostgreSQL test fix (2dbdf38) is folded into it, since it fixed a test this change had broken.

What changed beyond the rebase:

  • No new FfiValue. The data-path float32 test queries with 0.0f32 and -0.0f32 directly. The "f32 orders as its f64" check derives the dynamic term from a plain 1.5f32 and compares it to the typed f64 term. It no longer adds an FfiValue::Float32 line.
  • The new kinds from next. The float32/float64 kinds join next's integer kinds, int8 through uint128. Decimal, date and timestamp still answer no ordering. The Go README no longer cites an issue number for them.
  • Review findings:
    • The CHANGELOG "ordering operators" entry no longer says f64 only, or that an ordering on f32 doesn't compile.
    • The Go README type table lists the orderings on float32/float64, and its index note now names bytes only.
    • A stashgen test comment is updated to match.

Same caveat as #1235: stored_rows_v1.json fails, because the order terms change bytes. Float terms add to that: the frozen f32 = -0.0 and f64 rows. That needs your decision (see the #1235 comment).

Verification:

  • stack-encrypt nextest: 681/682 pass; the only failure is stored_rows.
  • clippy, fmt, rustdoc and doctests: clean.
  • Mutants on the diff against feat(stack-encrypt): order text over ADR-0002's folded order key #1235: 2 tested, 2 caught.
  • Guests: all 5 build, and guest tests pass (68 and 71).
  • Go: go test ./... passes, go:lint reports 0 issues, and go generate shows no drift.

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

Review: float ordering through cllw-ore's F64 target (head fa4652a, this PR's own diff against #1235's ec45e28)

Reviewed with the focus on the float ordering semantics, that stack-encrypt hands the value to the ORE crate rather than encoding it, key and domain separation, the dependency pin, Go parity, and the tests. The crypto path is sound; the findings are about the PR's framing, its CI state and fixture churn.

Findings, most severe first

  1. The PR title still describes the draft this PR no longer is. The title says "derive every order term over its ADR-0002 order key, and order floats"; the one commit is "order floats through cllw-ore's F64 target", and the body itself says #1235 did the order-term rework. Whoever reads the merge commit, or git log --oneline, will think this PR moved every order term. The ! is in the same position: nothing in this commit's diff changes a stored byte or removes anything that shipped (TermError::NarrowFloat only ever existed in the draft), so the breaking part the CHANGELOG entry points at ("Float order terms change bytes with every other order term (above)") is #1235's. Rename the PR to the commit's title; whether to keep the ! is a judgement call, but the body should say which PR carries the byte change.

  2. Three Rust jobs are red at this head, all on one test, and the test is not this PR's. fmt, clippy, nextest, doctests, rustdoc, CRAP (stack-encrypt) and Mutants gate each fail on stored_rows::every_frozen_row_opens_and_seals_as_it_did (tests/stored_rows.rs:238), which fails identically on #1235's head. I checked this PR's engine diff for anything byte-affecting: src/op/mod.rs only adds f32, f64 to ordered!, and src/dynamic/query.rs only adds the two kinds to ordered(); Orderable for f32/f64 => F64 and the dynamic Float::F32/F64 dispatch are already in the base. So the failure is inherited, not introduced, but it means two things for this PR: it cannot go green until #1235 settles the frozen-row question (the file's own rule is that a deliberate format change adds stored_rows_v2.json rather than editing v1), and the "2 mutants, 2 caught" in the description is a local result only, since the gate exited 4 (baseline failed) and tested nothing. Worth a line in the body so a reader does not take the red checks as this change's.

  3. tests/fixtures/record_lowering.json is pure re-sealing churn. Every eq, ore and match term in that file is byte-for-byte unchanged; only the eight c ciphertexts moved, because STACK_ENCRYPT_UPDATE_FIXTURES=1 re-seals them with fresh nonces whenever it runs. Nothing in that fixture depends on this PR (its only ordered field is a uint32), and the test only asserts the terms and that the records open. Suggest restoring the file from the base so the diff does not read as a stored-format change in a PR about floats. (The query_operators.json regeneration is real: it adds the score field and its two queries.)

  4. Closes #1236 closes an issue whose title still promises dates, timestamps and decimals. The issue's updated proposal defers those to #1144 and #1130, so closing on the proposal is defensible, but the title ("cannot order floats, dates, timestamps or decimals") will read as done once it is closed. Retitle the issue, or split the deferred kinds out before this merges.

  5. Minor test gap: no subnormal f32 on the Rust side. an_f32_is_ordered_as_the_f64_it_widens_to (tests/term_bytes.rs:344) covers MIN_POSITIVE, the smallest normal. The widening in vitaminc-normalize 0.6.0 is a hand-rolled integer path with a separate subnormal branch (widen_f32, top + 874 exponent, mantissa realigned), and the only test that exercises it from this repo is Go's adjacency check with math.SmallestNonzeroFloat32, which proves order among f32 terms but not that a subnormal f32 and its f64 are one term. vitaminc's own widening_matches_hardware_at_every_exponent_and_subnormal_bit covers it upstream, so this is low risk; adding f32::from_bits(1) to that list would pin it from this side for free.

Verified as sound

  • Stack Encrypt does not encode the float. sem/mod.rs orderable!(f32 => F64, f64 => F64) calls column.encrypt(*self) with the bare scalar, and the dynamic path's ore_of/ope_of dispatch Float::F32(v)/Float::F64(v) to the same ore/ope functions. No trace of NarrowFloat, float_order_key or OrderSource remains in src, tests or languages/golang.
  • The semantics live in the crate, and match ADR-0002. vitaminc-ore 0.6.0 order() routes (F64, Physical::Float(f)) to vitaminc_normalize::float, which widens an f32 exactly, then canonical_f64 folds -0.0 to +0.0 and every NaN (either sign, any payload, including the signalling 0x7ff0_0000_0000_0001 the test uses) to 0x7ff8_0000_0000_0000, and orderable_f64's sign-flip encoding puts that above +Infinity. That is ADR-0002's baseline table row for Float32/Float64 and Postgres float8 ('NaN' = 'NaN', '-0' = '0', 'NaN' > 'Infinity'). Write and query go through the same Orderable::encrypt_in, so the canonicalisation cannot differ between them. Lt(NaN) matching every non-NaN and Between(NaN, +Inf) matching nothing are both what Postgres answers.
  • The tests pin the right things. -0.0 == 0.0 and the NaN variants as one term; -Inf < MIN < -1.5 < -MIN_POSITIVE < 0 < MIN_POSITIVE < 1.5 < MAX < +Inf < NaN for ORE (comparator) and OPE (bytes); exact ORE/OPE term bytes for 1.5; the f32-equals-f64 check on the typed path, the dynamic path (dynamic/term.rs and the ratio float32 field in query.rs) and Go's property tests; and Score.Lt/Score.Between in Go checked byte-for-byte against the Rust query_operators.json.
  • Key and domain separation. The ORE and OPE keys are PRF(PAE([ORE_KEY_DOMAIN | OPE_KEY_DOMAIN, context])), unchanged here; vitaminc-ore's column salt binds the scheme name, the target domain (vitaminc/ore/value/float64/v1) and the context, so a float term and an integer term under the same field context are under different salts, and ORE and OPE never share a key.
  • Dependency. This PR changes neither Cargo.toml nor Cargo.lock; cllw-ore = "=0.5.2-rc.1" with the vitaminc feature and vitaminc-ore 0.6.0 are #1235's, and --locked is unaffected.
  • Go parity. stashgen emits Lt/Lte/Gt/Gte/Between on float32/float64 with ore or ope, the generated signatures take the field's own width, enginetest's answeredBy mirrors ordered(), the notice is narrowed to bytes, and CI's go generate drift check passed at this head.
  • Changelogs. Rust crate and Go module only, both CHANGELOGs updated; no npm package is touched, so the changeset-bot warning is expected.
  • The earlier CHANGELOG contradiction (f32 said not to compile) raised on 2dbdf38 is fixed at this head, as is the Go README table.

Checks run locally (no credentials)

  • cargo nextest run -p stack-encrypt --all-features --no-fail-fast: 681 passed, 1 failed (stored_rows, see finding 2), 1 skipped.
  • cargo clippy -p stack-encrypt --all-targets --all-features -- -D warnings: clean. cargo fmt --check: clean.
  • go build ./..., go vet ./encrypt/... ./stashgen/...: clean. go test ./encrypt/... ./stashgen/...: stashgen and the fixture test pass; three encrypt tests fail only because the EQL guest is not built locally, and TestTermOrderIsPlaintextOrder skips without credentials (CI's live Go job passed at this head).
  • Read the vitaminc-ore 0.6.0, vitaminc-normalize 0.6.0 and cllw-ore 0.5.2-rc.1 sources from the registry for the float encoding, the widening and the salt.

Not run: the live PostgreSQL test and anything needing CipherStash credentials.

},
"text": {
"c": "0100000000000000000000000000000000cf1a45d1517a08c45d6ae1c989ae99de2000da620d1dbdbba9372d1439b6d44f7cc4169e9a255387aa180d6a95fb17926d7002feb70f92eddc6cf5ca7057e55c63c0ad86f563b5fc6db163691f66a21e23a7b7e089",
"c": "0100000000000000000000000000000000cf1a45d1517a08c45d6ae1c989ae99de2000da620d1dbdbba9372d1439b6d44f7cc4169e9a255387aa180d6a95fb17926d700294bddaed0bd2c45206be9407edb15f4c046eff417d18a00d6c033139e01770404e72",

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.

Every eq, ore and match term in this file is unchanged; only the c ciphertexts moved, because the regeneration re-seals them with fresh nonces. Nothing here depends on this PR (its only ordered field is the uint32 age), so this hunk is noise that makes the diff look like a stored-format change. Suggest restoring the file from the base branch.

async fn an_f32_is_ordered_as_the_f64_it_widens_to() {
let cipher = cipher().await;
let keyset = cipher.default_keyset();
for value in [1.5f32, -0.0, f32::NAN, f32::MIN_POSITIVE, f32::INFINITY] {

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.

f32::MIN_POSITIVE is the smallest normal f32. vitaminc-normalize's widen_f32 has a separate integer-only subnormal branch, and nothing on the Rust side here checks a subnormal f32 and its f64 are one term (Go's adjacency test with SmallestNonzeroFloat32 checks order among f32 terms, not equality with the f64's). Adding f32::from_bits(1) to this list pins it for free.

operators are answered over `f32` and `f64` (`op::Ordered`) and over a
data plan's `float32` and `float64` fields, which they were not. An `f32`
is ordered as the `f64` it widens to: vitaminc widens it exactly, so an
`f32` and its `f64` are one term. Float order terms change bytes with

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.

This sentence is right that the byte change is the one "(above)", from #1235. The PR title and the ! on this commit say otherwise: nothing in this PR's own diff changes a stored byte (op::Ordered and ordered() only widen which kinds answer the orderings). Worth aligning the title with the commit's.

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

  • packages/stack-encrypt/src/dynamic/kind.rs:321 minor exact_f64 refuses every NaN, so now that the orderings reach read, Cipher.QueryTerm(ctx, plan, "f32", "lt", math.NaN()) on a float32 field fails with ErrEncoding while the same query on a float64 field succeeds. The stated reason (kind.rs:154-156, the test comment at kind.rs:863) is false now that every NaN is one value above +Inf. In the float arms, read any NaN as f32::NAN/f64::NAN, and rewrite read_refuses_a_nan_in_both_float_directions.
  • languages/golang/stashgen/enginetest/enginetest.go:185 minor The answeredBy comment still says the orderings are answered "over an integer or text", but the guard at :202 now admits KindFloat. Change it to "over an integer, a float or text".

Comment thread languages/golang/encrypt/order_test.go Outdated
Comment thread languages/golang/encrypt/query_test.go Outdated
@coderdan
coderdan force-pushed the feat/stack-encrypt-text-ordering branch from ec45e28 to 4392b57 Compare October 18, 2026 23:15
@coderdan
coderdan force-pushed the feat/stack-encrypt-order-keys branch from fa4652a to c1bdf66 Compare October 18, 2026 23:16
@coderdan
coderdan force-pushed the feat/stack-encrypt-text-ordering branch from 4392b57 to ba29e03 Compare October 19, 2026 02:28
@coderdan
coderdan force-pushed the feat/stack-encrypt-order-keys branch 2 times, most recently from 2d97f21 to ea26f9d Compare October 19, 2026 03:17

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

Re-review of the update to ea26f9d.

Against 2d97f21, the PR's single commit changed only in two Go tests:

  • order_test.go: the float64 and float32 order properties now draw values from uniformly random bit patterns (math.Float64frombits(r.Uint64()), Float32frombits(r.Uint32())). They previously used testing/quick's default generator, which scales by MaxFloat64 and so draws almost only huge values.
    • Every exponent is now reachable, including subnormals, both zeros and NaN payloads. That covers the earlier "no subnormal f32 test" item probabilistically, alongside the fixed edge list in floatValues().
    • floatOrder handles the new inputs correctly: NaN sorts last and equal to NaN, and cmp.Compare treats -0.0 and 0.0 as equal. This matches the documented float8 order.
  • query_test.go: the Score.Lt(-0.0) check now fails the test with t.Fatalf if building the -0.0 query errors. Before, it went through a mustTerm helper that turned the error into nil, which could compare equal to another failed term. mustTerm is removed.

Both are improvements, and there are no new findings. CI is green at this head.

@coderdan
coderdan force-pushed the feat/stack-encrypt-text-ordering branch from ba29e03 to 7a8c8ae Compare October 19, 2026 05:38
@coderdan
coderdan force-pushed the feat/stack-encrypt-order-keys branch from ea26f9d to c634933 Compare October 19, 2026 05:38
@coderdan
coderdan force-pushed the feat/stack-encrypt-text-ordering branch from 7a8c8ae to e086fcf Compare October 19, 2026 05:49
@coderdan
coderdan force-pushed the feat/stack-encrypt-order-keys branch from c634933 to 72dc64c Compare October 19, 2026 05:49

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

Incremental re-review of the rebase to 72dc64c (onto feat/stack-encrypt-text-ordering at e086fcf, after next gained #1262, #1264 and #1267).

Range-diff against ea26f9d. The PR's one commit changed in two hunks, both rebase adaptations to the base's reworded order-term entries; nothing substantive:

  • languages/golang/CHANGELOG.md: the float entry no longer repeats "every order term changes bytes from the alpha's; none was deployed", which #1235's own entry now carries at this head (lines 40–43).
  • packages/stack-encrypt/tests/term_bytes.rs module doc: "None was deployed" became "Terms written by 0.1.0 and 0.2.0 do not compare with these (the CHANGELOG says so)", matching the stack-encrypt CHANGELOG at this head.

No FfiValue use is in this diff and no smallint surface is touched. No fixture or frozen file moved: query_operators.json and record_lowering.json are byte-identical to ea26f9d, and stored_rows_v1.json is not in the diff. Everything else in the commit (engine, stashgen, enginetest, tests, UI snapshots, docs) is identical to the reviewed head.

Earlier findings at this head (the rebase changed none of them):

  • Still open, LOW (thread): record_lowering.json still carries the re-sealed c churn (16 lines in this commit, every term unchanged).
  • Still open, nit (thread): the PR title still says "derive every order term over its ADR-0002 order key"; the commit is the float one.
  • Still open, minor (review 5565461331): exact_f64 still refuses a NaN across float widths (read_refuses_a_nan_in_both_float_directions, kind.rs:878), so a dynamic query of math.NaN() against a float32 field is refused while the same against a float64 field is accepted; the rationale at kind.rs:154 is unchanged. And enginetest.go's answeredBy comment still says "over an integer or text" while the guard admits KindFloat.
  • Still open, optional (thread): no subnormal f32 in the term_bytes.rs:345 list; the Go property test now draws every bit pattern, so this is covered probabilistically.
  • Fixed since: Closes #1236 is now Refs #1236; the order_test.go generators and the mustTerm removal were confirmed in review 5566619279.

Checks (no credentials): CI is green at this head (every job pass; review and the fuzz campaign skipped by design). mise run types:check at this head: no drift. At the top of the stack (#1280's 2189d5a, which contains this commit): mise x --env test -- cargo nextest run -p stack-encrypt --all-features 695 passed, 1 skipped, including stored_rows::every_frozen_row_opens_and_seals_as_it_did; go test ./stashgen/... ./cmd/stashgen/... ok; go vet clean.

@coderdan
coderdan force-pushed the feat/stack-encrypt-order-keys branch from 72dc64c to 6fc0c6a Compare October 19, 2026 06:32
@coderdan
coderdan force-pushed the feat/stack-encrypt-text-ordering branch from e086fcf to b98bad2 Compare October 19, 2026 06:32
@coderdan

Copy link
Copy Markdown
Contributor Author

Rebased onto the new #1235 (itself rebased onto next after #1262, #1263 and #1264). One conflict: the test plan in dynamic/query.rs, where next changed the closing of the plan literal; the ratio and rating float fields are kept.

@coderdan
coderdan force-pushed the feat/stack-encrypt-text-ordering branch from b98bad2 to 21659d1 Compare October 19, 2026 08:06
@coderdan
coderdan force-pushed the feat/stack-encrypt-order-keys branch from 6fc0c6a to 2375eec Compare October 19, 2026 08:06
@coderdan

Copy link
Copy Markdown
Contributor Author

Rebased onto #1235's new head (6fc0c6a → 2375eec). Conflicts with the Orderable reduction and the 8-bit removal were resolved in this commit: op::Ordered is u16..u128, i16..i128, f32, f64, String; the diagnostic and the two trybuild snapshots say so; the fake engine answers orderings over 16-bit-and-wider integers, floats and text; every_integer_width_orders_by_its_own_key covers 16 bits and up. stack-encrypt: 684 tests pass at the tip.

@coderdan
coderdan added this pull request to stack #1282 October 19, 2026 08:36
@coderdan
coderdan force-pushed the feat/stack-encrypt-order-keys branch from 2375eec to b4e9b91 Compare October 19, 2026 08:39
@coderdan
coderdan force-pushed the feat/stack-encrypt-text-ordering branch from 21659d1 to 7840c3a Compare October 19, 2026 08:39
@coderdan

Copy link
Copy Markdown
Contributor Author

Rebased onto #1235's new head 7840c3a (2375eec → b4e9b91). One conflict, in stashgen/enginetest: #1235 rewrote the orderable-type condition for staticcheck and this PR adds floats to it; resolved as orderable := (integer(t) && !eightBit(t)) || t.Kind == stashgen.KindFloat || t.Kind == stashgen.KindString. golangci-lint, go build and go vet pass at this head.

An `f32` or `f64` order term is cllw-ore's vitaminc encryption of the
value at the `F64` target, whose vitaminc-ore encoding folds `-0.0` into
`0.0` and every NaN into one value above `+Infinity`: Postgres's
`float8` order. So the ordering operators are now answered over floats,
on the typed path (`op::Ordered` for `f32` and `f64`) and over a data
plan's `float32` and `float64` fields, and stashgen writes `Lt`, `Lte`,
`Gt`, `Gte` and `Between` on a float field with `ore` or `ope`.

An `f32` is ordered as the `f64` it widens to: its `Orderable` target is
`F64` and vitaminc widens it exactly, so an `f32` and its `f64` are one
term and `f32` can join `op::Ordered`. A float `Between` is not refused
when inverted (a range from NaN, which sorts last, matches nothing), as
SQL's `BETWEEN` is not.

The Go query profile gains a sealed `Score float64` with ORE, matching
the Rust query fixture, and the live PostgreSQL test stores the score and
its ORE term so the rows it reads back open. Float pins, the float
ordering baseline, the integer-width lengths and the `score` field in the
cross-path fixture are added; the fixtures and UI snapshots are
re-recorded.

Closes #1236.
@coderdan
coderdan force-pushed the feat/stack-encrypt-text-ordering branch from 7840c3a to 3004048 Compare October 19, 2026 09:27
@coderdan
coderdan force-pushed the feat/stack-encrypt-order-keys branch from b4e9b91 to 36271cb Compare October 19, 2026 09:27
@coderdan

Copy link
Copy Markdown
Contributor Author

Rebased onto next at 0800375 (after #1283 merged main back in). Only lockfiles changed, by exactly the crate moves that merge brought; the commits' content is otherwise identical to the previous head. Every Cargo.lock passes --locked at every commit, and the full Rust, guest, EQL and Go suites pass at the chain's tips against the merged lock.

@coderdan
coderdan removed this pull request from stack #1282 October 19, 2026 09:41
Base automatically changed from feat/stack-encrypt-text-ordering to next October 19, 2026 10:00
@coderdan
coderdan merged commit 15bbbc5 into next Oct 19, 2026
67 of 94 checks passed
@coderdan
coderdan deleted the feat/stack-encrypt-order-keys branch October 19, 2026 10:03
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.

3 participants