Skip to content

feat(stack-encrypt): order text over ADR-0002's folded order key - #1235

Open
coderdan wants to merge 5 commits into
nextfrom
feat/stack-encrypt-text-ordering
Open

coderdan wants to merge 5 commits into
nextfrom
feat/stack-encrypt-text-ordering

Conversation

@coderdan

@coderdan coderdan commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Stack Encrypt is CipherStash's Rust encryption engine; the Go SDK runs it as a WebAssembly guest. Next to each ciphertext it can store order terms, which let the database answer <, >, BETWEEN and ORDER BY without decrypting. There are two kinds: ORE (order-revealing encryption, compared by a special function) and OPE (order-preserving encryption, compared as plain bytes).

Until now Stack Encrypt could not order text at all. This PR makes text orderable, so a Go string field with index=ore or index=ope now gets Lt, Lte, Gt, Gte and Between. Text orders by ADR-0002's fold: case and accents are folded, under Unicode 16. So Café, cafe and CAFÉ tie, alice comes before Zoe, and é sorts with e rather than after z.

How it works changed since the first draft. The ORE crate cllw-ore now encodes and encrypts each type for ordering itself, through vitaminc-ore. That shipped as a pre-release, 0.5.2-rc.1, with a vitaminc feature. Stack Encrypt says which type a value is and passes it straight through. It no longer folds text or builds order keys of its own.

Changes

Engine (packages/stack-encrypt)

  • Dependency. cllw-ore is now =0.5.2-rc.1 with the vitaminc feature, in the root workspace. The root, packages/eql, encrypt guest and stack-encrypt fuzz lockfiles are refreshed and pass --locked.

  • New sem::Orderable trait. A sealed trait that maps each orderable Rust type to its vitaminc-ore target:

    Rust type Target
    integers their own width
    f32, f64 F64
    String, &'static str, Zeroizing<String> Text
    Vec<u8>, &'static [u8], Zeroizing<Vec<u8>> Bytes
    bool none: refused with TermError::NoOrderTerm before any key is made, as before
  • Order terms.

    • The per-index key derivation is unchanged: a PRF over the field's context.
    • Inside the visitor, the derived key now keys cllw_ore::vitaminc::Ore or Ope and is wiped. The visitor opens a column with the value's target and an empty context, since the key already belongs to this index, and encrypts the value.
    • The key labels move to ore-key/v2 and ope-key/v2.
  • Removed. OrderSource, text_order_key, the Any downcasts, and the CllwOreEncrypt/CllwOpeEncrypt re-exports.

  • Term types.

    • Every source type's term is now one ciphertext type: OreCllw8VariableV1 or OpeCllw8VariableV1.
    • OreTerm<T> and OpeTerm<T> keep T to name the source, so typed callers compile unchanged.
    • KeysetCipher::ore_term and ope_term return the ciphertext types.
  • Between.

    • op::Ordered covers String.
    • Between no longer refuses a range whose low bound is above its high bound. As in SQL, BETWEEN 20 AND 10 is a valid query that matches nothing. TermError::InvertedRange (code stack_encrypt::inverted_range) is gone; it was unreleased.
    • The data-plan path still refuses order text with no fold before any key is used, for one value and for each bound of a range. It asks vitaminc_ore::text, the encoding the Text target uses.
  • Unencodable text. Text with a code point Unicode 16 doesn't assign has no order term (TermError::Normalize), on write and on query.

  • Breaking: every order term changes bytes. The causes are the /v2 labels, the column salt (which binds the scheme and the target), and cllw-ore's one marker byte in front of text and bytes. Nothing was deployed (ADR-0002). The pins in term_bytes.rs and frozen_bytes.rs are re-recorded, as are both cross-path fixtures.

  • Docs. ADR-0009's text-ordering section names the crate, and the CHANGELOG has the Breaking entry.

Go (languages/golang)

  • Generated methods. stashgen writes the ordering methods on string fields with ore/ope. Generated files and goldens are regenerated, and the README, doc.go and the stashgen README describe the fold. These are unchanged from the first draft.
  • Guest test. A guest test that pinned a text ORE term's length now expects the marker byte: 48 bytes for alice, not 40.

Verification

  • mise x --env test -- cargo nextest run --workspace --all-features: 1183 passed.
  • cargo nextest run -p stack-encrypt --all-features: 644 passed. This includes the trybuild UI binary; no UI snapshot changed.
  • cargo clippy --workspace --all-targets --all-features -- -D warnings, cargo fmt, mise run doc (rustdoc with warnings as errors) and mise run test:doc (stack-encrypt: 40 doctests): all clean.
  • cargo mutants --in-diff on stack-encrypt against next: 32 mutants, 13 caught, 19 unviable, none missed. The first run missed one equivalent mutant: deleting the Backend arm in order_error gives the same value, because cllw_ore::Error has no content. I removed the arm, and the re-run above is clean. On the commit that removes the inverted-range check: 6 mutants, 6 caught.
  • EQL: cargo test --locked -p eql-bindings --all-features (197 passed), and packages/eql/tests/encryption (37 passed, 3 ignored; those need a database).
  • All five guests build. mise run wasm:guest:test passes (68 and 71 tests).
  • mise run go:test passes, mise run go:lint reports 0 issues, and go generate ./... leaves no diff.
  • New or changed Rust tests:
    • re-pinned ORE/OPE bytes for u32 and for text;
    • folded equivalents are one term in every owned text form;
    • a text term differs from the same bytes stored as bytes;
    • unencodable text has no order term;
    • typed Lt/Between on text, with folded ties;
    • an inverted range derives its terms without error, on the typed and data-plan paths;
    • data-plan boundary refusals for text with no fold.
  • Not run locally: the Postgres query test (needs DATABASE_URL).

Related

Review notes

  • Where to look: start with sem/mod.rs's ORE/OPE section: Orderable and the visitors.
  • The type-to-target table is a stored format. Changing a type's target changes its terms.
  • Pre-release dependency. cllw-ore is pinned to the pre-release on purpose. When 0.5.2 (or later) is final, bump the pin; the terms shouldn't change.
  • Not addressed here: the stack-auth and stack-kms fuzz lockfiles are already stale on next, so --locked fails there. This PR leaves them alone.

https://claude.ai/code/session_01BZhkvKLpNtKuzj3HCfMXoL

@changeset-bot

changeset-bot Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: ccd4431

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

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: 562c686f-27e7-40a6-a94b-f2b749702df5

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
  • 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
9 0 15 0

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

Copilot AI 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.

🔵 Needs a closer look

Changes to cryptographic term derivation, key framing, and stored-byte compatibility warrant final human review.

No open issues 🎉

2 files not reviewed
  • languages/golang/encrypt/internal/testusers/kinds_stash.go: Generated file
  • languages/golang/encrypt/internal/testusers/probe_stash.go: Generated file
What changed in this PR

Enables folded text ordering in the Rust encryption engine and Go SDK, preparing for EQL text-order types.

Changes:

  • Derives text order terms using Unicode 16 case-and-accent folding.
  • Enables text comparisons and validates folded range bounds.
  • Updates generated Go methods, regression tests, documentation, and dependencies.
File Description
packages/​stack-encrypt/​tests/​ui/​pass/​plan_chain.rs Adds compiling text queries.
packages/​stack-encrypt/​tests/​ui/​op_order_on_text.stderr Removes obsolete rejection snapshot.
packages/​stack-encrypt/​tests/​ui/​op_order_on_text.rs Removes text-order rejection test.
packages/​stack-encrypt/​tests/​ui/​op_order_on_bytes.stderr Adds bytes-order rejection snapshot.
packages/​stack-encrypt/​tests/​ui/​op_order_on_bytes.rs Checks bytes ordering remains unavailable.
packages/​stack-encrypt/​tests/​ui/​op_between_takes_a_pair.stderr Updates ordering diagnostics.
packages/​stack-encrypt/​tests/​term_bytes.rs Pins folded terms and rejection behavior.
packages/​stack-encrypt/​tests/​op.rs Tests folded comparisons and ranges.
packages/​stack-encrypt/​src/​sem/​mod.rs Folds text and separates its key framing.
packages/​stack-encrypt/​src/​op/​mod.rs Enables typed text ordering.
packages/​stack-encrypt/​src/​dynamic/​term.rs Updates text-order documentation.
packages/​stack-encrypt/​src/​dynamic/​query.rs Enables and validates dynamic text queries.
packages/​stack-encrypt/​fuzz/​Cargo.lock Locks the folding dependency.
packages/​stack-encrypt/​docs/​adr/​0009-operators-select-the-index-and-a-query-term-is-not-the-stored-term.md Records text ordering as decided.
packages/​stack-encrypt/​CHANGELOG.md Documents changed text term bytes.
packages/​stack-encrypt/​Cargo.toml Adds vitaminc-ore.
packages/​eql/​Cargo.lock Updates engine dependencies.
languages/​golang/​stashgen/​testdata/​cases/​orders/​order_stash.go.golden Adds expected string ordering methods.
languages/​golang/​stashgen/​refusal_test.go Updates unused-index notice coverage.
languages/​golang/​stashgen/​enginetest/​enginetest.go Mirrors text-order capability.
languages/​golang/​stashgen/​emit.go Updates unused-index explanations.
languages/​golang/​encrypt/​README.md Explains folded ordering and refusals.
languages/​golang/​encrypt/​order_test.go Tests folded ordering and generated queries.
languages/​golang/​encrypt/​internal/​testusers/​probe_stash.go Regenerates string ordering methods.
languages/​golang/​encrypt/​internal/​testusers/​kinds_stash.go Adds named-string ordering methods.
languages/​golang/​encrypt/​guest/​Cargo.lock Updates guest dependencies.
languages/​golang/​encrypt/​doc.go Documents text ordering semantics.
languages/​golang/​cmd/​stashgen/​README.md Documents generated string comparisons.
languages/​golang/​CHANGELOG.md Records text ordering support.
Cargo.toml Declares workspace folding dependency.
Cargo.lock Locks vitaminc-ore and dependencies.

Review effort: Balanced

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 17, 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-17T10:37:20.784296Z 54a204d Draft marked ready
🔒 Security Review ✅ Completed 2026-10-17T10:38:11.490082Z 54a204d 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.

Automated review attestation

Source: Claude Code scheduled review (/code-review medium + /cross-check-review-findings) at head 54a204d. This is an automated review, not a human approval.

Critical / blocking issues: none.

Findings: none survived cross-check.

Reviewed (packages/stack-encrypt/src):

  • sem/mod.rs: OrderSource and text_order_key. Text goes through the folded key. The PRF context adds the frozen TEXT domain label only for text, so integer, float and bytes terms keep their bytes. The key is zeroizing and moved, not copied.
  • op/mod.rs: sealed Ordered::inverted. The String impl compares folded keys. Integers keep lo > hi.
  • dynamic/query.rs: ordered(kind), bound_order and the check_input fold refusal. They agree with the typed path (("b","A") inverted, ("A","a") a tie, an unassigned code point refused before a keyset).
  • Docs, ADR-0009, trybuild snapshots, lockfiles and the Go generated/golden files, checked for consistency with the Rust change.

Cross-check against CI on this head:

  • fmt/clippy/nextest/doctests/rustdoc, the Go matrix (Linux/macOS/Windows), Go lint and live tests, the EQL suites and the fuzz regressions are green.
  • The mutants gate caught 24 of 24 mutants (0 missed).
  • Copilot raised no issues.
  • The Codex code and security reviews were still running when this review was posted.

Notes (non-blocking):

  • OrderSource::of selects the text path by runtime Any downcast on String, Zeroizing<String> and &'static str. A new owned text type passed to an order term would silently take the non-folded Value path. This is fine today because Ordered is sealed, and the PR notes it. A test asserting every Ordered text form takes the folded path would guard it.
  • Text order terms change bytes (the CHANGELOG notes this as Breaking, with none deployed). Confirm no stored text ORE/OPE terms exist outside tests before merge.
  • Changeset-bot reports no changeset. That looks acceptable here since the touched crates and the Go module use their own CHANGELOGs, but a maintainer should confirm.

Generated by Claude Code

@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: 54a204d924

ℹ️ 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/src/sem/mod.rs Outdated
/// not assign, whose folding is not known.
pub(crate) fn text_order_key(text: &str) -> Result<Zeroizing<Vec<u8>>, TermError> {
use vitaminc_protected::Controlled;
Ok(Zeroizing::new(vitaminc_ore::text(text)?.risky_unwrap()))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve unassigned code points in text order keys

For any valid string containing a code point unassigned in Unicode 16—including characters introduced by later Unicode versions—propagating this error makes every ORE/OPE write and query fail. This contradicts ADR-0002's frozen-transform contract in docs/adr/0002-one-value-model-three-encodings-and-eql-v4-by-producer.md:187-195, which requires such code points to pass through unchanged so existing columns remain usable as Unicode evolves; derive the stable pass-through key rather than rejecting the value.

Useful? React with 👍 / 👎.

@coderdan

Copy link
Copy Markdown
Contributor Author

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

This PR folds text inside Stack Encrypt (OrderSource in sem/mod.rs) and hands the folded bytes to cllw-ore 0.5. That's the wrong layer. The ORE crates own how each type is encoded for ordering, and the plan is for them to implement vitaminc-ore 0.6's OrderScheme and fold text internally. Stack Encrypt should pass the String to their encrypt unchanged. Folding in front of them duplicates their encoding, and the Any downcasts throw away the types their traits carry.

No released or built version of either crate implements vitaminc-ore 0.6 yet. Once they do, this PR gets reworked:

  • Bump the crates.
  • Delete OrderSource and text_order_key.
  • Pass text through, and judge Between's inverted range in the crate's order.

The engine surface stays: op::Ordered for String, the data path answering orderings on text, and the Go methods. #1234's proposal is updated to say so.

https://claude.ai/code/session_01BZhkvKLpNtKuzj3HCfMXoL

Text ordering is decided: the order key is the system ADR-0002's text fold, which vitaminc 0.6 ships in vitaminc-ore, and the ORE and OPE indexes answer the orderings on text over it. The ADR no longer says "if the catalog keeps text ordering" or that the paragraph goes if text ordering is dropped, and decision 7 names text beside the integer types.
…the orderings on text

Text could not be ordered: no ordering operator was answered on a text
field, and the ORE and OPE terms stored for text went through cllw-ore's
own collation, which keeps case, so Zoe ordered before alice and é after
z. ADR-0002 defines text's order key as its fold (Unicode 16 NFC with case
and accents folded), and vitaminc 0.6 ships it in vitaminc-ore.

Text ORE and OPE terms now derive over that key on the typed and the
data-plan paths alike: the text is folded with vitaminc_ore::text and the
folded bytes go to cllw-ore's byte encryption, whose ciphertext type is
the one it gives text, so OreTerm<String> and OpeTerm<String> keep their
type. The released cllw-ore and ore-rs do not implement vitaminc-ore's
OrderScheme yet, so the CLLW schemes the engine already uses encrypt the
key. A text term's key is derived under a framing that adds the fold's
frozen label; integer, float and bytes order terms keep their bytes and
framing. Café, cafe, CAFÉ and cafe with a combining accent are one term.
Text holding a code point Unicode 16 does not assign has no fold and no
order term.

op::Ordered covers String, whose Between judges an inverted range on the
folded keys through a new sealed method, Ordered::inverted: ("b", "A") is
inverted, ("A", "a") is one value. The data path answers the orderings on
text too, refuses an inverted text range and unfoldable order text at its
boundary, and the Go generator now writes Lt, Lte, Gt, Gte and Between on
a string field with ore or ope. Its notice is left for order indexes on
bytes or a float, and for a second order index.

Closes #1234.
… schemes

cllw-ore 0.5.2-rc.1 implements vitaminc-ore's `OrderScheme` behind its
`vitaminc` feature, so Stack Encrypt no longer encodes anything for
ordering itself. An ORE or OPE term is now cllw-ore's `vitaminc::Ore` or
`vitaminc::Ope` encryption of the value: the engine names the value's
order target through the new sealed `sem::Orderable` (integers at their
own width, `f32`/`f64` as `F64`, text as `Text`, bytes as `Bytes`) and
passes the value straight through; cllw-ore encodes it with vitaminc-ore,
text by the system ADR-0002's fold. That replaces the earlier workaround,
which built text's order key here and handed the folded bytes to
cllw-ore 0.5 through `Any` downcasts: `OrderSource`, `text_order_key`
and the downcasts are gone.

The per-index key derivation is kept: the PRF block keys the scheme
inside the visitor and is wiped there, and the column is opened with the
value's target and the empty context, since the key is already the
index's own. The key labels move to `/v2` because every order term
changes bytes (new column salt, cllw-ore's marker byte for text and
bytes). Nothing was deployed (ADR-0002); the pins and both fixtures are
re-recorded.

Every source type's term is now one ciphertext type
(`OreCllw8VariableV1`, `OpeCllw8VariableV1`); `OreTerm<T>`/`OpeTerm<T>`
keep `T` as a marker so typed callers compile unchanged. `Between`
judges an inverted range in the order the stored terms compare, by
encrypting both bounds with OPE under a fixed comparison key and
comparing the bytes, on the typed and the data-plan path alike.

Claude-Session: https://claude.ai/code/session_01BZhkvKLpNtKuzj3HCfMXoL
@coderdan
coderdan force-pushed the feat/stack-encrypt-text-ordering branch from 54a204d to f915cf5 Compare October 18, 2026 02:45
@coderdan

Copy link
Copy Markdown
Contributor Author

Unparked, and reworked onto cllw-ore 0.5.2-rc.1.

cllw-ore 0.5.2-rc.1 is a pre-release with a vitaminc feature that implements vitaminc-ore 0.6's OrderScheme. So this PR no longer folds text, or builds any order key, inside Stack Encrypt. What changed (f915cf5, rebased onto the current next):

  • Removed: OrderSource, text_order_key and the Any downcasts.
  • New mapping: each Rust type maps to its vitaminc order target through the sealed sem::Orderable. The per-index PRF key keys cllw_ore::vitaminc::Ore/Ope, and the value goes straight through.
  • Between: it compares the bounds in the order the stored terms compare, using OPE under a fixed comparison key whose terms are thrown away.
  • Breaking: every order term changes bytes (key labels /v2). Nothing was deployed. Pins and fixtures are re-recorded.

The Go surface and the tests from the first draft stay. The description is rewritten to match.

coderdan added a commit that referenced this pull request Oct 18, 2026
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`. That settles what the earlier
draft refused with `TermError::NarrowFloat`, which is gone. `Between`
judges a float range in the same order (`(0.0, -0.0)` is one value,
`(NaN, 1.0)` is inverted), by the comparison #1235 added.

Float pins, the float ordering baseline, the integer-width lengths and
the `score` field in the cross-path fixture carry over from the earlier
draft; the fixtures and UI snapshots are re-recorded.

Claude-Session: https://claude.ai/code/session_01BZhkvKLpNtKuzj3HCfMXoL
@coderdan

Copy link
Copy Markdown
Contributor Author

Mutants re-run after removing the equivalent Backend arm: 32 mutants, 13 caught, 19 unviable, none missed.

A `Between` whose low bound is above its high bound was refused with
`TermError::InvertedRange` before either term was derived. SQL's own
`BETWEEN` refuses nothing: `x BETWEEN 20 AND 10` is a valid query that
matches no row, and an encrypted range now behaves the same way. The
empty result leaks nothing and corrupts nothing, so a refusal bought
little.

It also cost more than it bought. Judging a range correctly for text and
floats means reproducing, before encryption, the order the index
compares in: text by its fold, floats by their canonical encoding. The
check did that by encrypting both bounds under a fixed public OPE key,
which tied a query-time refusal to one scheme's ciphertext. That order
belongs to the ORE crates, not to this one.

So `TermError::InvertedRange` (code `stack_encrypt::inverted_range`)
goes, with the typed path's `Ordered::inverted`, the data path's
`bound_order` and `integer_order`, and the comparison scheme behind
them. The variant is under [Unreleased] and never shipped.

The data path still refuses order text with no fold (a code point
Unicode 16 does not assign) before a term or a keyset, for one value and
now for each bound of a pair. It asks `vitaminc_ore::text` directly,
which is the encoding `vitaminc_ore::order` uses for the `Text` target,
so it refuses exactly what derivation would.

Claude-Session: https://claude.ai/code/session_01BZhkvKLpNtKuzj3HCfMXoL
@coderdan

Copy link
Copy Markdown
Contributor Author

Removed the inverted-range check from Between (commit ccd4431).

A range whose low bound is above its high bound is now encrypted like any other and matches nothing, as x BETWEEN 20 AND 10 does in SQL. The check gave little: an empty result leaks nothing and corrupts nothing. It also cost a lot: judging text and float ranges correctly meant reproducing the index's order before encryption, which the previous version did by encrypting both bounds under a fixed public OPE key. That order belongs to the ORE crates, not to stack-encrypt.

TermError::InvertedRange and its stack_encrypt::inverted_range code are gone. Both were unreleased. The data path still refuses order text with no fold before any key is used, now through vitaminc_ore::text.

https://claude.ai/code/session_01BZhkvKLpNtKuzj3HCfMXoL

This branch has not been deployed

No deployments
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