From 443f2a82cffb0801f0c6d8165e91482b65a4ce80 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2026 05:20:50 +0000 Subject: [PATCH 1/2] Version Packages --- .changeset/brave-hounds-monitor.md | 18 - .changeset/cli-tls-handling.md | 10 - .changeset/eql-preflight-deferred-grants.md | 12 - .changeset/eql-verify-review-fixes.md | 21 -- .changeset/eql-verify-surface.md | 5 - ...platform-packages-publishing-repository.md | 34 -- .changeset/lovable-handoff-target.md | 5 - .changeset/lucky-cows-repeat.md | 17 - .changeset/olive-moons-shave.md | 18 - .changeset/olive-poems-guess.md | 29 -- .changeset/olive-pugs-invite.md | 38 -- .changeset/ore-unavailable-at-install.md | 14 - ...pabase-init-and-backdated-push-guidance.md | 7 - .changeset/prisma-next-0-17.md | 16 - .../prisma-skill-psl-expression-indexes.md | 5 - .changeset/protect-ffi-lazy-load.md | 7 - .changeset/protect-ffi-repository-url.md | 19 - .changeset/proud-ravens-repeat.md | 84 ----- .changeset/quiet-moons-repair.md | 5 - .changeset/ship-changelogs-in-tarballs.md | 12 - .changeset/skills-managed-platforms.md | 13 - .changeset/skills-supabase-edge-entry.md | 9 - .changeset/skills-type-capability-matrix.md | 16 - .changeset/stack-2-0-0-version-alignment.md | 47 --- .changeset/stack-diagnostics-subpath.md | 13 - .../stash-doctor-probes-native-bindings.md | 15 - .../supabase-declared-schemas-edge-entry.md | 32 -- .changeset/supabase-eql-migration-file.md | 33 -- .../supply-chain-skill-ffi-release-path.md | 39 -- e2e/CHANGELOG.md | 32 ++ e2e/package.json | 2 +- examples/basic/CHANGELOG.md | 11 + examples/basic/package.json | 2 +- examples/prisma/CHANGELOG.md | 13 + examples/prisma/package.json | 2 +- packages/bench/CHANGELOG.md | 11 + packages/bench/package.json | 2 +- packages/cli/CHANGELOG.md | 347 ++++++++++++++++++ packages/cli/package.json | 2 +- packages/protect-ffi/CHANGELOG.md | 94 +++-- packages/protect-ffi/package.json | 2 +- .../platforms/darwin-arm64/CHANGELOG.md | 31 ++ .../platforms/darwin-arm64/package.json | 2 +- .../platforms/darwin-x64/CHANGELOG.md | 31 ++ .../platforms/darwin-x64/package.json | 2 +- .../platforms/linux-arm64-gnu/CHANGELOG.md | 31 ++ .../platforms/linux-arm64-gnu/package.json | 2 +- .../platforms/linux-x64-gnu/CHANGELOG.md | 31 ++ .../platforms/linux-x64-gnu/package.json | 2 +- .../platforms/linux-x64-musl/CHANGELOG.md | 31 ++ .../platforms/linux-x64-musl/package.json | 2 +- .../platforms/win32-x64-msvc/CHANGELOG.md | 31 ++ .../platforms/win32-x64-msvc/package.json | 2 +- packages/stack-drizzle/CHANGELOG.md | 52 +++ packages/stack-drizzle/package.json | 2 +- packages/stack-prisma/CHANGELOG.md | 32 ++ packages/stack-prisma/package.json | 2 +- packages/stack-supabase/CHANGELOG.md | 82 +++++ packages/stack-supabase/package.json | 2 +- packages/stack/CHANGELOG.md | 124 +++++++ packages/stack/package.json | 2 +- packages/test-kit/CHANGELOG.md | 10 + packages/test-kit/package.json | 2 +- packages/wizard/CHANGELOG.md | 44 +++ packages/wizard/package.json | 2 +- 65 files changed, 1030 insertions(+), 637 deletions(-) delete mode 100644 .changeset/brave-hounds-monitor.md delete mode 100644 .changeset/cli-tls-handling.md delete mode 100644 .changeset/eql-preflight-deferred-grants.md delete mode 100644 .changeset/eql-verify-review-fixes.md delete mode 100644 .changeset/eql-verify-surface.md delete mode 100644 .changeset/ffi-platform-packages-publishing-repository.md delete mode 100644 .changeset/lovable-handoff-target.md delete mode 100644 .changeset/lucky-cows-repeat.md delete mode 100644 .changeset/olive-moons-shave.md delete mode 100644 .changeset/olive-poems-guess.md delete mode 100644 .changeset/olive-pugs-invite.md delete mode 100644 .changeset/ore-unavailable-at-install.md delete mode 100644 .changeset/precise-supabase-init-and-backdated-push-guidance.md delete mode 100644 .changeset/prisma-next-0-17.md delete mode 100644 .changeset/prisma-skill-psl-expression-indexes.md delete mode 100644 .changeset/protect-ffi-lazy-load.md delete mode 100644 .changeset/protect-ffi-repository-url.md delete mode 100644 .changeset/proud-ravens-repeat.md delete mode 100644 .changeset/quiet-moons-repair.md delete mode 100644 .changeset/ship-changelogs-in-tarballs.md delete mode 100644 .changeset/skills-managed-platforms.md delete mode 100644 .changeset/skills-supabase-edge-entry.md delete mode 100644 .changeset/skills-type-capability-matrix.md delete mode 100644 .changeset/stack-2-0-0-version-alignment.md delete mode 100644 .changeset/stack-diagnostics-subpath.md delete mode 100644 .changeset/stash-doctor-probes-native-bindings.md delete mode 100644 .changeset/supabase-declared-schemas-edge-entry.md delete mode 100644 .changeset/supabase-eql-migration-file.md delete mode 100644 .changeset/supply-chain-skill-ffi-release-path.md create mode 100644 packages/protect-ffi/platforms/darwin-arm64/CHANGELOG.md create mode 100644 packages/protect-ffi/platforms/darwin-x64/CHANGELOG.md create mode 100644 packages/protect-ffi/platforms/linux-arm64-gnu/CHANGELOG.md create mode 100644 packages/protect-ffi/platforms/linux-x64-gnu/CHANGELOG.md create mode 100644 packages/protect-ffi/platforms/linux-x64-musl/CHANGELOG.md create mode 100644 packages/protect-ffi/platforms/win32-x64-msvc/CHANGELOG.md diff --git a/.changeset/brave-hounds-monitor.md b/.changeset/brave-hounds-monitor.md deleted file mode 100644 index ad2f37495..000000000 --- a/.changeset/brave-hounds-monitor.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'stash': patch ---- - -Correct the Dependabot section of the bundled `stash-supply-chain-security` -skill. It described two monitored ecosystems (`npm`, `github-actions`); there -are now three, because the in-tree Rust workspace at `packages/protect-ffi` -brought a `Cargo.lock` that nothing proposed updates for. The skill now names -the `cargo` entry, its non-root `directory`, its monthly cadence, and the -exact-pinned CipherStash crates it ignores. - -Two things the section previously got wrong are also fixed. Major bumps do not -"stay un-grouped — one PR each": every entry ignores -`version-update:semver-major`, so Dependabot proposes no major bumps at all and -they are applied by hand. And `ignore` conditions suppress Dependabot *security* -PRs as well as version updates — the skill now says so, and points at -`osv-scanner.yml` (which scans every lockfile in the tree, `Cargo.lock` -included) as the compensating control. diff --git a/.changeset/cli-tls-handling.md b/.changeset/cli-tls-handling.md deleted file mode 100644 index ae221d33d..000000000 --- a/.changeset/cli-tls-handling.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -'stash': minor ---- - -The CLI now handles database TLS properly, so the discoverable fix for a certificate failure is never `NODE_TLS_REJECT_UNAUTHORIZED=0`. - -- Every CLI database connection honours `sslmode` and `sslrootcert` from the connection string — and `PGSSLMODE` / `PGSSLROOTCERT` from the environment when the URL carries no TLS parameters (URL wins; unlike raw node-postgres, `PGSSLROOTCERT` is actually consumed): `verify-full` (and `require`/`verify-ca`/`prefer`, kept as full verification — node-postgres's current behaviour) verifies the server certificate; `no-verify` is honoured with a one-line stderr warning; `disable` turns TLS off. Client-certificate setups (`sslcert`/`sslkey`) pass through untouched. -- CA resolution: `sslrootcert=` (libpq semantics — sole trust anchor; `sslrootcert=system` selects the system store) → `PGSSLROOTCERT` → for `*.supabase.co`/`*.supabase.com` hosts a **bundled Supabase root CA** (appended to the system roots) → the system store. `sslmode=verify-full` against Supabase — direct hosts and the pgBouncer pooler — now verifies out of the box. -- Certificate-verification failures — shaped centrally in the connection factory, so every command surfaces them — name the host and the supported remedies in order (`sslrootcert=…`, then `sslmode=no-verify` as a last resort with the consequence spelled out), and explicitly warn against `NODE_TLS_REJECT_UNAUTHORIZED=0`, which is process-wide and would also disable verification for the connections carrying CipherStash credentials. -- The node-postgres "SSL modes … are treated as aliases for verify-full" SECURITY WARNING no longer appears on every invocation against `sslmode=require` URLs: the CLI decides the TLS config itself and hands pg a URL with the TLS params stripped (fixes the upstream-advisory passthrough). diff --git a/.changeset/eql-preflight-deferred-grants.md b/.changeset/eql-preflight-deferred-grants.md deleted file mode 100644 index 62b1d20fe..000000000 --- a/.changeset/eql-preflight-deferred-grants.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -'stash': minor ---- - -EQL installs no longer abort on managed platforms whose database role is not `postgres`, and a new `stash eql preflight` command reports role capability before anything is attempted. - -- `stash eql install` (and `eql upgrade`) now run the EQL v3 bundle in its own transaction and the Supabase role grants after it commits, so a grants failure can no longer roll back a working install. When the connecting role is not a member of `postgres` (e.g. Lovable's `sandbox_exec`), the three owner-scoped `ALTER DEFAULT PRIVILEGES FOR ROLE postgres` statements are skipped and the install completes without them — they are optional (they only cover EQL objects `postgres` might later create outside stash tooling, and stash re-grants every object on each install/upgrade); the SQL is printed as "Optional SQL — requires postgres" for operators who want it. Every plain `GRANT` still runs. Previously that single refused statement rolled back the entire install (~194 functions). -- Re-running `stash eql install` on an already-installed Supabase database now re-applies the role grants (idempotent) instead of exiting early, so an install whose grants step failed heals on a plain re-run. -- The migration generated by `stash eql migration --supabase` wraps the owner-scoped statements in a `pg_has_role` guard, so it applies cleanly whatever role the project's migration runner uses — a non-member role skips them instead of aborting the whole migration. -- New read-only `stash eql preflight` (`--json` for agents): reports `current_user`, superuser, membership of `postgres` (guarded for databases with no `postgres` role), `CREATE` on the database and on `public` (guarded for databases without a `public` schema), `pgcrypto` presence *and placement* (a pgcrypto outside `extensions`/`public` aborts the bundle, even for superusers), and the EQL v3 schemas' presence and drop-ownership (a reinstall begins with `DROP SCHEMA ... CASCADE`) — each blocked row naming the statement it blocks. Exits 1 on blocking gaps; membership of `postgres` never blocks. `--json` stdout is pure JSON in every outcome: `{ status: 'ok' | 'blocked', ... }`, or the shared `{ status: 'error', code, message }` envelope — including when no DATABASE_URL is configured. The same check runs at the head of `eql install`. -- Install failure messages now state recoverability: a bundle failure says nothing was applied (rolled back); a grants failure says the install itself was kept. -- Library surface: `EQLInstaller.preflight()` (rich `PreflightResult`) supersedes `checkPermissions()`, which remains as a deprecated adapter with its `PermissionCheckResult` shape unchanged — no breaking change for existing `stash@1.x` consumers. `install()` now returns `InstallResult` with the skipped SQL, if any, and `applySupabaseGrants()` re-applies the grants alone. The exact `SUPABASE_PERMISSIONS_SQL_V3` block is unchanged byte-for-byte; new exports expose its immediate (`SUPABASE_IMMEDIATE_GRANTS_SQL_V3`), owner-scoped (`SUPABASE_DEFAULT_PRIVILEGES_SQL_V3`), guarded (`SUPABASE_GUARDED_DEFAULT_PRIVILEGES_SQL_V3`), and migration (`SUPABASE_MIGRATION_GRANTS_SQL_V3`) forms. diff --git a/.changeset/eql-verify-review-fixes.md b/.changeset/eql-verify-review-fixes.md deleted file mode 100644 index 05be5c40f..000000000 --- a/.changeset/eql-verify-review-fixes.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -'stash': patch ---- - -`stash eql status` no longer reports ORE damage on a healthy database running a -different EQL version. The `_ord_ore` domains its poison CHECKs are counted over -come from the bundle this CLI pins, so a fallback install of an older EQL poisons -domains the pinned list only partly sees — which classified as an incoherent -half-install and told the operator to reinstall with `--force`, on the ordinary -"CLI upgraded, database not yet" case. The ORE probe now gates on the installed -version the same way `eql verify` does and reports that the state could not be -compared, pointing at `eql upgrade`. - -Two hardening fixes to `stash eql verify` alongside it. Its cast check now -matches an EQL endpoint on either side, so a future bundle cast to or from a -`pg_catalog` type (`jsonb`, `text`) cannot enter the expected surface while being -unreadable as installed — which would have reported "Cast missing" on every -healthy database. And the parser that derives the expected surface from the -pinned bundle now fails loudly on any statement it does not model, instead of -silently omitting the objects it creates: a bundle that outgrows the parser can -no longer make `verify` report a partial install as complete. diff --git a/.changeset/eql-verify-surface.md b/.changeset/eql-verify-surface.md deleted file mode 100644 index 608669ac2..000000000 --- a/.changeset/eql-verify-surface.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'stash': minor ---- - -New `stash eql verify`: assert the installed EQL surface is complete and coherent, independent of any application schema. A partial install — domains present, some of their comparison functions or operators absent — used to report success at install time and fail at query time on a specific predicate (e.g. `weight >= x`); nothing detected it. `eql verify` compares the database against everything the pinned bundle installs (every domain, function overload, operator, cast, and the ORE operator class) via read-only catalog queries, reports damage grouped per domain, and distinguishes expected absence from damage: the ORE operator class being skipped on managed Postgres, with its loud-failure fallback in place, reads as the supported configuration it is rather than a failed install. Exit 0 means exactly one thing — the surface was checked and found complete; damage, EQL absent, and a version mismatch with the pinned bundle (nothing verifiable) all exit 1. `--json` emits the structured report for agents. `stash eql install` now runs the same check automatically before declaring success, on the fresh-install path and the already-installed early exit alike — there, only damage fails the install: a version mismatch warns and continues, so a no-op re-run over an older EQL stays exit 0 for idempotent provisioning scripts. A valueless `--database-url` (booleanised by the parser when the next token is another flag) is now rejected up front on every command instead of silently falling back to `DATABASE_URL` — previously `eql install --database-url --force` could drop and reinstall the EQL schemas on a database the command never named. diff --git a/.changeset/ffi-platform-packages-publishing-repository.md b/.changeset/ffi-platform-packages-publishing-repository.md deleted file mode 100644 index fd6ebddc9..000000000 --- a/.changeset/ffi-platform-packages-publishing-repository.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -'@cipherstash/protect-ffi-darwin-x64': patch -'@cipherstash/protect-ffi-darwin-arm64': patch -'@cipherstash/protect-ffi-win32-x64-msvc': patch -'@cipherstash/protect-ffi-linux-x64-gnu': patch -'@cipherstash/protect-ffi-linux-arm64-gnu': patch -'@cipherstash/protect-ffi-linux-x64-musl': patch ---- - -**This is the first release of these packages published from -`cipherstash/stack`.** Every version up to and including 0.31.0 was published -from `cipherstash/protectjs-ffi`, which is archived once this release is out. - -If you verify npm provenance, the attested source repository changes with this -release: - -``` -0.31.0 github.com/cipherstash/protectjs-ffi .github/workflows/release.yml -0.32.0 github.com/cipherstash/stack .github/workflows/release.yml -``` - -A verification policy that pins the source repository will reject 0.32.0 until -it is updated. The packages, their contents and their maintainers are otherwise -unchanged: the Rust source moved into the monorepo at -`packages/protect-ffi/crates/protect-ffi`, and each of these packages' -`repository.url` now names `cipherstash/stack`, with `repository.directory` -pointing at its own stub under `packages/protect-ffi/platforms/`. - -`CHANGELOG.md` is also added to each package's published files, so this note and -later ones are readable in the package you install rather than only on GitHub. - -The binaries themselves differ from 0.31.0 in one user-visible way: the Rust -core's `InvariantViolation` message asks the reader to file an issue, and the -repository it names has moved with the rest. diff --git a/.changeset/lovable-handoff-target.md b/.changeset/lovable-handoff-target.md deleted file mode 100644 index f22bee9b4..000000000 --- a/.changeset/lovable-handoff-target.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'stash': minor ---- - -Add a `lovable` handoff target to `stash plan` and `stash impl` (`--target lovable`, plus a new agent-target picker entry). It writes the same AGENTS.md as the editor-agent handoff — doctrine plus the per-integration skills inlined — but the next-steps guidance is Lovable-specific: commit and push the generated files through Lovable's GitHub sync, then add a Knowledge note in the Lovable project settings pointing the agent at `AGENTS.md` and `.cipherstash/setup-prompt.md`. Without repo-local guidance, Lovable's agent answers CipherStash questions from stale training data (the pre-EQL-v3 "needs a Postgres extension and superuser" story) and talks users out of a supported Supabase setup. diff --git a/.changeset/lucky-cows-repeat.md b/.changeset/lucky-cows-repeat.md deleted file mode 100644 index fa5707430..000000000 --- a/.changeset/lucky-cows-repeat.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -'stash': patch ---- - -Document in the bundled `stash-auth` skill that `CS_CLIENT_KEY` must be -hex-encoded. Hex is what `stash env` emits and what the skill's variable table -already stated, but the decoder underneath used to fall back to standard padded -base64 — the encoding the Rust `stash-profile` crate uses for -`~/.cipherstash/secretkey.json` on disk — so a key copied out of that file -happened to work despite never being a supported input. That fallback is gone -and such a key is now rejected at client construction, with a message that -deliberately withholds detail — so the skill names the symptom and the fix. - -The recovery advice is split by entry point: falling back to the profile store -works on the native entry, but not on `@cipherstash/stack/wasm-inline`, where -`clientId` and `clientKey` are required config and the target runtimes have no -profile store to read. Re-encoding as hex is the fix that works on both. diff --git a/.changeset/olive-moons-shave.md b/.changeset/olive-moons-shave.md deleted file mode 100644 index 876df2eb9..000000000 --- a/.changeset/olive-moons-shave.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'stash': patch ---- - -Correct the release-workflow section of the bundled `stash-supply-chain-security` -skill. It described the no-Actions-cache rule as a property of one file — "no -`cache:`, `package-manager-cache: false`, `pnpm/action-setup` with -`cache: false`" — which is no longer the whole rule. - -The gate now follows any local composite action or reusable workflow the job -reaches, so the constraint is on the whole call tree rather than the workflow -file. And every published `uses:` must appear in the script's `AUDITED_ACTIONS` -allowlist: the check cannot open a published action to prove it does not cache, -and caching actions are not reliably named — a `setup-` action that caches -by default has no `cache:` input and nothing in its name to match. The list is -therefore what is permitted, not what is forbidden, and adding a step to -`release.yml` or `tests-supply-chain.yml` means auditing the action and adding -it there in the same PR. diff --git a/.changeset/olive-poems-guess.md b/.changeset/olive-poems-guess.md deleted file mode 100644 index 669e33754..000000000 --- a/.changeset/olive-poems-guess.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -'@cipherstash/stack': minor ---- - -Add `EncryptionClient.getSchemas()` — the tables passed to -`Encryption({ schemas })`, returned by reference. - -This is the domain-bearing view of your schema. `getEncryptConfig()` returns -what the FFI consumes: each column builds to `{ cast_as, indexes }`, and the -concrete EQL v3 domain name is dropped. That makes `cast_as: 'number'` with an -`ope` index ambiguous across `eql_v3_integer_ord`, `smallint_ord`, `real_ord`, -`double_ord` and `numeric_ord` — so tooling that has to reason about the -*declared* domain (schema linting, drift-checking a live database's -`information_schema.columns.domain_name`) could not recover it from a client -alone. - -`getSchemas()` closes that gap. Read a column's domain with -`column.getEqlType()`, its capabilities with `column.getQueryCapabilities()`, -and its DB name with `column.getName()`: - -```typescript -for (const table of client.getSchemas()) { - for (const column of Object.values(table.columnBuilders)) { - console.log(table.tableName, column.getName(), column.getEqlType()) - } -} -``` - -`stash eql validate` is the first consumer. diff --git a/.changeset/olive-pugs-invite.md b/.changeset/olive-pugs-invite.md deleted file mode 100644 index 38c801523..000000000 --- a/.changeset/olive-pugs-invite.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -'@cipherstash/stack': patch ---- - -Adopt protect-ffi 0.31.0. - -**`clientKey` is hex, and a decoder tolerance that accepted other spellings is -gone.** Hex has always been the documented and only supported encoding for -`config.clientKey` / `CS_CLIENT_KEY` — it is what `stash env` emits and what -the docs and skills have always shown. The decoder underneath happened to fall -back to standard padded base64, which is the encoding the Rust -`stash-profile` crate uses for `~/.cipherstash/secretkey.json` on disk; that -fallback was never part of this package's contract, and nothing in the -JavaScript stack ever produced or accepted a base64 key. It is now rejected at -client construction with `invalid clientKey: expected a hex-encoded key`. - -The message deliberately says nothing more, because the underlying decode error -names the offending character and its offset and would put part of a live key -into your logs. So if construction starts failing after this upgrade, the key -you supplied is not hex — re-encode it, or drop the explicit key and let the -native client read it from the profile store. - -Reading the key from `~/.cipherstash/secretkey.json` is unaffected — that path -still uses base64, and only an explicitly supplied key is hex-only. - -**DynamoDB errors no longer report foreign error codes as encryption codes.** -`handleError` accepted any string-valued `code` on a caught error and passed it -through as a `ProtectErrorCode`, so a Node or AWS SDK failure — `ECONNRESET`, -say — surfaced as though it were an encryption error code. Codes are now checked -against the set the encryption layer actually emits, and anything else becomes -`DYNAMODB_ENCRYPTION_ERROR`. If you branch on `error.code` for DynamoDB -operations, a branch that was matching transport errors will stop. - -Also in this release, with no action needed: the WASM entry passes credentials -under the option shape 0.31 expects and no longer pre-normalises `cast_as` -(the native layer does it on both bindings now), and bulk operations no longer -forward their internal correlation id across the FFI boundary, which 0.31 -rejects rather than ignores. diff --git a/.changeset/ore-unavailable-at-install.md b/.changeset/ore-unavailable-at-install.md deleted file mode 100644 index cd228f51d..000000000 --- a/.changeset/ore-unavailable-at-install.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -'stash': minor ---- - -Report the ORE-unavailable case once, at install time, instead of leaving it to surface as a failing predicate the first time a column is cast. - -The EQL bundle skips the ORE btree operator class when the installing role cannot create one and poisons every `_ord_ore` domain with a loud-failure CHECK in its place. That is a supported configuration — but nothing said so where the choice between `types.*Ord` and `types.*OrdOre` is actually made, so the trade was discovered at query time. - -- **`stash eql preflight` now probes whether the role can create an operator class** and reports it as a non-blocking `ORE operator class` row (`creatable` / `not creatable` / `unknown`; `canCreateOperatorClass` in `--json`). It is *probed*, not inferred from `superuser`: `CREATE OPERATOR CLASS` is superuser-gated in stock PostgreSQL, but AWS RDS and Aurora let their admin role create one while cloud-hosted Supabase does not, so `rolsuper` is not evidence either way. The probe attempts the DDL inside a transaction it always rolls back, leaving preflight read-only; a probe that could not ask reports `unknown` rather than guessing. -- **`stash eql install` names the consequence and the remedy** on its own line when the fallback was installed, rather than as a parenthetical on the "verified" line. -- **`stash eql status` reports the ORE state** on a v3 install, so the answer survives past the install output. -- **The remedy now names a type that exists.** The previous wording pointed at the `_ord_ope` domains; the bundle creates those, but `@cipherstash/stack` ships no `types.*OrdOpe` factory, so it named a column type no schema author could declare. Every command now says `types.*Ord` (`public.eql_v3_*_ord`), which is the same CLLW-OPE ordering and has a factory behind it. -- The ORE state machine, the catalogue probe, and this copy now live in one module shared by `eql preflight`, `eql install`, `eql status`, `eql verify`, and `eql validate`, so the five commands cannot drift into disagreeing about the same catalogue fact. -- The scaffolded encryption client's type cheat-sheet now says why ordered columns should be `*Ord` rather than `*OrdOre`. diff --git a/.changeset/precise-supabase-init-and-backdated-push-guidance.md b/.changeset/precise-supabase-init-and-backdated-push-guidance.md deleted file mode 100644 index a194da85e..000000000 --- a/.changeset/precise-supabase-init-and-backdated-push-guidance.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'stash': patch ---- - -Correct two inaccuracies in the bundled `stash-cli` skill. The `stash init` overview said the **Supabase** flow always generates an EQL migration; it now says **local Supabase**, matching `resolveMigrationRoute` — only a project with local `supabase/` CLI scaffolding takes the migration-first route, while a hosted Supabase project with no `supabase/` directory falls through to a direct `stash eql install`. And the guidance for back-dating the Supabase install migration no longer recommends `supabase db push --include-all` unconditionally: on a remote where `stash eql install` has already run, pushing the file re-runs a bundle that opens with `DROP SCHEMA IF EXISTS eql_v3 CASCADE`, dropping every index, constraint, and RLS policy on those schemas. That case is now `supabase migration repair --status applied ` (ledger only, no SQL) — after confirming EQL is genuinely installed on that remote with `psql "$REMOTE_DATABASE_URL" -Atc "select eql_v3.version()"`; `--include-all` stays for a remote that still needs the SQL applied. - -The same correction lands in the CLI itself, and in the two other places that repeated the old advice — the `stash-supabase` skill and the CLI README. `stash eql migration --supabase` warns when the project already has EQL-referencing migrations that sort before the install it is about to write, and that warning carried the identical blanket `--include-all` advice. Since this warning only fires on projects that ran `stash eql install` directly — so the remote usually already has the bundle and is missing only the ledger row — it now names `supabase migration repair --status applied ` as the remedy, spells out the `DROP SCHEMA IF EXISTS eql_v3 CASCADE` hazard of pushing the file instead, and keeps `--include-all` for the remote that has not had the SQL applied. diff --git a/.changeset/prisma-next-0-17.md b/.changeset/prisma-next-0-17.md deleted file mode 100644 index 7fec0c84a..000000000 --- a/.changeset/prisma-next-0-17.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -'@cipherstash/stack-prisma': major -'stash': patch ---- - -Upgrade the Prisma Next integration to Prisma Next 0.17 (the `prisma/prisma` "Prisma 8" main line). Consuming apps must move to the 0.17 publish surface to use this release. - -Breaking changes for consumers: - -- **Dependencies**: the `@prisma-next/*` scope is retired. An application now depends on exactly one database facade — `@prisma/orm-postgres@0.17.0` — plus this extension. `@cipherstash/stack-prisma` itself builds against `@prisma/orm-framework`, `@prisma/orm-family-sql`, and `@prisma/orm-toolchain`, and declares `@prisma/orm-target-postgres` as a peer dependency. -- **Generated imports**: the emitted `contract.d.ts` now imports this extension's types from `@cipherstash/stack-prisma/{codec-types,operation-types,runtime}` (previously the stale `@prisma-next/extension-cipherstash/*` names, which no longer resolve). Re-run `prisma-next contract emit` after upgrading. -- **Contract and migration hashes**: 0.17 renames the contract's `extensionPacks` key to `extensions` and drops the `sha256:` prefix from every content hash, so every contract `storageHash` and `migrationHash` changes. The shipped migration set is re-anchored accordingly; consumer repos convert their checked-in `migrations/` trees with the upstream `strip-sha256-hash-prefixes` codemod and `scripts/migrate-migrations-layout.mjs` (the content-addressed `migrations/snapshots/` store replaces per-migration `end-contract.*` files). Vendored `migrations/cipherstash/` copies must be refreshed (delete and re-run `prisma-next migration plan`, or copy the shipped artefacts). -- **Codec descriptors**: the v3 codec descriptors are now Postgres target descriptors (`nativeTypeFor` / `projectJson` via `postgresCodec`), replacing the deleted `meta.db.sql.postgres` channel, and the pack meta publishes them through `types.codecTypes.codecDescriptors` (0.17 removed `codecInstances`). -- **Config**: in `prisma-next.config.ts` use the facade's `defineConfig` from `@prisma/orm-postgres/config` with `extensions: [cipherstash]` (`extensionPacks` fails loudly on 0.17). - -The `stash` CLI now also detects Prisma Next projects that depend on the 0.17 packages (`prisma-next` or any `@prisma/orm-*` package), and the bundled `stash-prisma` skill documents the 0.17 surface. diff --git a/.changeset/prisma-skill-psl-expression-indexes.md b/.changeset/prisma-skill-psl-expression-indexes.md deleted file mode 100644 index ece239a66..000000000 --- a/.changeset/prisma-skill-psl-expression-indexes.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'stash': patch ---- - -Correct the bundled `stash-prisma` and `stash-indexing` skills for Prisma Next 0.17's functional-index support: `@@index` now takes an `expression` argument, so the `eql_v3.*` functional indexes are declared directly in `schema.prisma` (expression indexes require a `name` or `map`) instead of hand-written raw-SQL migration operations. Also documents the physical-name rule (`name:` gains a content-hash suffix, `map:` pins the exact name), the TS contract form (`type` requires `options`), and that `CREATE INDEX CONCURRENTLY` cannot run through the migration runner's transaction — via `rawSql` or otherwise. diff --git a/.changeset/protect-ffi-lazy-load.md b/.changeset/protect-ffi-lazy-load.md deleted file mode 100644 index 2dd592866..000000000 --- a/.changeset/protect-ffi-lazy-load.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@cipherstash/protect-ffi': minor ---- - -The native binding now loads lazily. Importing the package no longer resolves the platform binary: the CJS entry pulls in `./load.cjs` with `import native = require(...)`, which emits a plain `require` and leaves the `@neon-rs/load` proxy untouched, where the previous `import * as native from` compiled to an `__importStar` that enumerated the module and forced resolution at module-evaluation time. `require('@cipherstash/protect-ffi')` therefore succeeds with no binding installed, and the same `MODULE_NOT_FOUND` — identical `code` and `message` — is raised on first use instead of at import. - -Adds `assertNativeBindingAvailable()`: a diagnostic entry point that resolves the platform binary and throws the loader's error unwrapped if it is missing, without constructing a client, reading credentials or touching the network. It exists because laziness removed the implicit probe that importing the package used to be, and there is no consumer-side replacement — `lib/load.cjs` is not an exported path (`ERR_PACKAGE_PATH_NOT_EXPORTED`), reading one of this module's own exports never reaches the proxy, and forcing it through a real wrapper means picking one whose argument validation does not reject first. diff --git a/.changeset/protect-ffi-repository-url.md b/.changeset/protect-ffi-repository-url.md deleted file mode 100644 index 78a7f5125..000000000 --- a/.changeset/protect-ffi-repository-url.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -'@cipherstash/protect-ffi': patch ---- - -Point the published metadata at `cipherstash/stack`, the repository these -packages are now built and published from. The wrapper's `repository.url`, -`bugs.url` and `homepage`, and each platform package's `repository.url`, all -named `cipherstash/protectjs-ffi`; each platform package's -`repository.directory` also named `platforms/`, which resolves from -the root of the repository named above and so addressed nothing here. - -npm requires `repository.url` to match the publishing repository exactly for a -trusted publish, and rejects a mismatch rather than warning about it. A stale -`repository.directory` fails more quietly: the publish succeeds and the source -link on the package page 404s. - -The one repository URL that reaches an end user at runtime moves too — the Rust -core's `InvariantViolation` error asks the reader to file an issue, and the -repository it pointed at is archived at the end of the publishing cutover. diff --git a/.changeset/proud-ravens-repeat.md b/.changeset/proud-ravens-repeat.md deleted file mode 100644 index 86f5fc39d..000000000 --- a/.changeset/proud-ravens-repeat.md +++ /dev/null @@ -1,84 +0,0 @@ ---- -'stash': minor ---- - -Rewrite `db validate` as `eql validate`, for the EQL v3 domain-type vocabulary. - -**Fixes a false finding on the most ordinary v3 columns.** The old rule set -checked for `ore` / `unique` / `match` / `ste_vec` indexes and never learned -about `ope`. EQL v3's default ordering domains emit `ope`, so -`types.IntegerOrd('age')` and `types.TimestampOrd('created_at')` were both -reported as "Column is encrypted but has no indexes — it will not be -searchable". They are now silent. - -The command reads your tables through the new -`EncryptionClient.getSchemas()`, so it sees each column's **concrete domain** -rather than the lossy encrypt config, and gains a database pass when one is -reachable. - -Schema checks (no database needed): - -| Rule | Severity | -|---|---| -| An `_ord_ore` domain is declared — its ORE operator class needs superuser | Warning | -| Storage-only column: encrypts and decrypts, carries no query terms | Info | -| Searchable `boolean` column | Error | -| Free-text `match` index on a non-text domain | Error | -| Encrypted-JSONB (`ste_vec`) index without `types.Json` | Error | - -Database checks (skipped with a notice, not a failure, when no database is -reachable): - -| Rule | Severity | -|---|---| -| EQL v3 is not installed — reported once, remaining database checks skipped | Error | -| A declared table lives in a different schema than the one searched | Warning | -| A declared table is in the searched schema but invisible to the connected role | Warning | -| A declared table name carries a schema qualifier (`schema.table`) — not checked | Warning | -| A declared table exists in no schema at all | Error | -| A declared column is missing from a table that was found | Error | -| The database column's domain differs from the declared one | Error | -| The database column is still plain (no EQL domain) | Error | -| An `_ord_ore` domain where the EQL install could not create the ORE operator class | Error | -| A queryable column with no functional index over its term extractor | Info | -| A declared table name that resolved in the searched schema also exists in another one | Info | - -`--exclude-operator-family` is removed: it warned that an `ore` index would not -support `ORDER BY` without operator families, and the pinned EQL v3 bundle -self-adapts. `eql install` / `eql upgrade` had already rejected the flag; -`validate` was its last consumer. - -The database pass inspects `current_schema()` only, and distinguishes four -reasons a declared table can be missing from it, so that only the last fails -the command. In another schema (Prisma `multiSchema`, a tenant schema): a -Warning naming that schema. Present but invisible to the connected role: a -Warning carrying the `GRANT SELECT` to run — `information_schema` reports only -what the role holds a privilege on, so a missing grant is not a missing -migration. Declared as `schema.table`: a Warning saying it was not checked, -because validate matches table names unqualified. Absent everywhere: an Error. -Reported once per table rather than once per column. - -The relation lookup that answers those questions excludes `pg_*` and -`information_schema`. Unscoped it matched the system views named `columns`, -`domains`, `parameters`, `routines`, `sequences`, `tables` and `triggers` — all -ordinary application table names — so a project declaring one of them that had -not run its migration was told the table "exists in schema information_schema", -as a Warning, and the command exited 0 on a genuinely unapplied migration. - -An unqualified name found in more than one schema is now reported as an Info -naming the relation that was actually checked (`"public"."users"`) and the -other schemas holding that name. A bare name resolves through `search_path`, so -`users` in both `public` and Supabase's `auth` left it ambiguous which relation -every other finding described. Info, not Warning: it must not fail or -de-clean an ordinary Supabase project. - -Two of those used to exit 1 and no longer do: a privilege-invisible table and -a schema-qualified declaration were both reported as "does not exist in any -schema", which sent people to re-run a migration that had already run. - -Against a project whose `@cipherstash/stack` predates `getSchemas()`, validate -says so and falls back to the encrypt config, running the index-derived rules -and skipping the domain ones. - -`stash db validate` keeps working as a deprecated alias, like `db install` / -`db upgrade` / `db status`. Exits 1 on errors only. diff --git a/.changeset/quiet-moons-repair.md b/.changeset/quiet-moons-repair.md deleted file mode 100644 index 0687c7be2..000000000 --- a/.changeset/quiet-moons-repair.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'stash': patch ---- - -Document the Dependabot major-version policy in `skills/stash-supply-chain-security`: no entry configures a `semver-major-days` cooldown, because every entry ignores `version-update:semver-major` and cooldown applies to version updates only. The supply-chain e2e suite now pins both halves of that relationship. diff --git a/.changeset/ship-changelogs-in-tarballs.md b/.changeset/ship-changelogs-in-tarballs.md deleted file mode 100644 index 5e574d85e..000000000 --- a/.changeset/ship-changelogs-in-tarballs.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -'@cipherstash/stack-prisma': patch -'@cipherstash/protect-ffi': patch ---- - -Ship `CHANGELOG.md` inside the published tarball. It was missing from `files`, -so the release notes for these packages were readable on GitHub and on the npm -web page but not in the package you actually install — which is the copy you -have when something breaks offline, or when the repository has moved. - -`@cipherstash/stack-drizzle` and `@cipherstash/stack-supabase` gain it in the -same release, as do the six `@cipherstash/protect-ffi-` packages. diff --git a/.changeset/skills-managed-platforms.md b/.changeset/skills-managed-platforms.md deleted file mode 100644 index 9c53be49d..000000000 --- a/.changeset/skills-managed-platforms.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -'stash': patch ---- - -New `stash-managed-platforms` skill: implementing CipherStash on a managed AI app platform (Lovable, v0, Bolt, Replit). - -These platforms share a shape — no shell the developer controls, an edge/Workers runtime, a database role that is not `postgres`, and schema changes only through the platform's own migration tool — and every one of those changes the setup. The skill covers the WASM entry, running `stash auth login --json` headlessly in an ephemeral sandbox, minting `CS_*` with `stash env`, installing EQL as a non-`postgres` role (including generating a migration instead of installing directly), which predicates survive PostgREST, and why `encryptedSupabase` cannot be constructed inside a Worker. - -The costly one is first, because it decides whether anyone gets any further: **use `@cipherstash/stack` with the `@cipherstash/stack/wasm-inline` entry.** `@cipherstash/protect` is the deprecated predecessor, and reasoning from its `@cipherstash/protect-ffi` dependency to "there is no way to run this on an edge runtime" is a wrong conclusion drawn from the wrong package. That dead end cost an agent a full turn on a real project before it found `stash`. The same correction is now stated in `stash-edge`'s entry table, where an agent comparing runtimes will hit it. - -Two things were also lifted above the fold in `stash-supabase`: a pointer to the new skill, and a one-line summary of what does and does not survive PostgREST (`eq`/`neq`/`in`/`match()` and the range filters do; encrypted `matches()` and JSON containment do not). The full treatment was correct but ~500 lines down, which is not where a time-pressured agent finds it. - -Registered for the `supabase` and `postgresql` integrations in both the CLI and wizard skill maps, so it installs into `.claude/skills` / `.codex/skills` and inlines into `AGENTS.md` on those paths. diff --git a/.changeset/skills-supabase-edge-entry.md b/.changeset/skills-supabase-edge-entry.md deleted file mode 100644 index e913a492b..000000000 --- a/.changeset/skills-supabase-edge-entry.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'stash': patch ---- - -Skills: `encryptedSupabase` can now be constructed in a Worker, so the guidance that said it could not has been corrected. - -`skills/stash-managed-platforms` replaces its "cannot be constructed in a Worker" section with the two things that must both be right — import `@cipherstash/stack-supabase/wasm-inline` rather than the package root, and declare your `schemas` so nothing introspects — plus what declared mode gives up (`select('*')`, `from()` on an undeclared table, and the drift check) and how to keep the drift check on Node by passing `databaseUrl` as well. - -`skills/stash-supabase` and `skills/stash-edge` gain the same correction where each would be read: the above-the-fold managed-platform callout, and the runtime-entry table respectively. diff --git a/.changeset/skills-type-capability-matrix.md b/.changeset/skills-type-capability-matrix.md deleted file mode 100644 index 29405728f..000000000 --- a/.changeset/skills-type-capability-matrix.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -'stash': patch ---- - -Add a type → predicate → domain → index capability matrix to the `stash-encryption` skill, cross-linked from `stash-indexing` and `stash-postgres`. - -Picking the wrong `types.*` factory is silent at authoring time — there is no type error and no runtime warning, just a predicate that never runs. The skills documented the capability *suffixes* and the families they apply to, but never the 40 concrete factories in one lookup, so answering "can `types.Double` do a range query" meant composing two tables and knowing the exceptions. It cannot: `types.Double` is storage-only. - -The new matrix has one row per factory with its Postgres column domain, the predicates it supports, the extractor to index it through, and whether it works on managed Postgres. Alongside it: a note on which schema holds what (`public` for column domains, `eql_v3` for query domains and operator functions, `eql_v3_internal` for index-term types) and why the Supabase grants have to cover the last two. - -Two corrections came out of writing it: - -- The `Ord` vs `OrdOre` callout said the install "disables the `_ord_ore` domains" on managed Postgres. Precisely: the bundle adds an always-raising `eql_ore_unavailable` CHECK to them, so a *write* fails — the domain is unusable, not merely unindexed. The callout now says that, notes that RDS and Aurora do support ORE while cloud-hosted Supabase does not, and points at `stash eql preflight` / `eql status` rather than asking the reader to guess. -- The `stash-postgres` naming table omitted `types.TextOrdOre` entirely (its `` shorthand covers only the numeric and temporal families). Added. - -A new test derives the matrix from the `types` namespace and fails if the skill disagrees — every factory present exactly once, mapped to the domain it actually builds, naming the extractors it actually emits and none it does not, with every ORE row marked unusable where the operator class is absent. diff --git a/.changeset/stack-2-0-0-version-alignment.md b/.changeset/stack-2-0-0-version-alignment.md deleted file mode 100644 index 2c7064bfc..000000000 --- a/.changeset/stack-2-0-0-version-alignment.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -'stash': major -'@cipherstash/stack': major -'@cipherstash/stack-drizzle': major -'@cipherstash/stack-supabase': major -'@cipherstash/wizard': major ---- - -**Why this package went to 2.0.0.** The major version number comes from -`@cipherstash/stack-prisma`, which moves to Prisma Next 0.17 — a breaking change -for its consumers, with the upgrade steps in its own Major Changes entry. These -six packages share one version line, so a major in any of them takes all six to -the same number: - -- `stash` -- `@cipherstash/stack` -- `@cipherstash/stack-drizzle` -- `@cipherstash/stack-supabase` -- `@cipherstash/stack-prisma` -- `@cipherstash/wizard` - -They are versioned together on purpose. `stash init` pins the versions of the -packages it installs and the CLI embeds that map at build time, so a package -shipping alone would leave the CLI recommending versions that no longer match -what is published, and warning about a skew it had itself created. - -**This does not mean every package in the release is drop-in.** The version -number is shared; the changes are not. Two changes elsewhere in this release -need action from some users, and neither is filed under Major Changes — each is -recorded at the level its own author judged correct. They are named here so you -do not have to read six changelogs to find them: - -- **`@cipherstash/stack` — `clientKey` is hex-only.** A decoder fallback that - also accepted standard padded base64 is gone, and such a key is now rejected - at client construction with `invalid clientKey: expected a hex-encoded key`. - Hex is what `stash env` emits and what the docs have always specified, so most - callers are unaffected; a key pasted out of `~/.cipherstash/secretkey.json` - (which stores base64) is not. The full entry is "Adopt protect-ffi 0.31.0" in - the **`@cipherstash/stack`** changelog; it also narrows which `error.code` - values DynamoDB operations report. -- **`stash` — `stash eql validate` lost `--exclude-operator-family`,** and two - checks that used to exit 1 no longer do. A script passing that flag, or a CI - gate relying on those exit codes, needs updating. The full entry is under - `eql validate` in the **`stash`** changelog. - -If you use neither `@cipherstash/stack-prisma` nor either of those, upgrading -1.x → 2.0.0 needs no code changes. diff --git a/.changeset/stack-diagnostics-subpath.md b/.changeset/stack-diagnostics-subpath.md deleted file mode 100644 index cb949f29d..000000000 --- a/.changeset/stack-diagnostics-subpath.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -'@cipherstash/stack': minor ---- - -Add a `@cipherstash/stack/diagnostics` subpath, for tooling that needs to prove the protect-ffi native binding is installed. - -It exports one function, `assertNativeBindingAvailable()`. Calling it forces the platform binary to load and throws the loader's own `MODULE_NOT_FOUND` — unwrapped, naming the missing `@cipherstash/protect-ffi--` package — if it is absent. Importing the subpath does not force anything, so the laziness that makes the native load cost nothing for callers that never encrypt is preserved. - -The subpath exists because there is no way to do this from outside: the package's loader is not in its `exports` map, and reading an export never reaches the `@neon-rs/load` proxy. Importing `@cipherstash/stack` itself is not a substitute either — the root entry re-exports the auth strategies, so evaluating it resolves `@cipherstash/auth`'s binding instead. This entry reaches protect-ffi and nothing else. - -It probes by calling `isEncrypted`, which has been published since 0.28.0, rather than protect-ffi's own `assertNativeBindingAvailable` — that export arrived with the lazy native load and is not in any released version, so re-exporting it would build here and fail wherever it shipped (a link-time error under ESM, an `undefined` under CJS). - -Available as both `import` and `require`. diff --git a/.changeset/stash-doctor-probes-native-bindings.md b/.changeset/stash-doctor-probes-native-bindings.md deleted file mode 100644 index ff94dcd6e..000000000 --- a/.changeset/stash-doctor-probes-native-bindings.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -'stash': patch ---- - -`stash doctor` now detects a missing native binary. Both of its checks had stopped doing so, in different ways, and each reported a green row instead. - -**The encryption engine check never loaded anything.** Since the protect-ffi native load became lazy, importing the package resolves no platform binary — `@neon-rs/load`'s proxy resolves on first use — so the probe passed with nothing installed and the failure surfaced later, at the first encrypt. It now calls `assertNativeBindingAvailable()` through the new `@cipherstash/stack/diagnostics` subpath, which forces the load. - -**It was also reporting the wrong package.** Importing `@cipherstash/stack` reaches `@cipherstash/auth`, whose binding is eager, so the encryption row was really a second auth check: one signal rendered as two rows. The diagnostics subpath does not reach auth, so each row now means what it says. - -**A missing `@cipherstash/auth` binary produced a bare `Fatal error`.** That package's napi loader swallows the resolver's `MODULE_NOT_FOUND` and throws a summary carrying no error `code`, which the CLI's native-binary classifier did not recognise — so every command, not only `doctor`, skipped the recovery guidance and printed a raw message. The classifier now recognises that shape, and prints the missing package with the reinstall steps. - -`stash doctor` exits non-zero when either platform package is missing, and reports an install of `@cipherstash/stack` that predates the diagnostics subpath as unprobeable rather than failing on it. A run that could not complete a check now ends with "stash doctor could not run every check." instead of claiming they all passed — still exit 0, since an unrunnable check is not a diagnosis. - -**A package that is installed but broken is no longer reported as "not installed".** The check for an absent package matched the package name anywhere in the failure message, and the probe's own import path contains it — so a partially installed or partially built `@cipherstash/stack` was reported as one you simply had not installed yet, in green, with nothing to suggest looking further. It now matches on the specifier Node failed to resolve. diff --git a/.changeset/supabase-declared-schemas-edge-entry.md b/.changeset/supabase-declared-schemas-edge-entry.md deleted file mode 100644 index 6bfa7b23a..000000000 --- a/.changeset/supabase-declared-schemas-edge-entry.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -'@cipherstash/stack-supabase': minor ---- - -`encryptedSupabase` can now be constructed without a Postgres connection, and there is a new edge entry that runs it off Node. - -**The rule: declare your schemas and it runs anywhere; omit them and we discover them for you, which needs a database connection and is therefore Node-only.** - -Previously the wrapper always introspected the database to derive each column's encryption config from its Postgres domain. That made it unconstructible anywhere a TCP socket to Postgres is unavailable, and cost a second, more privileged credential even on Node — the caller already had an authenticated Supabase client and had to supply a `databaseUrl` as well. - -- **Passing `schemas` with no database URL skips introspection entirely.** No connection, no `pg`, no `databaseUrl`. -- **New `@cipherstash/stack-supabase/wasm-inline` entry.** Identical wrapper, WASM engine. The package root statically imports the native engine (`@cipherstash/protect-ffi` and `@cipherstash/auth`, both Node-API), and a static import loads whether or not you encrypt anything — so an edge runtime needs a different entry, not a different code path. ESM-only, matching `@cipherstash/stack/wasm-inline`. Server-side only; not browser-safe (#804). -- **`DATABASE_URL` is now read through a guard.** On a runtime with no `process` global a bare `process.env.X` is a `ReferenceError`, not `undefined`, so the unguarded read threw during construction before declared mode could help. - -**Existing callers are unaffected.** The gate is the database URL, not the presence of `schemas`: if a URL resolves — from `options.databaseUrl` or `DATABASE_URL` — introspection still runs, and a `schemas`-passing caller still gets the drift check that verifies their declaration against the real column domains. "Pass `databaseUrl` as well" is how you keep verification while declaring types. - -What declared mode gives up, it gives up loudly rather than silently: - -- **`select('*')` and bare `select()` are refused.** `allColumns` comes only from introspection, and an unexpanded `*` reaches PostgREST without the `::jsonb` casts encrypted columns need. -- **`from()` on an undeclared table throws**, naming the declaration rather than an introspection pass that never ran. -- **The drift check is absent**, so a wrong declared domain surfaces as a `23514` CHECK violation on the first write instead of at construction. -- **`queryDomainsRequired` is forced rather than detected**, since the installed EQL version is read by introspection. This is the fail-loud direction: correct on EQL >= 3.0.2, and on an older install the operand cast fails visibly instead of emitting an operator the database will not engage. -- **Passing `databaseUrl` to the `wasm-inline` entry is refused** — it carries no Postgres driver, and saying so beats ignoring the option. - -One tradeoff is **not** loud, and is the declared-mode contract you have to hold yourself: **your declaration must cover every encrypted column of a table you query.** Nothing introspects, so a column carrying an `eql_v3` domain in the database but absent from `schemas` is treated as an ordinary plaintext column — a `select` naming it returns the raw EQL payload as data, and a filter on it sends the plaintext operand to PostgREST. The always-introspect path could not do this (undeclared columns were synthesized from their domains). Declare every encrypted column, or pass `databaseUrl` so introspection fills the gaps. - -An ambient `DATABASE_URL` no longer overrules a declaration, and is consulted only by a build that could act on it: on the edge entry — which cannot introspect at all — it is never read, so a `DATABASE_URL` that happens to be set in the environment cannot break a declared-mode client. On the native entry, passing `schemas` without an explicit `databaseUrl` ignores the variable and warns that the declaration is unverified. The refusal of a `databaseUrl` on the edge entry now keys on the option you actually passed, so it can never fire for a value you did not write. Previously a stray variable silently exited declared mode — introspecting a database the caller never named on Node, and on the edge entry throwing "drop databaseUrl" about an option never passed. - -**The edge entry adapts the WASM client rather than casting to it.** The two engines are not drop-in for each other, and every difference is silent at construction — the entry would have built a client happily while each query through it failed. `decryptModel` / `bulkDecryptModels` require the table on WASM and derive it from the payloads on native (both call sites now pass it, which native ignores); WASM operations are plain Results with no `.withLockContext()` or `.audit()`, so both are attached and throw a sentence naming the gap rather than a bare `TypeError`; and `bulkEncrypt` is deliberately not forwarded, selecting the supported per-term fallback instead of a mismatched signature. Lock context is a real capability gap on the WASM engine (cipherstash/stack#797) — failing loudly is the only honest option, since silently dropping the claim would write values any keyset holder could decrypt. - -The edge entry's options are also typed for what it actually requires: `schemas` and a `WasmClientConfig` `config` are both mandatory (there is no `~/.cipherstash` to discover credentials from), and `databaseUrl` is absent from the type as well as refused at runtime. Previously the shared factory's erased config type let an edge caller omit credentials entirely and reach a `TypeError` from inside the engine. - diff --git a/.changeset/supabase-eql-migration-file.md b/.changeset/supabase-eql-migration-file.md deleted file mode 100644 index 75af4108c..000000000 --- a/.changeset/supabase-eql-migration-file.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -'stash': minor ---- - -Add `stash eql migration --supabase`, so an EQL v3 install survives `supabase db reset` (#613). - -Supabase projects previously had only `stash eql install --supabase`, which applies the SQL directly to a running database. `supabase db reset` — the ordinary local development loop — drops that database and replays `supabase/migrations/`, so the install was wiped and the next query failed with `type "eql_v3_encrypted" does not exist`. There was no supported way to get EQL into the migrations directory. - -`stash eql migration --supabase` now writes `supabase/migrations/_cipherstash_eql.sql`, carrying the EQL v3 bundle, the `anon` / `authenticated` / `service_role` grants, and the `cipherstash.cs_migrations` tracking schema — so one `supabase db reset` provisions everything `stash encrypt` needs. The file is timestamped at generation time, so it sorts after everything already applied and pushes without `--include-all`. A second run exits rather than adding a duplicate install; `--force` regenerates the existing one in place. - -The command now warns when the migrations directory already holds EQL-referencing migrations that sort *before* the install it is about to write. A project that ran `stash eql install` directly and then added `public.eql_v3_*` columns against the live database gets an install stamped today — after those migrations — and `supabase db reset`, which replays in version order with no dependency awareness, then fails with `type "eql_v3_text_search" does not exist`. The warning names the specific files and the remedy (rename the install below the earliest of them, then reconcile each remote — see below). It fires on `--dry-run` too, and nothing is renamed automatically — the ordering of someone else's deployed history is not ours to change silently. - -That warning's remote guidance now requires you to verify the remote before writing to its ledger. It splits by whether the remote already has EQL: one where it does needs only the ledger row (`supabase migration repair --status applied `, which runs no SQL — pushing the file instead re-runs a bundle opening with `DROP SCHEMA IF EXISTS eql_v3 CASCADE`), and one where it does not needs the SQL genuinely applied (`supabase db push --include-all`, the back-dated version being a gap in the middle of that history). Previously the first branch was recommended on an assumption the user was never asked to check, and it is the one operation here with no self-correcting failure: mark a version applied on a remote that never ran the SQL and EQL is permanently absent *and* permanently marked applied, so no future push installs it and the first migration referencing `eql_v3` fails with nothing pointing at the cause. The warning now prints the check first — `psql "$REMOTE_DATABASE_URL" -Atc "select eql_v3.version()"` — and says never to mark applied when it errors. It asks for `eql_v3.version()` rather than the `eql_v3` schema deliberately: that function is created by the bundle's closing statements, so it cannot resolve on an install that aborted partway, while the schema is created by its opening ones and survives one. The same correction lands in the `stash-cli` and `stash-supabase` skills and the CLI README, and a guard test now fails the build if a shipped skill recommends the ledger-only repair without that check above it. - -`--force`'s follow-up guidance was wrong and is now correct. It said to re-apply with `supabase db reset` (local) **or `supabase db push` (remote)**, but a push never re-applies a rewritten migration: the Supabase CLI decides what is pending by comparing versions, never file content, so an in-place rewrite keeping its version is skipped and push reports `Remote database is up to date.` The remote recipe is now `supabase migration repair --status reverted ` (tracking table only — it applies no SQL) followed by `supabase db push`, with `--include-all` called out as a conditional: it is needed only when migrations sort *after* the install, which leaves the reverted version as a gap in the middle of remote history. Reverting the newest version leaves it at the tail, where a plain push applies it — and the flag applies every out-of-order migration you have, so recommending it unconditionally was itself a hazard. The warning also names the hazard it never mentioned: the EQL bundle opens with `DROP SCHEMA IF EXISTS eql_v3 CASCADE`, so re-applying drops every index, constraint, and RLS policy that references `eql_v3` / `eql_v3_internal` — free on a fresh `db reset`, destructive on a populated remote. - -`--out` on a bare `--supabase` now warns. The Supabase CLI's migrations directory is not configurable — `supabase db reset` and `supabase db push` read `/supabase/migrations` and nothing else, `config.toml` has no key for it, and `--workdir` relocates the whole `supabase/` directory rather than this subdirectory — so an install written elsewhere is never applied, which is the original bug relocated. The flag still writes the file (a project may apply that directory through its own tooling) but names the consequence, on `--dry-run` too. `--out` alongside `--drizzle --supabase` is unaffected: there it is drizzle-kit's output directory. - -`--supabase` keeps its existing meaning alongside `--drizzle` (append the role grants to the Drizzle migration); only a bare `--supabase` selects the new emitter. - -`stash init --supabase` now generates that migration instead of installing directly, when the project has local `supabase/` scaffolding — a hosted project without it still installs directly. Re-running init over a project that already has an install migration reports it and moves on, rather than treating the duplicate refusal as a failed setup. Its next steps no longer tell you to run `eql install --supabase` and then `supabase db reset`, which was the exact sequence that destroyed the install. - -`stash init`'s EQL summary line now distinguishes the migration it wrote from one it merely found. A re-run over an existing install migration says "EQL migration **already present**" instead of "EQL migration generated" — same apply guidance, same successful exit, but no claim about work the run did not do. - -`stash init`'s EQL prompt now names the action for the route it is actually on. On the migration-first routes it asks whether to generate a migration (naming `supabase/migrations/` or your Drizzle migrations folder) rather than whether to install into your database, which described the wrong action on both. Declining is fixed the same way: the retry hint is now `stash eql migration --supabase` / `--drizzle` on those routes instead of `stash eql install`, which on Supabase would reinstate the very bug above. - -`stash init` now routes on the integration flags themselves rather than on the provider's display name, so combining them works. `stash init --drizzle --supabase` is accepted — and is the natural invocation for a Drizzle project on Supabase — but init joined the matched flags into a single provider name (`drizzle-supabase`) for referrer tracking and then compared that name against `'drizzle'` and `'supabase'` everywhere it had a decision to make. Every comparison went false. A local Supabase stack answers on `127.0.0.1:54322`, so host detection reports plain Postgres and the flags are the only signal left: the run installed EQL directly instead of writing a migration — nothing in `supabase/migrations/`, no `anon` / `authenticated` / `service_role` grants — which is the #613 failure this release exists to fix, reached through a flag combination the CLI accepts. The same fall-through dropped the `supabase status` hint when resolving `DATABASE_URL` (the one lookup that finds a local stack's URL), lost the Prisma Next branch for `--prisma --supabase` — scaffolding a client Prisma Next never uses and running a duplicate EQL install that races `prisma-next migrate`'s journal — and installed no integration adapter at all, where a combined run needs both `@cipherstash/stack-drizzle` and `@cipherstash/stack-supabase`. The provider now carries the matched flags alongside its name and every step reads those; the combined name is still exactly what gets recorded as the referrer, it is simply no longer what the CLI branches on. Drizzle still wins the migration route when both flags fire — it owns the migration history, and `--supabase` is the grants modifier there. Single-flag runs behave exactly as before. - -Also corrects the remote apply command across the Supabase guidance: a bare `supabase migration up` targets the local database, so the instructions now say `supabase db push`. - -Also corrects the `eql install --migration` removal message, which pointed every Supabase user at `--drizzle`. - -The Supabase CLI behaviour all of the above depends on is now pinned by a live test rather than by reading the CLI's source. `supabase-push.live.test.ts` drives the real binary against a real Postgres — `db push --db-url` needs neither Docker nor a linked project — and covers: the generated install applying with no `--include-all`; `anon` reaching `eql_v3` via `SET ROLE` through the grants carried in the emitted file (not just the ones `eql install --direct` applies); an out-of-order version aborting the whole push rather than being skipped; a `--force`-replaced file never re-applying; `--include-all` being needed only for the gap case; and a leaked `.tmp` file being ignored. Gated on `STASH_TEST_SUPABASE_DB_URL` + `STASH_TEST_SUPABASE_CLI`, so the default suite is unchanged. diff --git a/.changeset/supply-chain-skill-ffi-release-path.md b/.changeset/supply-chain-skill-ffi-release-path.md deleted file mode 100644 index 22b5bb65f..000000000 --- a/.changeset/supply-chain-skill-ffi-release-path.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -'stash': patch ---- - -Document the native-binding publish path in the bundled -`stash-supply-chain-security` skill, and correct what it claims about -frozen-lockfile coverage. - -`@cipherstash/protect-ffi` and its six platform packages ship compiled binaries, -which `changeset publish` cannot produce — it packs from the workspace, where -`index.node` is a build output. The skill now describes the pipeline that does: -a registry-state gate, a target-explicit build matrix in a reusable workflow, -and a publish step that ships the six platform packages before the wrapper and -tags all seven itself, because changesets tags only what it published. It also -records two npm requirements that fail late and quietly — `repository.url` must -match the publishing repository exactly (and `repository.directory` resolves -from that repository's root), and trusted-publisher configurations created after -2026-05-20 need an explicit "Allowed actions" selection. - -It also now states, per action, which input disables that action's built-in -caching and what that input defaults to. Two of the three default to caching -ON — `actions/setup-node`'s `package-manager-cache` and `jdx/mise-action`'s -`cache` — so omitting the key is not "no caching", it is caching spelled -invisibly, and the gate's generic rule only sees a *truthy* value rather than a -missing one. - -The OIDC section said `permissions: id-token: write` is what mints the token and -left it there. It now says where that grant belongs: on the publishing jobs, not -at the workflow level. A trusted publisher is registered against a repository -*and a workflow filename*, so npm accepts a token minted by any job in the -registered file — declaring the scope at the top hands the publish credential to -every job that does not override it, including ones added later. - -The frozen-lockfile section said the rule was enforced in `tests.yml`, which was -true and misleading: that is where it was *checked*, and `release.yml` ran a -bare `pnpm install` from the day it was written — so the single install permitted -to resolve outside the lockfile was the one whose output goes to the registry. -The install is fixed and the check now scans every workflow and every local -composite action. diff --git a/e2e/CHANGELOG.md b/e2e/CHANGELOG.md index 990c97036..3bb946197 100644 --- a/e2e/CHANGELOG.md +++ b/e2e/CHANGELOG.md @@ -1,5 +1,37 @@ # @cipherstash/e2e +## 0.0.4 + +### Patch Changes + +- Updated dependencies [a1b53af] +- Updated dependencies [c82c59d] +- Updated dependencies [71c802a] +- Updated dependencies [1a3e980] +- Updated dependencies [4b2b331] +- Updated dependencies [7785643] +- Updated dependencies [66ba41a] +- Updated dependencies [05f84bc] +- Updated dependencies [93d62c2] +- Updated dependencies [6c211f5] +- Updated dependencies [b2dbcf9] +- Updated dependencies [41c4531] +- Updated dependencies [c795225] +- Updated dependencies [1486a46] +- Updated dependencies [d473745] +- Updated dependencies [d348f84] +- Updated dependencies [3c7ed59] +- Updated dependencies [1a3e980] +- Updated dependencies [68b4d68] +- Updated dependencies [8dc2ebe] +- Updated dependencies [483a925] +- Updated dependencies [483a925] +- Updated dependencies [1d10003] +- Updated dependencies [cf08c3b] + - stash@2.0.0 + - @cipherstash/stack@2.0.0 + - @cipherstash/wizard@2.0.0 + ## 0.0.3 ### Patch Changes diff --git a/e2e/package.json b/e2e/package.json index 41315fdfd..fe2bfc891 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -1,6 +1,6 @@ { "name": "@cipherstash/e2e", - "version": "0.0.3", + "version": "0.0.4", "private": true, "description": "End-to-end tests that exercise built CipherStash binaries and cross-package behaviour.", "type": "module", diff --git a/examples/basic/CHANGELOG.md b/examples/basic/CHANGELOG.md index 9b07d0ef6..4262e59c7 100644 --- a/examples/basic/CHANGELOG.md +++ b/examples/basic/CHANGELOG.md @@ -1,5 +1,16 @@ # @cipherstash/basic-example +## 1.2.15 + +### Patch Changes + +- Updated dependencies [93d62c2] +- Updated dependencies [6c211f5] +- Updated dependencies [8dc2ebe] +- Updated dependencies [483a925] + - @cipherstash/stack@2.0.0 + - @cipherstash/stack-drizzle@2.0.0 + ## 1.2.14 ### Patch Changes diff --git a/examples/basic/package.json b/examples/basic/package.json index 3767080d5..f03c21406 100644 --- a/examples/basic/package.json +++ b/examples/basic/package.json @@ -1,7 +1,7 @@ { "name": "@cipherstash/basic-example", "private": true, - "version": "1.2.14", + "version": "1.2.15", "type": "module", "scripts": { "start": "tsx index.ts", diff --git a/examples/prisma/CHANGELOG.md b/examples/prisma/CHANGELOG.md index b5fad4108..f4accb019 100644 --- a/examples/prisma/CHANGELOG.md +++ b/examples/prisma/CHANGELOG.md @@ -1,5 +1,18 @@ # @cipherstash/prisma-next-example +## 0.1.1 + +### Patch Changes + +- Updated dependencies [93d62c2] +- Updated dependencies [6c211f5] +- Updated dependencies [c795225] +- Updated dependencies [ba3830f] +- Updated dependencies [8dc2ebe] +- Updated dependencies [483a925] + - @cipherstash/stack@2.0.0 + - @cipherstash/stack-prisma@2.0.0 + ## 0.1.0 ### Minor Changes diff --git a/examples/prisma/package.json b/examples/prisma/package.json index 582f0512a..d294aa36f 100644 --- a/examples/prisma/package.json +++ b/examples/prisma/package.json @@ -1,7 +1,7 @@ { "name": "@cipherstash/prisma-example", "private": true, - "version": "0.1.0", + "version": "0.1.1", "description": "End-to-end example of @cipherstash/stack-prisma: searchable application-layer encryption for Postgres with Prisma Next, using @cipherstash/stack as the SDK.", "type": "module", "scripts": { diff --git a/packages/bench/CHANGELOG.md b/packages/bench/CHANGELOG.md index 8b1b6c222..d0c5488d5 100644 --- a/packages/bench/CHANGELOG.md +++ b/packages/bench/CHANGELOG.md @@ -1,5 +1,16 @@ # @cipherstash/bench +## 0.0.6 + +### Patch Changes + +- Updated dependencies [93d62c2] +- Updated dependencies [6c211f5] +- Updated dependencies [8dc2ebe] +- Updated dependencies [483a925] + - @cipherstash/stack@2.0.0 + - @cipherstash/stack-drizzle@2.0.0 + ## 0.0.5 ### Patch Changes diff --git a/packages/bench/package.json b/packages/bench/package.json index ef8999be7..2bd33ba3b 100644 --- a/packages/bench/package.json +++ b/packages/bench/package.json @@ -1,6 +1,6 @@ { "name": "@cipherstash/bench", - "version": "0.0.5", + "version": "0.0.6", "private": true, "description": "Performance / index-engagement benchmarks for stack integrations (Drizzle, encryptedSupabase, Prisma).", "type": "module", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 0bd312d24..468600fa2 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,352 @@ # stash +## 2.0.0 + +### Major Changes + +- 8dc2ebe: **Why this package went to 2.0.0.** The major version number comes from + `@cipherstash/stack-prisma`, which moves to Prisma Next 0.17 — a breaking change + for its consumers, with the upgrade steps in its own Major Changes entry. These + six packages share one version line, so a major in any of them takes all six to + the same number: + + - `stash` + - `@cipherstash/stack` + - `@cipherstash/stack-drizzle` + - `@cipherstash/stack-supabase` + - `@cipherstash/stack-prisma` + - `@cipherstash/wizard` + + They are versioned together on purpose. `stash init` pins the versions of the + packages it installs and the CLI embeds that map at build time, so a package + shipping alone would leave the CLI recommending versions that no longer match + what is published, and warning about a skew it had itself created. + + **This does not mean every package in the release is drop-in.** The version + number is shared; the changes are not. Two changes elsewhere in this release + need action from some users, and neither is filed under Major Changes — each is + recorded at the level its own author judged correct. They are named here so you + do not have to read six changelogs to find them: + + - **`@cipherstash/stack` — `clientKey` is hex-only.** A decoder fallback that + also accepted standard padded base64 is gone, and such a key is now rejected + at client construction with `invalid clientKey: expected a hex-encoded key`. + Hex is what `stash env` emits and what the docs have always specified, so most + callers are unaffected; a key pasted out of `~/.cipherstash/secretkey.json` + (which stores base64) is not. The full entry is "Adopt protect-ffi 0.31.0" in + the **`@cipherstash/stack`** changelog; it also narrows which `error.code` + values DynamoDB operations report. + - **`stash` — `stash eql validate` lost `--exclude-operator-family`,** and two + checks that used to exit 1 no longer do. A script passing that flag, or a CI + gate relying on those exit codes, needs updating. The full entry is under + `eql validate` in the **`stash`** changelog. + + If you use neither `@cipherstash/stack-prisma` nor either of those, upgrading + 1.x → 2.0.0 needs no code changes. + +### Minor Changes + +- c82c59d: The CLI now handles database TLS properly, so the discoverable fix for a certificate failure is never `NODE_TLS_REJECT_UNAUTHORIZED=0`. + + - Every CLI database connection honours `sslmode` and `sslrootcert` from the connection string — and `PGSSLMODE` / `PGSSLROOTCERT` from the environment when the URL carries no TLS parameters (URL wins; unlike raw node-postgres, `PGSSLROOTCERT` is actually consumed): `verify-full` (and `require`/`verify-ca`/`prefer`, kept as full verification — node-postgres's current behaviour) verifies the server certificate; `no-verify` is honoured with a one-line stderr warning; `disable` turns TLS off. Client-certificate setups (`sslcert`/`sslkey`) pass through untouched. + - CA resolution: `sslrootcert=` (libpq semantics — sole trust anchor; `sslrootcert=system` selects the system store) → `PGSSLROOTCERT` → for `*.supabase.co`/`*.supabase.com` hosts a **bundled Supabase root CA** (appended to the system roots) → the system store. `sslmode=verify-full` against Supabase — direct hosts and the pgBouncer pooler — now verifies out of the box. + - Certificate-verification failures — shaped centrally in the connection factory, so every command surfaces them — name the host and the supported remedies in order (`sslrootcert=…`, then `sslmode=no-verify` as a last resort with the consequence spelled out), and explicitly warn against `NODE_TLS_REJECT_UNAUTHORIZED=0`, which is process-wide and would also disable verification for the connections carrying CipherStash credentials. + - The node-postgres "SSL modes … are treated as aliases for verify-full" SECURITY WARNING no longer appears on every invocation against `sslmode=require` URLs: the CLI decides the TLS config itself and hands pg a URL with the TLS params stripped (fixes the upstream-advisory passthrough). + +- 71c802a: EQL installs no longer abort on managed platforms whose database role is not `postgres`, and a new `stash eql preflight` command reports role capability before anything is attempted. + + - `stash eql install` (and `eql upgrade`) now run the EQL v3 bundle in its own transaction and the Supabase role grants after it commits, so a grants failure can no longer roll back a working install. When the connecting role is not a member of `postgres` (e.g. Lovable's `sandbox_exec`), the three owner-scoped `ALTER DEFAULT PRIVILEGES FOR ROLE postgres` statements are skipped and the install completes without them — they are optional (they only cover EQL objects `postgres` might later create outside stash tooling, and stash re-grants every object on each install/upgrade); the SQL is printed as "Optional SQL — requires postgres" for operators who want it. Every plain `GRANT` still runs. Previously that single refused statement rolled back the entire install (~194 functions). + - Re-running `stash eql install` on an already-installed Supabase database now re-applies the role grants (idempotent) instead of exiting early, so an install whose grants step failed heals on a plain re-run. + - The migration generated by `stash eql migration --supabase` wraps the owner-scoped statements in a `pg_has_role` guard, so it applies cleanly whatever role the project's migration runner uses — a non-member role skips them instead of aborting the whole migration. + - New read-only `stash eql preflight` (`--json` for agents): reports `current_user`, superuser, membership of `postgres` (guarded for databases with no `postgres` role), `CREATE` on the database and on `public` (guarded for databases without a `public` schema), `pgcrypto` presence _and placement_ (a pgcrypto outside `extensions`/`public` aborts the bundle, even for superusers), and the EQL v3 schemas' presence and drop-ownership (a reinstall begins with `DROP SCHEMA ... CASCADE`) — each blocked row naming the statement it blocks. Exits 1 on blocking gaps; membership of `postgres` never blocks. `--json` stdout is pure JSON in every outcome: `{ status: 'ok' | 'blocked', ... }`, or the shared `{ status: 'error', code, message }` envelope — including when no DATABASE_URL is configured. The same check runs at the head of `eql install`. + - Install failure messages now state recoverability: a bundle failure says nothing was applied (rolled back); a grants failure says the install itself was kept. + - Library surface: `EQLInstaller.preflight()` (rich `PreflightResult`) supersedes `checkPermissions()`, which remains as a deprecated adapter with its `PermissionCheckResult` shape unchanged — no breaking change for existing `stash@1.x` consumers. `install()` now returns `InstallResult` with the skipped SQL, if any, and `applySupabaseGrants()` re-applies the grants alone. The exact `SUPABASE_PERMISSIONS_SQL_V3` block is unchanged byte-for-byte; new exports expose its immediate (`SUPABASE_IMMEDIATE_GRANTS_SQL_V3`), owner-scoped (`SUPABASE_DEFAULT_PRIVILEGES_SQL_V3`), guarded (`SUPABASE_GUARDED_DEFAULT_PRIVILEGES_SQL_V3`), and migration (`SUPABASE_MIGRATION_GRANTS_SQL_V3`) forms. + +- 4b2b331: New `stash eql verify`: assert the installed EQL surface is complete and coherent, independent of any application schema. A partial install — domains present, some of their comparison functions or operators absent — used to report success at install time and fail at query time on a specific predicate (e.g. `weight >= x`); nothing detected it. `eql verify` compares the database against everything the pinned bundle installs (every domain, function overload, operator, cast, and the ORE operator class) via read-only catalog queries, reports damage grouped per domain, and distinguishes expected absence from damage: the ORE operator class being skipped on managed Postgres, with its loud-failure fallback in place, reads as the supported configuration it is rather than a failed install. Exit 0 means exactly one thing — the surface was checked and found complete; damage, EQL absent, and a version mismatch with the pinned bundle (nothing verifiable) all exit 1. `--json` emits the structured report for agents. `stash eql install` now runs the same check automatically before declaring success, on the fresh-install path and the already-installed early exit alike — there, only damage fails the install: a version mismatch warns and continues, so a no-op re-run over an older EQL stays exit 0 for idempotent provisioning scripts. A valueless `--database-url` (booleanised by the parser when the next token is another flag) is now rejected up front on every command instead of silently falling back to `DATABASE_URL` — previously `eql install --database-url --force` could drop and reinstall the EQL schemas on a database the command never named. +- 7785643: Add a `lovable` handoff target to `stash plan` and `stash impl` (`--target lovable`, plus a new agent-target picker entry). It writes the same AGENTS.md as the editor-agent handoff — doctrine plus the per-integration skills inlined — but the next-steps guidance is Lovable-specific: commit and push the generated files through Lovable's GitHub sync, then add a Knowledge note in the Lovable project settings pointing the agent at `AGENTS.md` and `.cipherstash/setup-prompt.md`. Without repo-local guidance, Lovable's agent answers CipherStash questions from stale training data (the pre-EQL-v3 "needs a Postgres extension and superuser" story) and talks users out of a supported Supabase setup. +- b2dbcf9: Report the ORE-unavailable case once, at install time, instead of leaving it to surface as a failing predicate the first time a column is cast. + + The EQL bundle skips the ORE btree operator class when the installing role cannot create one and poisons every `_ord_ore` domain with a loud-failure CHECK in its place. That is a supported configuration — but nothing said so where the choice between `types.*Ord` and `types.*OrdOre` is actually made, so the trade was discovered at query time. + + - **`stash eql preflight` now probes whether the role can create an operator class** and reports it as a non-blocking `ORE operator class` row (`creatable` / `not creatable` / `unknown`; `canCreateOperatorClass` in `--json`). It is _probed_, not inferred from `superuser`: `CREATE OPERATOR CLASS` is superuser-gated in stock PostgreSQL, but AWS RDS and Aurora let their admin role create one while cloud-hosted Supabase does not, so `rolsuper` is not evidence either way. The probe attempts the DDL inside a transaction it always rolls back, leaving preflight read-only; a probe that could not ask reports `unknown` rather than guessing. + - **`stash eql install` names the consequence and the remedy** on its own line when the fallback was installed, rather than as a parenthetical on the "verified" line. + - **`stash eql status` reports the ORE state** on a v3 install, so the answer survives past the install output. + - **The remedy now names a type that exists.** The previous wording pointed at the `_ord_ope` domains; the bundle creates those, but `@cipherstash/stack` ships no `types.*OrdOpe` factory, so it named a column type no schema author could declare. Every command now says `types.*Ord` (`public.eql_v3_*_ord`), which is the same CLLW-OPE ordering and has a factory behind it. + - The ORE state machine, the catalogue probe, and this copy now live in one module shared by `eql preflight`, `eql install`, `eql status`, `eql verify`, and `eql validate`, so the five commands cannot drift into disagreeing about the same catalogue fact. + - The scaffolded encryption client's type cheat-sheet now says why ordered columns should be `*Ord` rather than `*OrdOre`. + +- d473745: Rewrite `db validate` as `eql validate`, for the EQL v3 domain-type vocabulary. + + **Fixes a false finding on the most ordinary v3 columns.** The old rule set + checked for `ore` / `unique` / `match` / `ste_vec` indexes and never learned + about `ope`. EQL v3's default ordering domains emit `ope`, so + `types.IntegerOrd('age')` and `types.TimestampOrd('created_at')` were both + reported as "Column is encrypted but has no indexes — it will not be + searchable". They are now silent. + + The command reads your tables through the new + `EncryptionClient.getSchemas()`, so it sees each column's **concrete domain** + rather than the lossy encrypt config, and gains a database pass when one is + reachable. + + Schema checks (no database needed): + + | Rule | Severity | + | ------------------------------------------------------------------------- | -------- | + | An `_ord_ore` domain is declared — its ORE operator class needs superuser | Warning | + | Storage-only column: encrypts and decrypts, carries no query terms | Info | + | Searchable `boolean` column | Error | + | Free-text `match` index on a non-text domain | Error | + | Encrypted-JSONB (`ste_vec`) index without `types.Json` | Error | + + Database checks (skipped with a notice, not a failure, when no database is + reachable): + + | Rule | Severity | + | ------------------------------------------------------------------------------------- | -------- | + | EQL v3 is not installed — reported once, remaining database checks skipped | Error | + | A declared table lives in a different schema than the one searched | Warning | + | A declared table is in the searched schema but invisible to the connected role | Warning | + | A declared table name carries a schema qualifier (`schema.table`) — not checked | Warning | + | A declared table exists in no schema at all | Error | + | A declared column is missing from a table that was found | Error | + | The database column's domain differs from the declared one | Error | + | The database column is still plain (no EQL domain) | Error | + | An `_ord_ore` domain where the EQL install could not create the ORE operator class | Error | + | A queryable column with no functional index over its term extractor | Info | + | A declared table name that resolved in the searched schema also exists in another one | Info | + + `--exclude-operator-family` is removed: it warned that an `ore` index would not + support `ORDER BY` without operator families, and the pinned EQL v3 bundle + self-adapts. `eql install` / `eql upgrade` had already rejected the flag; + `validate` was its last consumer. + + The database pass inspects `current_schema()` only, and distinguishes four + reasons a declared table can be missing from it, so that only the last fails + the command. In another schema (Prisma `multiSchema`, a tenant schema): a + Warning naming that schema. Present but invisible to the connected role: a + Warning carrying the `GRANT SELECT` to run — `information_schema` reports only + what the role holds a privilege on, so a missing grant is not a missing + migration. Declared as `schema.table`: a Warning saying it was not checked, + because validate matches table names unqualified. Absent everywhere: an Error. + Reported once per table rather than once per column. + + The relation lookup that answers those questions excludes `pg_*` and + `information_schema`. Unscoped it matched the system views named `columns`, + `domains`, `parameters`, `routines`, `sequences`, `tables` and `triggers` — all + ordinary application table names — so a project declaring one of them that had + not run its migration was told the table "exists in schema information_schema", + as a Warning, and the command exited 0 on a genuinely unapplied migration. + + An unqualified name found in more than one schema is now reported as an Info + naming the relation that was actually checked (`"public"."users"`) and the + other schemas holding that name. A bare name resolves through `search_path`, so + `users` in both `public` and Supabase's `auth` left it ambiguous which relation + every other finding described. Info, not Warning: it must not fail or + de-clean an ordinary Supabase project. + + Two of those used to exit 1 and no longer do: a privilege-invisible table and + a schema-qualified declaration were both reported as "does not exist in any + schema", which sent people to re-run a migration that had already run. + + Against a project whose `@cipherstash/stack` predates `getSchemas()`, validate + says so and falls back to the encrypt config, running the index-derived rules + and skipping the domain ones. + + `stash db validate` keeps working as a deprecated alias, like `db install` / + `db upgrade` / `db status`. Exits 1 on errors only. + +- 1d10003: Add `stash eql migration --supabase`, so an EQL v3 install survives `supabase db reset` (#613). + + Supabase projects previously had only `stash eql install --supabase`, which applies the SQL directly to a running database. `supabase db reset` — the ordinary local development loop — drops that database and replays `supabase/migrations/`, so the install was wiped and the next query failed with `type "eql_v3_encrypted" does not exist`. There was no supported way to get EQL into the migrations directory. + + `stash eql migration --supabase` now writes `supabase/migrations/_cipherstash_eql.sql`, carrying the EQL v3 bundle, the `anon` / `authenticated` / `service_role` grants, and the `cipherstash.cs_migrations` tracking schema — so one `supabase db reset` provisions everything `stash encrypt` needs. The file is timestamped at generation time, so it sorts after everything already applied and pushes without `--include-all`. A second run exits rather than adding a duplicate install; `--force` regenerates the existing one in place. + + The command now warns when the migrations directory already holds EQL-referencing migrations that sort _before_ the install it is about to write. A project that ran `stash eql install` directly and then added `public.eql_v3_*` columns against the live database gets an install stamped today — after those migrations — and `supabase db reset`, which replays in version order with no dependency awareness, then fails with `type "eql_v3_text_search" does not exist`. The warning names the specific files and the remedy (rename the install below the earliest of them, then reconcile each remote — see below). It fires on `--dry-run` too, and nothing is renamed automatically — the ordering of someone else's deployed history is not ours to change silently. + + That warning's remote guidance now requires you to verify the remote before writing to its ledger. It splits by whether the remote already has EQL: one where it does needs only the ledger row (`supabase migration repair --status applied `, which runs no SQL — pushing the file instead re-runs a bundle opening with `DROP SCHEMA IF EXISTS eql_v3 CASCADE`), and one where it does not needs the SQL genuinely applied (`supabase db push --include-all`, the back-dated version being a gap in the middle of that history). Previously the first branch was recommended on an assumption the user was never asked to check, and it is the one operation here with no self-correcting failure: mark a version applied on a remote that never ran the SQL and EQL is permanently absent _and_ permanently marked applied, so no future push installs it and the first migration referencing `eql_v3` fails with nothing pointing at the cause. The warning now prints the check first — `psql "$REMOTE_DATABASE_URL" -Atc "select eql_v3.version()"` — and says never to mark applied when it errors. It asks for `eql_v3.version()` rather than the `eql_v3` schema deliberately: that function is created by the bundle's closing statements, so it cannot resolve on an install that aborted partway, while the schema is created by its opening ones and survives one. The same correction lands in the `stash-cli` and `stash-supabase` skills and the CLI README, and a guard test now fails the build if a shipped skill recommends the ledger-only repair without that check above it. + + `--force`'s follow-up guidance was wrong and is now correct. It said to re-apply with `supabase db reset` (local) **or `supabase db push` (remote)**, but a push never re-applies a rewritten migration: the Supabase CLI decides what is pending by comparing versions, never file content, so an in-place rewrite keeping its version is skipped and push reports `Remote database is up to date.` The remote recipe is now `supabase migration repair --status reverted ` (tracking table only — it applies no SQL) followed by `supabase db push`, with `--include-all` called out as a conditional: it is needed only when migrations sort _after_ the install, which leaves the reverted version as a gap in the middle of remote history. Reverting the newest version leaves it at the tail, where a plain push applies it — and the flag applies every out-of-order migration you have, so recommending it unconditionally was itself a hazard. The warning also names the hazard it never mentioned: the EQL bundle opens with `DROP SCHEMA IF EXISTS eql_v3 CASCADE`, so re-applying drops every index, constraint, and RLS policy that references `eql_v3` / `eql_v3_internal` — free on a fresh `db reset`, destructive on a populated remote. + + `--out` on a bare `--supabase` now warns. The Supabase CLI's migrations directory is not configurable — `supabase db reset` and `supabase db push` read `/supabase/migrations` and nothing else, `config.toml` has no key for it, and `--workdir` relocates the whole `supabase/` directory rather than this subdirectory — so an install written elsewhere is never applied, which is the original bug relocated. The flag still writes the file (a project may apply that directory through its own tooling) but names the consequence, on `--dry-run` too. `--out` alongside `--drizzle --supabase` is unaffected: there it is drizzle-kit's output directory. + + `--supabase` keeps its existing meaning alongside `--drizzle` (append the role grants to the Drizzle migration); only a bare `--supabase` selects the new emitter. + + `stash init --supabase` now generates that migration instead of installing directly, when the project has local `supabase/` scaffolding — a hosted project without it still installs directly. Re-running init over a project that already has an install migration reports it and moves on, rather than treating the duplicate refusal as a failed setup. Its next steps no longer tell you to run `eql install --supabase` and then `supabase db reset`, which was the exact sequence that destroyed the install. + + `stash init`'s EQL summary line now distinguishes the migration it wrote from one it merely found. A re-run over an existing install migration says "EQL migration **already present**" instead of "EQL migration generated" — same apply guidance, same successful exit, but no claim about work the run did not do. + + `stash init`'s EQL prompt now names the action for the route it is actually on. On the migration-first routes it asks whether to generate a migration (naming `supabase/migrations/` or your Drizzle migrations folder) rather than whether to install into your database, which described the wrong action on both. Declining is fixed the same way: the retry hint is now `stash eql migration --supabase` / `--drizzle` on those routes instead of `stash eql install`, which on Supabase would reinstate the very bug above. + + `stash init` now routes on the integration flags themselves rather than on the provider's display name, so combining them works. `stash init --drizzle --supabase` is accepted — and is the natural invocation for a Drizzle project on Supabase — but init joined the matched flags into a single provider name (`drizzle-supabase`) for referrer tracking and then compared that name against `'drizzle'` and `'supabase'` everywhere it had a decision to make. Every comparison went false. A local Supabase stack answers on `127.0.0.1:54322`, so host detection reports plain Postgres and the flags are the only signal left: the run installed EQL directly instead of writing a migration — nothing in `supabase/migrations/`, no `anon` / `authenticated` / `service_role` grants — which is the #613 failure this release exists to fix, reached through a flag combination the CLI accepts. The same fall-through dropped the `supabase status` hint when resolving `DATABASE_URL` (the one lookup that finds a local stack's URL), lost the Prisma Next branch for `--prisma --supabase` — scaffolding a client Prisma Next never uses and running a duplicate EQL install that races `prisma-next migrate`'s journal — and installed no integration adapter at all, where a combined run needs both `@cipherstash/stack-drizzle` and `@cipherstash/stack-supabase`. The provider now carries the matched flags alongside its name and every step reads those; the combined name is still exactly what gets recorded as the referrer, it is simply no longer what the CLI branches on. Drizzle still wins the migration route when both flags fire — it owns the migration history, and `--supabase` is the grants modifier there. Single-flag runs behave exactly as before. + + Also corrects the remote apply command across the Supabase guidance: a bare `supabase migration up` targets the local database, so the instructions now say `supabase db push`. + + Also corrects the `eql install --migration` removal message, which pointed every Supabase user at `--drizzle`. + + The Supabase CLI behaviour all of the above depends on is now pinned by a live test rather than by reading the CLI's source. `supabase-push.live.test.ts` drives the real binary against a real Postgres — `db push --db-url` needs neither Docker nor a linked project — and covers: the generated install applying with no `--include-all`; `anon` reaching `eql_v3` via `SET ROLE` through the grants carried in the emitted file (not just the ones `eql install --direct` applies); an out-of-order version aborting the whole push rather than being skipped; a `--force`-replaced file never re-applying; `--include-all` being needed only for the gap case; and a leaked `.tmp` file being ignored. Gated on `STASH_TEST_SUPABASE_DB_URL` + `STASH_TEST_SUPABASE_CLI`, so the default suite is unchanged. + +### Patch Changes + +- a1b53af: Correct the Dependabot section of the bundled `stash-supply-chain-security` + skill. It described two monitored ecosystems (`npm`, `github-actions`); there + are now three, because the in-tree Rust workspace at `packages/protect-ffi` + brought a `Cargo.lock` that nothing proposed updates for. The skill now names + the `cargo` entry, its non-root `directory`, its monthly cadence, and the + exact-pinned CipherStash crates it ignores. + + Two things the section previously got wrong are also fixed. Major bumps do not + "stay un-grouped — one PR each": every entry ignores + `version-update:semver-major`, so Dependabot proposes no major bumps at all and + they are applied by hand. And `ignore` conditions suppress Dependabot _security_ + PRs as well as version updates — the skill now says so, and points at + `osv-scanner.yml` (which scans every lockfile in the tree, `Cargo.lock` + included) as the compensating control. + +- 1a3e980: `stash eql status` no longer reports ORE damage on a healthy database running a + different EQL version. The `_ord_ore` domains its poison CHECKs are counted over + come from the bundle this CLI pins, so a fallback install of an older EQL poisons + domains the pinned list only partly sees — which classified as an incoherent + half-install and told the operator to reinstall with `--force`, on the ordinary + "CLI upgraded, database not yet" case. The ORE probe now gates on the installed + version the same way `eql verify` does and reports that the state could not be + compared, pointing at `eql upgrade`. + + Two hardening fixes to `stash eql verify` alongside it. Its cast check now + matches an EQL endpoint on either side, so a future bundle cast to or from a + `pg_catalog` type (`jsonb`, `text`) cannot enter the expected surface while being + unreadable as installed — which would have reported "Cast missing" on every + healthy database. And the parser that derives the expected surface from the + pinned bundle now fails loudly on any statement it does not model, instead of + silently omitting the objects it creates: a bundle that outgrows the parser can + no longer make `verify` report a partial install as complete. + +- 66ba41a: Document in the bundled `stash-auth` skill that `CS_CLIENT_KEY` must be + hex-encoded. Hex is what `stash env` emits and what the skill's variable table + already stated, but the decoder underneath used to fall back to standard padded + base64 — the encoding the Rust `stash-profile` crate uses for + `~/.cipherstash/secretkey.json` on disk — so a key copied out of that file + happened to work despite never being a supported input. That fallback is gone + and such a key is now rejected at client construction, with a message that + deliberately withholds detail — so the skill names the symptom and the fix. + + The recovery advice is split by entry point: falling back to the profile store + works on the native entry, but not on `@cipherstash/stack/wasm-inline`, where + `clientId` and `clientKey` are required config and the target runtimes have no + profile store to read. Re-encoding as hex is the fix that works on both. + +- 05f84bc: Correct the release-workflow section of the bundled `stash-supply-chain-security` + skill. It described the no-Actions-cache rule as a property of one file — "no + `cache:`, `package-manager-cache: false`, `pnpm/action-setup` with + `cache: false`" — which is no longer the whole rule. + + The gate now follows any local composite action or reusable workflow the job + reaches, so the constraint is on the whole call tree rather than the workflow + file. And every published `uses:` must appear in the script's `AUDITED_ACTIONS` + allowlist: the check cannot open a published action to prove it does not cache, + and caching actions are not reliably named — a `setup-` action that caches + by default has no `cache:` input and nothing in its name to match. The list is + therefore what is permitted, not what is forbidden, and adding a step to + `release.yml` or `tests-supply-chain.yml` means auditing the action and adding + it there in the same PR. + +- 41c4531: Correct two inaccuracies in the bundled `stash-cli` skill. The `stash init` overview said the **Supabase** flow always generates an EQL migration; it now says **local Supabase**, matching `resolveMigrationRoute` — only a project with local `supabase/` CLI scaffolding takes the migration-first route, while a hosted Supabase project with no `supabase/` directory falls through to a direct `stash eql install`. And the guidance for back-dating the Supabase install migration no longer recommends `supabase db push --include-all` unconditionally: on a remote where `stash eql install` has already run, pushing the file re-runs a bundle that opens with `DROP SCHEMA IF EXISTS eql_v3 CASCADE`, dropping every index, constraint, and RLS policy on those schemas. That case is now `supabase migration repair --status applied ` (ledger only, no SQL) — after confirming EQL is genuinely installed on that remote with `psql "$REMOTE_DATABASE_URL" -Atc "select eql_v3.version()"`; `--include-all` stays for a remote that still needs the SQL applied. + + The same correction lands in the CLI itself, and in the two other places that repeated the old advice — the `stash-supabase` skill and the CLI README. `stash eql migration --supabase` warns when the project already has EQL-referencing migrations that sort before the install it is about to write, and that warning carried the identical blanket `--include-all` advice. Since this warning only fires on projects that ran `stash eql install` directly — so the remote usually already has the bundle and is missing only the ledger row — it now names `supabase migration repair --status applied ` as the remedy, spells out the `DROP SCHEMA IF EXISTS eql_v3 CASCADE` hazard of pushing the file instead, and keeps `--include-all` for the remote that has not had the SQL applied. + +- c795225: Upgrade the Prisma Next integration to Prisma Next 0.17 (the `prisma/prisma` "Prisma 8" main line). Consuming apps must move to the 0.17 publish surface to use this release. + + Breaking changes for consumers: + + - **Dependencies**: the `@prisma-next/*` scope is retired. An application now depends on exactly one database facade — `@prisma/orm-postgres@0.17.0` — plus this extension. `@cipherstash/stack-prisma` itself builds against `@prisma/orm-framework`, `@prisma/orm-family-sql`, and `@prisma/orm-toolchain`, and declares `@prisma/orm-target-postgres` as a peer dependency. + - **Generated imports**: the emitted `contract.d.ts` now imports this extension's types from `@cipherstash/stack-prisma/{codec-types,operation-types,runtime}` (previously the stale `@prisma-next/extension-cipherstash/*` names, which no longer resolve). Re-run `prisma-next contract emit` after upgrading. + - **Contract and migration hashes**: 0.17 renames the contract's `extensionPacks` key to `extensions` and drops the `sha256:` prefix from every content hash, so every contract `storageHash` and `migrationHash` changes. The shipped migration set is re-anchored accordingly; consumer repos convert their checked-in `migrations/` trees with the upstream `strip-sha256-hash-prefixes` codemod and `scripts/migrate-migrations-layout.mjs` (the content-addressed `migrations/snapshots/` store replaces per-migration `end-contract.*` files). Vendored `migrations/cipherstash/` copies must be refreshed (delete and re-run `prisma-next migration plan`, or copy the shipped artefacts). + - **Codec descriptors**: the v3 codec descriptors are now Postgres target descriptors (`nativeTypeFor` / `projectJson` via `postgresCodec`), replacing the deleted `meta.db.sql.postgres` channel, and the pack meta publishes them through `types.codecTypes.codecDescriptors` (0.17 removed `codecInstances`). + - **Config**: in `prisma-next.config.ts` use the facade's `defineConfig` from `@prisma/orm-postgres/config` with `extensions: [cipherstash]` (`extensionPacks` fails loudly on 0.17). + + The `stash` CLI now also detects Prisma Next projects that depend on the 0.17 packages (`prisma-next` or any `@prisma/orm-*` package), and the bundled `stash-prisma` skill documents the 0.17 surface. + +- 1486a46: Correct the bundled `stash-prisma` and `stash-indexing` skills for Prisma Next 0.17's functional-index support: `@@index` now takes an `expression` argument, so the `eql_v3.*` functional indexes are declared directly in `schema.prisma` (expression indexes require a `name` or `map`) instead of hand-written raw-SQL migration operations. Also documents the physical-name rule (`name:` gains a content-hash suffix, `map:` pins the exact name), the TS contract form (`type` requires `options`), and that `CREATE INDEX CONCURRENTLY` cannot run through the migration runner's transaction — via `rawSql` or otherwise. +- d348f84: Document the Dependabot major-version policy in `skills/stash-supply-chain-security`: no entry configures a `semver-major-days` cooldown, because every entry ignores `version-update:semver-major` and cooldown applies to version updates only. The supply-chain e2e suite now pins both halves of that relationship. +- 3c7ed59: New `stash-managed-platforms` skill: implementing CipherStash on a managed AI app platform (Lovable, v0, Bolt, Replit). + + These platforms share a shape — no shell the developer controls, an edge/Workers runtime, a database role that is not `postgres`, and schema changes only through the platform's own migration tool — and every one of those changes the setup. The skill covers the WASM entry, running `stash auth login --json` headlessly in an ephemeral sandbox, minting `CS_*` with `stash env`, installing EQL as a non-`postgres` role (including generating a migration instead of installing directly), which predicates survive PostgREST, and why `encryptedSupabase` cannot be constructed inside a Worker. + + The costly one is first, because it decides whether anyone gets any further: **use `@cipherstash/stack` with the `@cipherstash/stack/wasm-inline` entry.** `@cipherstash/protect` is the deprecated predecessor, and reasoning from its `@cipherstash/protect-ffi` dependency to "there is no way to run this on an edge runtime" is a wrong conclusion drawn from the wrong package. That dead end cost an agent a full turn on a real project before it found `stash`. The same correction is now stated in `stash-edge`'s entry table, where an agent comparing runtimes will hit it. + + Two things were also lifted above the fold in `stash-supabase`: a pointer to the new skill, and a one-line summary of what does and does not survive PostgREST (`eq`/`neq`/`in`/`match()` and the range filters do; encrypted `matches()` and JSON containment do not). The full treatment was correct but ~500 lines down, which is not where a time-pressured agent finds it. + + Registered for the `supabase` and `postgresql` integrations in both the CLI and wizard skill maps, so it installs into `.claude/skills` / `.codex/skills` and inlines into `AGENTS.md` on those paths. + +- 1a3e980: Skills: `encryptedSupabase` can now be constructed in a Worker, so the guidance that said it could not has been corrected. + + `skills/stash-managed-platforms` replaces its "cannot be constructed in a Worker" section with the two things that must both be right — import `@cipherstash/stack-supabase/wasm-inline` rather than the package root, and declare your `schemas` so nothing introspects — plus what declared mode gives up (`select('*')`, `from()` on an undeclared table, and the drift check) and how to keep the drift check on Node by passing `databaseUrl` as well. + + `skills/stash-supabase` and `skills/stash-edge` gain the same correction where each would be read: the above-the-fold managed-platform callout, and the runtime-entry table respectively. + +- 68b4d68: Add a type → predicate → domain → index capability matrix to the `stash-encryption` skill, cross-linked from `stash-indexing` and `stash-postgres`. + + Picking the wrong `types.*` factory is silent at authoring time — there is no type error and no runtime warning, just a predicate that never runs. The skills documented the capability _suffixes_ and the families they apply to, but never the 40 concrete factories in one lookup, so answering "can `types.Double` do a range query" meant composing two tables and knowing the exceptions. It cannot: `types.Double` is storage-only. + + The new matrix has one row per factory with its Postgres column domain, the predicates it supports, the extractor to index it through, and whether it works on managed Postgres. Alongside it: a note on which schema holds what (`public` for column domains, `eql_v3` for query domains and operator functions, `eql_v3_internal` for index-term types) and why the Supabase grants have to cover the last two. + + Two corrections came out of writing it: + + - The `Ord` vs `OrdOre` callout said the install "disables the `_ord_ore` domains" on managed Postgres. Precisely: the bundle adds an always-raising `eql_ore_unavailable` CHECK to them, so a _write_ fails — the domain is unusable, not merely unindexed. The callout now says that, notes that RDS and Aurora do support ORE while cloud-hosted Supabase does not, and points at `stash eql preflight` / `eql status` rather than asking the reader to guess. + - The `stash-postgres` naming table omitted `types.TextOrdOre` entirely (its `` shorthand covers only the numeric and temporal families). Added. + + A new test derives the matrix from the `types` namespace and fails if the skill disagrees — every factory present exactly once, mapped to the domain it actually builds, naming the extractors it actually emits and none it does not, with every ORE row marked unusable where the operator class is absent. + +- 483a925: `stash doctor` now detects a missing native binary. Both of its checks had stopped doing so, in different ways, and each reported a green row instead. + + **The encryption engine check never loaded anything.** Since the protect-ffi native load became lazy, importing the package resolves no platform binary — `@neon-rs/load`'s proxy resolves on first use — so the probe passed with nothing installed and the failure surfaced later, at the first encrypt. It now calls `assertNativeBindingAvailable()` through the new `@cipherstash/stack/diagnostics` subpath, which forces the load. + + **It was also reporting the wrong package.** Importing `@cipherstash/stack` reaches `@cipherstash/auth`, whose binding is eager, so the encryption row was really a second auth check: one signal rendered as two rows. The diagnostics subpath does not reach auth, so each row now means what it says. + + **A missing `@cipherstash/auth` binary produced a bare `Fatal error`.** That package's napi loader swallows the resolver's `MODULE_NOT_FOUND` and throws a summary carrying no error `code`, which the CLI's native-binary classifier did not recognise — so every command, not only `doctor`, skipped the recovery guidance and printed a raw message. The classifier now recognises that shape, and prints the missing package with the reinstall steps. + + `stash doctor` exits non-zero when either platform package is missing, and reports an install of `@cipherstash/stack` that predates the diagnostics subpath as unprobeable rather than failing on it. A run that could not complete a check now ends with "stash doctor could not run every check." instead of claiming they all passed — still exit 0, since an unrunnable check is not a diagnosis. + + **A package that is installed but broken is no longer reported as "not installed".** The check for an absent package matched the package name anywhere in the failure message, and the probe's own import path contains it — so a partially installed or partially built `@cipherstash/stack` was reported as one you simply had not installed yet, in green, with nothing to suggest looking further. It now matches on the specifier Node failed to resolve. + +- cf08c3b: Document the native-binding publish path in the bundled + `stash-supply-chain-security` skill, and correct what it claims about + frozen-lockfile coverage. + + `@cipherstash/protect-ffi` and its six platform packages ship compiled binaries, + which `changeset publish` cannot produce — it packs from the workspace, where + `index.node` is a build output. The skill now describes the pipeline that does: + a registry-state gate, a target-explicit build matrix in a reusable workflow, + and a publish step that ships the six platform packages before the wrapper and + tags all seven itself, because changesets tags only what it published. It also + records two npm requirements that fail late and quietly — `repository.url` must + match the publishing repository exactly (and `repository.directory` resolves + from that repository's root), and trusted-publisher configurations created after + 2026-05-20 need an explicit "Allowed actions" selection. + + It also now states, per action, which input disables that action's built-in + caching and what that input defaults to. Two of the three default to caching + ON — `actions/setup-node`'s `package-manager-cache` and `jdx/mise-action`'s + `cache` — so omitting the key is not "no caching", it is caching spelled + invisibly, and the gate's generic rule only sees a _truthy_ value rather than a + missing one. + + The OIDC section said `permissions: id-token: write` is what mints the token and + left it there. It now says where that grant belongs: on the publishing jobs, not + at the workflow level. A trusted publisher is registered against a repository + _and a workflow filename_, so npm accepts a token minted by any job in the + registered file — declaring the scope at the top hands the publish credential to + every job that does not override it, including ones added later. + + The frozen-lockfile section said the rule was enforced in `tests.yml`, which was + true and misleading: that is where it was _checked_, and `release.yml` ran a + bare `pnpm install` from the day it was written — so the single install permitted + to resolve outside the lockfile was the one whose output goes to the registry. + The install is fixed and the check now scans every workflow and every local + composite action. + + - @cipherstash/migrate@1.0.0 + ## 1.0.0 ### Major Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index 89a4d9fc2..a9edeaca8 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "stash", - "version": "1.0.0", + "version": "2.0.0", "description": "CipherStash CLI — the one stash command for auth, init, encryption schema, database setup, and secrets.", "repository": { "type": "git", diff --git a/packages/protect-ffi/CHANGELOG.md b/packages/protect-ffi/CHANGELOG.md index ebfe25a1a..e9c71fa29 100644 --- a/packages/protect-ffi/CHANGELOG.md +++ b/packages/protect-ffi/CHANGELOG.md @@ -1,5 +1,39 @@ # Changelog +## 0.32.0 + +### Minor Changes + +- e77bfce: The native binding now loads lazily. Importing the package no longer resolves the platform binary: the CJS entry pulls in `./load.cjs` with `import native = require(...)`, which emits a plain `require` and leaves the `@neon-rs/load` proxy untouched, where the previous `import * as native from` compiled to an `__importStar` that enumerated the module and forced resolution at module-evaluation time. `require('@cipherstash/protect-ffi')` therefore succeeds with no binding installed, and the same `MODULE_NOT_FOUND` — identical `code` and `message` — is raised on first use instead of at import. + + Adds `assertNativeBindingAvailable()`: a diagnostic entry point that resolves the platform binary and throws the loader's error unwrapped if it is missing, without constructing a client, reading credentials or touching the network. It exists because laziness removed the implicit probe that importing the package used to be, and there is no consumer-side replacement — `lib/load.cjs` is not an exported path (`ERR_PACKAGE_PATH_NOT_EXPORTED`), reading one of this module's own exports never reaches the proxy, and forcing it through a real wrapper means picking one whose argument validation does not reject first. + +### Patch Changes + +- e77bfce: Point the published metadata at `cipherstash/stack`, the repository these + packages are now built and published from. The wrapper's `repository.url`, + `bugs.url` and `homepage`, and each platform package's `repository.url`, all + named `cipherstash/protectjs-ffi`; each platform package's + `repository.directory` also named `platforms/`, which resolves from + the root of the repository named above and so addressed nothing here. + + npm requires `repository.url` to match the publishing repository exactly for a + trusted publish, and rejects a mismatch rather than warning about it. A stale + `repository.directory` fails more quietly: the publish succeeds and the source + link on the package page 404s. + + The one repository URL that reaches an end user at runtime moves too — the Rust + core's `InvariantViolation` error asks the reader to file an issue, and the + repository it pointed at is archived at the end of the publishing cutover. + +- ba3830f: Ship `CHANGELOG.md` inside the published tarball. It was missing from `files`, + so the release notes for these packages were readable on GitHub and on the npm + web page but not in the package you actually install — which is the copy you + have when something breaks offline, or when the repository has moved. + + `@cipherstash/stack-drizzle` and `@cipherstash/stack-supabase` gain it in the + same release, as do the six `@cipherstash/protect-ffi-` packages. + ## About 0.31.0 and earlier Every entry from `[0.31.0]` down was written by hand, under an `[Unreleased]` @@ -39,13 +73,13 @@ ships inside the npm tarball. Guidance for contributors belongs in AGENTS.md. ```js // before - await newClient({ encryptConfig, strategy, clientId, clientKey, keyset }) + await newClient({ encryptConfig, strategy, clientId, clientKey, keyset }); // after await newClient({ encryptConfig, authStrategy, clientOpts: { clientId, clientKey, keyset }, - }) + }); ``` **Move `keyset` with the rest.** Unknown option keys are still dropped @@ -66,7 +100,7 @@ ships inside the npm tarball. Guidance for contributors belongs in AGENTS.md. encoding `~/.cipherstash/secretkey.json` uses on disk — so a base64 value passed as `clientKey`, or set in `CS_CLIENT_KEY` (which the Neon entry forwards as `clientKey`), used to work. It is now rejected with `invalid - clientKey: expected a hex-encoded key`. Re-encode as hex, or read the key +clientKey: expected a hex-encoded key`. Re-encode as hex, or read the key from the profile store instead of pasting it. The decode error deliberately says nothing more: `hex`'s own message names @@ -88,7 +122,7 @@ ships inside the npm tarball. Guidance for contributors belongs in AGENTS.md. - **A key an options object doesn't declare is now an error, not a silent drop.** Every options struct rejects unrecognised fields, naming the - offender — ``unknown field `clientId` `` — instead of discarding them on the + offender — `` unknown field `clientId` `` — instead of discarding them on the way in. A misspelling, a stale key, or a value in the wrong place fails loudly. ([#144]) @@ -121,41 +155,49 @@ ships inside the npm tarball. Guidance for contributors belongs in AGENTS.md. its inherited fields, and a field defined through `Object.defineProperty({enumerable: false})` is dropped. Neon has always been `JSON.stringify`, which is own-enumerable too. - - **A misspelled *required* field now reports it as missing, not unknown.** - `encrypt(client, {plaintext, column, tabel: 'users'})` says ``missing - field `table` `` and never names `tabel`; it used to say both. Serde's + - **A misspelled _required_ field now reports it as missing, not unknown.** + `encrypt(client, {plaintext, column, tabel: 'users'})` says `` missing +field `table` `` and never names `tabel`; it used to say both. Serde's flatten path buffers the map and reports at its closing brace, which also drops the `expected one of ...` list from every rejection. Neon-only — the wasm path had no error to lose. Three differences between the boundaries remain, all of them about how - strictly a *mistake* is reported. Correct input behaves identically on both. + strictly a _mistake_ is reported. Correct input behaves identically on both. - A key whose value is `undefined` (`{...opts, typo: undefined}`) is rejected on wasm and accepted on Neon, where `JSON.stringify` drops it before serde runs. - A key whose value is a **function or a symbol** is reported on wasm — as a - *type* error, naming the wrong problem — and dropped in silence on Neon, + _type_ error, naming the wrong problem — and dropped in silence on Neon, where `JSON.stringify` omits it. - A key holding a **circular value or a `bigint`** throws in `JSON.stringify` on Neon before serde can see it. `newClient` names the key itself rather than let a bare `TypeError: Converting circular - structure to JSON` out; the other entries do not. +structure to JSON` out; the other entries do not. - **`ProtectError` and `normalizeError` are gone. Both entries throw an ordinary `Error` with a `code` property.** ([#146]) ```ts // before — Node entry only, and only for errors the message table matched - if (err instanceof ProtectError && err.code === 'INVALID_JSON_PATH') { } + if (err instanceof ProtectError && err.code === "INVALID_JSON_PATH") { + } // after — both entries, nothing to import - if (err instanceof Error && 'code' in err && err.code === 'INVALID_JSON_PATH') { } + if ( + err instanceof Error && + "code" in err && + err.code === "INVALID_JSON_PATH" + ) { + } // after, when you want the code as a typed value - import { isProtectErrorCode } from '@cipherstash/protect-ffi' - const { code } = err as { code?: unknown } - if (isProtectErrorCode(code)) { /* code narrows to ProtectErrorCode */ } + import { isProtectErrorCode } from "@cipherstash/protect-ffi"; + const { code } = err as { code?: unknown }; + if (isProtectErrorCode(code)) { + /* code narrows to ProtectErrorCode */ + } ``` Every export used to run through a try/catch that re-threw the failure as a @@ -184,7 +226,7 @@ ships inside the npm tarball. Guidance for contributors belongs in AGENTS.md. - **The wasm build declares the real option types**, emitted by wasm-bindgen from `typescript_type` / `typescript_custom_section` attributes on the Rust. Previously it typed every export as `(client: WasmClient, opts: any): - Promise`, so the +Promise`, so the `./wasm` and `./wasm-inline` entries checked nothing and exported no option or payload types at all — while the Neon entry declared fourteen. Both entries now name the same types. @@ -220,19 +262,19 @@ ships inside the npm tarball. Guidance for contributors belongs in AGENTS.md. ([#146]) Branching on a code needs neither — `err instanceof Error && 'code' in err && - err.code === 'MISSING_INDEX'` compiles under `strict` on its own. - `isProtectErrorCode` earns its place when you want the code as a *typed* +err.code === 'MISSING_INDEX'` compiles under `strict` on its own. + `isProtectErrorCode` earns its place when you want the code as a _typed_ value, since it narrows `unknown` to `ProtectErrorCode`: ```ts import { isProtectErrorCode, type ProtectErrorCode, - } from '@cipherstash/protect-ffi' + } from "@cipherstash/protect-ffi"; function errorCode(err: unknown): ProtectErrorCode | undefined { - const { code } = err as { code?: unknown } - return isProtectErrorCode(code) ? code : undefined + const { code } = err as { code?: unknown }; + return isProtectErrorCode(code) ? code : undefined; } ``` @@ -250,7 +292,7 @@ ships inside the npm tarball. Guidance for contributors belongs in AGENTS.md. ### Changed - **`encryptConfig` normalisation moved into Rust.** `cast_as: 'string' | - 'number' | 'bigint'` → `'text' | 'float' | 'big_int'`, and the `ste_vec` +'number' | 'bigint'` → `'text' | 'float' | 'big_int'`, and the `ste_vec` `array_index_mode` default of `'none'`, now happen at the deserialization boundary rather than in the Neon entry's JS wrapper. @@ -270,7 +312,7 @@ ships inside the npm tarball. Guidance for contributors belongs in AGENTS.md. JavaScript and has always worked on the Neon entry, whose extractor is `JSON.stringify`-based; on wasm those keys survive as `null` and every non-optional field rejected them (`invalid type: null, expected string or - map`). One config now works on both. +map`). One config now works on both. - **`newClient`'s `strategy` option is now `authStrategy`**, matching `@cipherstash/stack`'s `config.authStrategy` so one concept has one name @@ -291,8 +333,8 @@ ships inside the npm tarball. Guidance for contributors belongs in AGENTS.md. not this repo — ```ts - if (message.includes('requires plaintext_type: json')) - return 'STE_VEC_REQUIRES_JSON_CAST_AS' + if (message.includes("requires plaintext_type: json")) + return "STE_VEC_REQUIRES_JSON_CAST_AS"; ``` — so an upstream reword would have silently downgraded a caller's error to @@ -509,7 +551,7 @@ ships inside the npm tarball. Guidance for contributors belongs in AGENTS.md. - Support for `@cipherstash/auth` `0.41`'s `@byteslice/result` `Result`-shaped `getToken()` — `{ data: { token, … } }` on success, `{ failure: { type, - error, … } }` on error — on both the Node (Neon) and WASM auth paths. The +error, … } }` on error — on both the Node (Neon) and WASM auth paths. The bare `{ token }` shape (the documented `getToken(): Promise<{ token }>` contract, used by `@cipherstash/auth` `<= 0.40` and custom strategies) is still accepted, so this is backward compatible. A `failure` result is diff --git a/packages/protect-ffi/package.json b/packages/protect-ffi/package.json index 6b97a427c..36ebd1a7c 100644 --- a/packages/protect-ffi/package.json +++ b/packages/protect-ffi/package.json @@ -1,6 +1,6 @@ { "name": "@cipherstash/protect-ffi", - "version": "0.31.0", + "version": "0.32.0", "repository": { "type": "git", "url": "git+https://github.com/cipherstash/stack.git" diff --git a/packages/protect-ffi/platforms/darwin-arm64/CHANGELOG.md b/packages/protect-ffi/platforms/darwin-arm64/CHANGELOG.md new file mode 100644 index 000000000..735a567f4 --- /dev/null +++ b/packages/protect-ffi/platforms/darwin-arm64/CHANGELOG.md @@ -0,0 +1,31 @@ +# @cipherstash/protect-ffi-darwin-arm64 + +## 0.32.0 + +### Patch Changes + +- 8dc2ebe: **This is the first release of these packages published from + `cipherstash/stack`.** Every version up to and including 0.31.0 was published + from `cipherstash/protectjs-ffi`, which is archived once this release is out. + + If you verify npm provenance, the attested source repository changes with this + release: + + ``` + 0.31.0 github.com/cipherstash/protectjs-ffi .github/workflows/release.yml + 0.32.0 github.com/cipherstash/stack .github/workflows/release.yml + ``` + + A verification policy that pins the source repository will reject 0.32.0 until + it is updated. The packages, their contents and their maintainers are otherwise + unchanged: the Rust source moved into the monorepo at + `packages/protect-ffi/crates/protect-ffi`, and each of these packages' + `repository.url` now names `cipherstash/stack`, with `repository.directory` + pointing at its own stub under `packages/protect-ffi/platforms/`. + + `CHANGELOG.md` is also added to each package's published files, so this note and + later ones are readable in the package you install rather than only on GitHub. + + The binaries themselves differ from 0.31.0 in one user-visible way: the Rust + core's `InvariantViolation` message asks the reader to file an issue, and the + repository it names has moved with the rest. diff --git a/packages/protect-ffi/platforms/darwin-arm64/package.json b/packages/protect-ffi/platforms/darwin-arm64/package.json index d911e246d..155b6f6bd 100644 --- a/packages/protect-ffi/platforms/darwin-arm64/package.json +++ b/packages/protect-ffi/platforms/darwin-arm64/package.json @@ -1,7 +1,7 @@ { "name": "@cipherstash/protect-ffi-darwin-arm64", "description": "Prebuilt binary package for `@cipherstash/protect-ffi` on `darwin-arm64`.", - "version": "0.31.0", + "version": "0.32.0", "repository": { "type": "git", "url": "git+https://github.com/cipherstash/stack.git", diff --git a/packages/protect-ffi/platforms/darwin-x64/CHANGELOG.md b/packages/protect-ffi/platforms/darwin-x64/CHANGELOG.md new file mode 100644 index 000000000..7d02bc2d0 --- /dev/null +++ b/packages/protect-ffi/platforms/darwin-x64/CHANGELOG.md @@ -0,0 +1,31 @@ +# @cipherstash/protect-ffi-darwin-x64 + +## 0.32.0 + +### Patch Changes + +- 8dc2ebe: **This is the first release of these packages published from + `cipherstash/stack`.** Every version up to and including 0.31.0 was published + from `cipherstash/protectjs-ffi`, which is archived once this release is out. + + If you verify npm provenance, the attested source repository changes with this + release: + + ``` + 0.31.0 github.com/cipherstash/protectjs-ffi .github/workflows/release.yml + 0.32.0 github.com/cipherstash/stack .github/workflows/release.yml + ``` + + A verification policy that pins the source repository will reject 0.32.0 until + it is updated. The packages, their contents and their maintainers are otherwise + unchanged: the Rust source moved into the monorepo at + `packages/protect-ffi/crates/protect-ffi`, and each of these packages' + `repository.url` now names `cipherstash/stack`, with `repository.directory` + pointing at its own stub under `packages/protect-ffi/platforms/`. + + `CHANGELOG.md` is also added to each package's published files, so this note and + later ones are readable in the package you install rather than only on GitHub. + + The binaries themselves differ from 0.31.0 in one user-visible way: the Rust + core's `InvariantViolation` message asks the reader to file an issue, and the + repository it names has moved with the rest. diff --git a/packages/protect-ffi/platforms/darwin-x64/package.json b/packages/protect-ffi/platforms/darwin-x64/package.json index 29be22966..b0e369e85 100644 --- a/packages/protect-ffi/platforms/darwin-x64/package.json +++ b/packages/protect-ffi/platforms/darwin-x64/package.json @@ -1,7 +1,7 @@ { "name": "@cipherstash/protect-ffi-darwin-x64", "description": "Prebuilt binary package for `@cipherstash/protect-ffi` on `darwin-x64`.", - "version": "0.31.0", + "version": "0.32.0", "repository": { "type": "git", "url": "git+https://github.com/cipherstash/stack.git", diff --git a/packages/protect-ffi/platforms/linux-arm64-gnu/CHANGELOG.md b/packages/protect-ffi/platforms/linux-arm64-gnu/CHANGELOG.md new file mode 100644 index 000000000..385bf614b --- /dev/null +++ b/packages/protect-ffi/platforms/linux-arm64-gnu/CHANGELOG.md @@ -0,0 +1,31 @@ +# @cipherstash/protect-ffi-linux-arm64-gnu + +## 0.32.0 + +### Patch Changes + +- 8dc2ebe: **This is the first release of these packages published from + `cipherstash/stack`.** Every version up to and including 0.31.0 was published + from `cipherstash/protectjs-ffi`, which is archived once this release is out. + + If you verify npm provenance, the attested source repository changes with this + release: + + ``` + 0.31.0 github.com/cipherstash/protectjs-ffi .github/workflows/release.yml + 0.32.0 github.com/cipherstash/stack .github/workflows/release.yml + ``` + + A verification policy that pins the source repository will reject 0.32.0 until + it is updated. The packages, their contents and their maintainers are otherwise + unchanged: the Rust source moved into the monorepo at + `packages/protect-ffi/crates/protect-ffi`, and each of these packages' + `repository.url` now names `cipherstash/stack`, with `repository.directory` + pointing at its own stub under `packages/protect-ffi/platforms/`. + + `CHANGELOG.md` is also added to each package's published files, so this note and + later ones are readable in the package you install rather than only on GitHub. + + The binaries themselves differ from 0.31.0 in one user-visible way: the Rust + core's `InvariantViolation` message asks the reader to file an issue, and the + repository it names has moved with the rest. diff --git a/packages/protect-ffi/platforms/linux-arm64-gnu/package.json b/packages/protect-ffi/platforms/linux-arm64-gnu/package.json index c5f7771f5..dbbb71e8d 100644 --- a/packages/protect-ffi/platforms/linux-arm64-gnu/package.json +++ b/packages/protect-ffi/platforms/linux-arm64-gnu/package.json @@ -1,7 +1,7 @@ { "name": "@cipherstash/protect-ffi-linux-arm64-gnu", "description": "Prebuilt binary package for `@cipherstash/protect-ffi` on `linux-arm64-gnu`.", - "version": "0.31.0", + "version": "0.32.0", "repository": { "type": "git", "url": "git+https://github.com/cipherstash/stack.git", diff --git a/packages/protect-ffi/platforms/linux-x64-gnu/CHANGELOG.md b/packages/protect-ffi/platforms/linux-x64-gnu/CHANGELOG.md new file mode 100644 index 000000000..d28e59981 --- /dev/null +++ b/packages/protect-ffi/platforms/linux-x64-gnu/CHANGELOG.md @@ -0,0 +1,31 @@ +# @cipherstash/protect-ffi-linux-x64-gnu + +## 0.32.0 + +### Patch Changes + +- 8dc2ebe: **This is the first release of these packages published from + `cipherstash/stack`.** Every version up to and including 0.31.0 was published + from `cipherstash/protectjs-ffi`, which is archived once this release is out. + + If you verify npm provenance, the attested source repository changes with this + release: + + ``` + 0.31.0 github.com/cipherstash/protectjs-ffi .github/workflows/release.yml + 0.32.0 github.com/cipherstash/stack .github/workflows/release.yml + ``` + + A verification policy that pins the source repository will reject 0.32.0 until + it is updated. The packages, their contents and their maintainers are otherwise + unchanged: the Rust source moved into the monorepo at + `packages/protect-ffi/crates/protect-ffi`, and each of these packages' + `repository.url` now names `cipherstash/stack`, with `repository.directory` + pointing at its own stub under `packages/protect-ffi/platforms/`. + + `CHANGELOG.md` is also added to each package's published files, so this note and + later ones are readable in the package you install rather than only on GitHub. + + The binaries themselves differ from 0.31.0 in one user-visible way: the Rust + core's `InvariantViolation` message asks the reader to file an issue, and the + repository it names has moved with the rest. diff --git a/packages/protect-ffi/platforms/linux-x64-gnu/package.json b/packages/protect-ffi/platforms/linux-x64-gnu/package.json index e6635b39c..18cafb70e 100644 --- a/packages/protect-ffi/platforms/linux-x64-gnu/package.json +++ b/packages/protect-ffi/platforms/linux-x64-gnu/package.json @@ -1,7 +1,7 @@ { "name": "@cipherstash/protect-ffi-linux-x64-gnu", "description": "Prebuilt binary package for `@cipherstash/protect-ffi` on `linux-x64-gnu`.", - "version": "0.31.0", + "version": "0.32.0", "repository": { "type": "git", "url": "git+https://github.com/cipherstash/stack.git", diff --git a/packages/protect-ffi/platforms/linux-x64-musl/CHANGELOG.md b/packages/protect-ffi/platforms/linux-x64-musl/CHANGELOG.md new file mode 100644 index 000000000..c3cddf89c --- /dev/null +++ b/packages/protect-ffi/platforms/linux-x64-musl/CHANGELOG.md @@ -0,0 +1,31 @@ +# @cipherstash/protect-ffi-linux-x64-musl + +## 0.32.0 + +### Patch Changes + +- 8dc2ebe: **This is the first release of these packages published from + `cipherstash/stack`.** Every version up to and including 0.31.0 was published + from `cipherstash/protectjs-ffi`, which is archived once this release is out. + + If you verify npm provenance, the attested source repository changes with this + release: + + ``` + 0.31.0 github.com/cipherstash/protectjs-ffi .github/workflows/release.yml + 0.32.0 github.com/cipherstash/stack .github/workflows/release.yml + ``` + + A verification policy that pins the source repository will reject 0.32.0 until + it is updated. The packages, their contents and their maintainers are otherwise + unchanged: the Rust source moved into the monorepo at + `packages/protect-ffi/crates/protect-ffi`, and each of these packages' + `repository.url` now names `cipherstash/stack`, with `repository.directory` + pointing at its own stub under `packages/protect-ffi/platforms/`. + + `CHANGELOG.md` is also added to each package's published files, so this note and + later ones are readable in the package you install rather than only on GitHub. + + The binaries themselves differ from 0.31.0 in one user-visible way: the Rust + core's `InvariantViolation` message asks the reader to file an issue, and the + repository it names has moved with the rest. diff --git a/packages/protect-ffi/platforms/linux-x64-musl/package.json b/packages/protect-ffi/platforms/linux-x64-musl/package.json index 833f0374e..5885c934b 100644 --- a/packages/protect-ffi/platforms/linux-x64-musl/package.json +++ b/packages/protect-ffi/platforms/linux-x64-musl/package.json @@ -1,7 +1,7 @@ { "name": "@cipherstash/protect-ffi-linux-x64-musl", "description": "Prebuilt binary package for `@cipherstash/protect-ffi` on `linux-x64-musl`.", - "version": "0.31.0", + "version": "0.32.0", "repository": { "type": "git", "url": "git+https://github.com/cipherstash/stack.git", diff --git a/packages/protect-ffi/platforms/win32-x64-msvc/CHANGELOG.md b/packages/protect-ffi/platforms/win32-x64-msvc/CHANGELOG.md new file mode 100644 index 000000000..b144314ca --- /dev/null +++ b/packages/protect-ffi/platforms/win32-x64-msvc/CHANGELOG.md @@ -0,0 +1,31 @@ +# @cipherstash/protect-ffi-win32-x64-msvc + +## 0.32.0 + +### Patch Changes + +- 8dc2ebe: **This is the first release of these packages published from + `cipherstash/stack`.** Every version up to and including 0.31.0 was published + from `cipherstash/protectjs-ffi`, which is archived once this release is out. + + If you verify npm provenance, the attested source repository changes with this + release: + + ``` + 0.31.0 github.com/cipherstash/protectjs-ffi .github/workflows/release.yml + 0.32.0 github.com/cipherstash/stack .github/workflows/release.yml + ``` + + A verification policy that pins the source repository will reject 0.32.0 until + it is updated. The packages, their contents and their maintainers are otherwise + unchanged: the Rust source moved into the monorepo at + `packages/protect-ffi/crates/protect-ffi`, and each of these packages' + `repository.url` now names `cipherstash/stack`, with `repository.directory` + pointing at its own stub under `packages/protect-ffi/platforms/`. + + `CHANGELOG.md` is also added to each package's published files, so this note and + later ones are readable in the package you install rather than only on GitHub. + + The binaries themselves differ from 0.31.0 in one user-visible way: the Rust + core's `InvariantViolation` message asks the reader to file an issue, and the + repository it names has moved with the rest. diff --git a/packages/protect-ffi/platforms/win32-x64-msvc/package.json b/packages/protect-ffi/platforms/win32-x64-msvc/package.json index 3a80a838d..6a33f28d8 100644 --- a/packages/protect-ffi/platforms/win32-x64-msvc/package.json +++ b/packages/protect-ffi/platforms/win32-x64-msvc/package.json @@ -1,7 +1,7 @@ { "name": "@cipherstash/protect-ffi-win32-x64-msvc", "description": "Prebuilt binary package for `@cipherstash/protect-ffi` on `win32-x64-msvc`.", - "version": "0.31.0", + "version": "0.32.0", "repository": { "type": "git", "url": "git+https://github.com/cipherstash/stack.git", diff --git a/packages/stack-drizzle/CHANGELOG.md b/packages/stack-drizzle/CHANGELOG.md index cc6b8a902..d03fc5e59 100644 --- a/packages/stack-drizzle/CHANGELOG.md +++ b/packages/stack-drizzle/CHANGELOG.md @@ -1,5 +1,57 @@ # @cipherstash/stack-drizzle +## 2.0.0 + +### Major Changes + +- 8dc2ebe: **Why this package went to 2.0.0.** The major version number comes from + `@cipherstash/stack-prisma`, which moves to Prisma Next 0.17 — a breaking change + for its consumers, with the upgrade steps in its own Major Changes entry. These + six packages share one version line, so a major in any of them takes all six to + the same number: + + - `stash` + - `@cipherstash/stack` + - `@cipherstash/stack-drizzle` + - `@cipherstash/stack-supabase` + - `@cipherstash/stack-prisma` + - `@cipherstash/wizard` + + They are versioned together on purpose. `stash init` pins the versions of the + packages it installs and the CLI embeds that map at build time, so a package + shipping alone would leave the CLI recommending versions that no longer match + what is published, and warning about a skew it had itself created. + + **This does not mean every package in the release is drop-in.** The version + number is shared; the changes are not. Two changes elsewhere in this release + need action from some users, and neither is filed under Major Changes — each is + recorded at the level its own author judged correct. They are named here so you + do not have to read six changelogs to find them: + + - **`@cipherstash/stack` — `clientKey` is hex-only.** A decoder fallback that + also accepted standard padded base64 is gone, and such a key is now rejected + at client construction with `invalid clientKey: expected a hex-encoded key`. + Hex is what `stash env` emits and what the docs have always specified, so most + callers are unaffected; a key pasted out of `~/.cipherstash/secretkey.json` + (which stores base64) is not. The full entry is "Adopt protect-ffi 0.31.0" in + the **`@cipherstash/stack`** changelog; it also narrows which `error.code` + values DynamoDB operations report. + - **`stash` — `stash eql validate` lost `--exclude-operator-family`,** and two + checks that used to exit 1 no longer do. A script passing that flag, or a CI + gate relying on those exit codes, needs updating. The full entry is under + `eql validate` in the **`stash`** changelog. + + If you use neither `@cipherstash/stack-prisma` nor either of those, upgrading + 1.x → 2.0.0 needs no code changes. + +### Patch Changes + +- Updated dependencies [93d62c2] +- Updated dependencies [6c211f5] +- Updated dependencies [8dc2ebe] +- Updated dependencies [483a925] + - @cipherstash/stack@2.0.0 + ## 1.0.0 ### Major Changes diff --git a/packages/stack-drizzle/package.json b/packages/stack-drizzle/package.json index ec60d110a..18eb0e892 100644 --- a/packages/stack-drizzle/package.json +++ b/packages/stack-drizzle/package.json @@ -1,6 +1,6 @@ { "name": "@cipherstash/stack-drizzle", - "version": "1.0.0", + "version": "2.0.0", "description": "CipherStash Stack Drizzle ORM integration: searchable, application-layer field-level encryption for PostgreSQL.", "keywords": [ "encrypted", diff --git a/packages/stack-prisma/CHANGELOG.md b/packages/stack-prisma/CHANGELOG.md index bae13f9f5..d0d24901e 100644 --- a/packages/stack-prisma/CHANGELOG.md +++ b/packages/stack-prisma/CHANGELOG.md @@ -1,5 +1,37 @@ # @cipherstash/stack-prisma +## 2.0.0 + +### Major Changes + +- c795225: Upgrade the Prisma Next integration to Prisma Next 0.17 (the `prisma/prisma` "Prisma 8" main line). Consuming apps must move to the 0.17 publish surface to use this release. + + Breaking changes for consumers: + + - **Dependencies**: the `@prisma-next/*` scope is retired. An application now depends on exactly one database facade — `@prisma/orm-postgres@0.17.0` — plus this extension. `@cipherstash/stack-prisma` itself builds against `@prisma/orm-framework`, `@prisma/orm-family-sql`, and `@prisma/orm-toolchain`, and declares `@prisma/orm-target-postgres` as a peer dependency. + - **Generated imports**: the emitted `contract.d.ts` now imports this extension's types from `@cipherstash/stack-prisma/{codec-types,operation-types,runtime}` (previously the stale `@prisma-next/extension-cipherstash/*` names, which no longer resolve). Re-run `prisma-next contract emit` after upgrading. + - **Contract and migration hashes**: 0.17 renames the contract's `extensionPacks` key to `extensions` and drops the `sha256:` prefix from every content hash, so every contract `storageHash` and `migrationHash` changes. The shipped migration set is re-anchored accordingly; consumer repos convert their checked-in `migrations/` trees with the upstream `strip-sha256-hash-prefixes` codemod and `scripts/migrate-migrations-layout.mjs` (the content-addressed `migrations/snapshots/` store replaces per-migration `end-contract.*` files). Vendored `migrations/cipherstash/` copies must be refreshed (delete and re-run `prisma-next migration plan`, or copy the shipped artefacts). + - **Codec descriptors**: the v3 codec descriptors are now Postgres target descriptors (`nativeTypeFor` / `projectJson` via `postgresCodec`), replacing the deleted `meta.db.sql.postgres` channel, and the pack meta publishes them through `types.codecTypes.codecDescriptors` (0.17 removed `codecInstances`). + - **Config**: in `prisma-next.config.ts` use the facade's `defineConfig` from `@prisma/orm-postgres/config` with `extensions: [cipherstash]` (`extensionPacks` fails loudly on 0.17). + + The `stash` CLI now also detects Prisma Next projects that depend on the 0.17 packages (`prisma-next` or any `@prisma/orm-*` package), and the bundled `stash-prisma` skill documents the 0.17 surface. + +### Patch Changes + +- ba3830f: Ship `CHANGELOG.md` inside the published tarball. It was missing from `files`, + so the release notes for these packages were readable on GitHub and on the npm + web page but not in the package you actually install — which is the copy you + have when something breaks offline, or when the repository has moved. + + `@cipherstash/stack-drizzle` and `@cipherstash/stack-supabase` gain it in the + same release, as do the six `@cipherstash/protect-ffi-` packages. + +- Updated dependencies [93d62c2] +- Updated dependencies [6c211f5] +- Updated dependencies [8dc2ebe] +- Updated dependencies [483a925] + - @cipherstash/stack@2.0.0 + ## 1.0.0 ### Major Changes diff --git a/packages/stack-prisma/package.json b/packages/stack-prisma/package.json index 18f59369c..8311e8a37 100644 --- a/packages/stack-prisma/package.json +++ b/packages/stack-prisma/package.json @@ -1,6 +1,6 @@ { "name": "@cipherstash/stack-prisma", - "version": "1.0.0", + "version": "2.0.0", "license": "MIT", "author": "CipherStash ", "description": "CipherStash extension for Prisma Next: searchable application-layer field-level encryption for Postgres (EQL v3), with domain-typed encrypted columns, the eql* query operators, bulk encrypt/decrypt middleware, and a baseline migration that installs the EQL v3 bundle.", diff --git a/packages/stack-supabase/CHANGELOG.md b/packages/stack-supabase/CHANGELOG.md index 1fadbc53a..c285fb0a9 100644 --- a/packages/stack-supabase/CHANGELOG.md +++ b/packages/stack-supabase/CHANGELOG.md @@ -1,5 +1,87 @@ # @cipherstash/stack-supabase +## 2.0.0 + +### Major Changes + +- 8dc2ebe: **Why this package went to 2.0.0.** The major version number comes from + `@cipherstash/stack-prisma`, which moves to Prisma Next 0.17 — a breaking change + for its consumers, with the upgrade steps in its own Major Changes entry. These + six packages share one version line, so a major in any of them takes all six to + the same number: + + - `stash` + - `@cipherstash/stack` + - `@cipherstash/stack-drizzle` + - `@cipherstash/stack-supabase` + - `@cipherstash/stack-prisma` + - `@cipherstash/wizard` + + They are versioned together on purpose. `stash init` pins the versions of the + packages it installs and the CLI embeds that map at build time, so a package + shipping alone would leave the CLI recommending versions that no longer match + what is published, and warning about a skew it had itself created. + + **This does not mean every package in the release is drop-in.** The version + number is shared; the changes are not. Two changes elsewhere in this release + need action from some users, and neither is filed under Major Changes — each is + recorded at the level its own author judged correct. They are named here so you + do not have to read six changelogs to find them: + + - **`@cipherstash/stack` — `clientKey` is hex-only.** A decoder fallback that + also accepted standard padded base64 is gone, and such a key is now rejected + at client construction with `invalid clientKey: expected a hex-encoded key`. + Hex is what `stash env` emits and what the docs have always specified, so most + callers are unaffected; a key pasted out of `~/.cipherstash/secretkey.json` + (which stores base64) is not. The full entry is "Adopt protect-ffi 0.31.0" in + the **`@cipherstash/stack`** changelog; it also narrows which `error.code` + values DynamoDB operations report. + - **`stash` — `stash eql validate` lost `--exclude-operator-family`,** and two + checks that used to exit 1 no longer do. A script passing that flag, or a CI + gate relying on those exit codes, needs updating. The full entry is under + `eql validate` in the **`stash`** changelog. + + If you use neither `@cipherstash/stack-prisma` nor either of those, upgrading + 1.x → 2.0.0 needs no code changes. + +### Minor Changes + +- 1a3e980: `encryptedSupabase` can now be constructed without a Postgres connection, and there is a new edge entry that runs it off Node. + + **The rule: declare your schemas and it runs anywhere; omit them and we discover them for you, which needs a database connection and is therefore Node-only.** + + Previously the wrapper always introspected the database to derive each column's encryption config from its Postgres domain. That made it unconstructible anywhere a TCP socket to Postgres is unavailable, and cost a second, more privileged credential even on Node — the caller already had an authenticated Supabase client and had to supply a `databaseUrl` as well. + + - **Passing `schemas` with no database URL skips introspection entirely.** No connection, no `pg`, no `databaseUrl`. + - **New `@cipherstash/stack-supabase/wasm-inline` entry.** Identical wrapper, WASM engine. The package root statically imports the native engine (`@cipherstash/protect-ffi` and `@cipherstash/auth`, both Node-API), and a static import loads whether or not you encrypt anything — so an edge runtime needs a different entry, not a different code path. ESM-only, matching `@cipherstash/stack/wasm-inline`. Server-side only; not browser-safe (#804). + - **`DATABASE_URL` is now read through a guard.** On a runtime with no `process` global a bare `process.env.X` is a `ReferenceError`, not `undefined`, so the unguarded read threw during construction before declared mode could help. + + **Existing callers are unaffected.** The gate is the database URL, not the presence of `schemas`: if a URL resolves — from `options.databaseUrl` or `DATABASE_URL` — introspection still runs, and a `schemas`-passing caller still gets the drift check that verifies their declaration against the real column domains. "Pass `databaseUrl` as well" is how you keep verification while declaring types. + + What declared mode gives up, it gives up loudly rather than silently: + + - **`select('*')` and bare `select()` are refused.** `allColumns` comes only from introspection, and an unexpanded `*` reaches PostgREST without the `::jsonb` casts encrypted columns need. + - **`from()` on an undeclared table throws**, naming the declaration rather than an introspection pass that never ran. + - **The drift check is absent**, so a wrong declared domain surfaces as a `23514` CHECK violation on the first write instead of at construction. + - **`queryDomainsRequired` is forced rather than detected**, since the installed EQL version is read by introspection. This is the fail-loud direction: correct on EQL >= 3.0.2, and on an older install the operand cast fails visibly instead of emitting an operator the database will not engage. + - **Passing `databaseUrl` to the `wasm-inline` entry is refused** — it carries no Postgres driver, and saying so beats ignoring the option. + + One tradeoff is **not** loud, and is the declared-mode contract you have to hold yourself: **your declaration must cover every encrypted column of a table you query.** Nothing introspects, so a column carrying an `eql_v3` domain in the database but absent from `schemas` is treated as an ordinary plaintext column — a `select` naming it returns the raw EQL payload as data, and a filter on it sends the plaintext operand to PostgREST. The always-introspect path could not do this (undeclared columns were synthesized from their domains). Declare every encrypted column, or pass `databaseUrl` so introspection fills the gaps. + + An ambient `DATABASE_URL` no longer overrules a declaration, and is consulted only by a build that could act on it: on the edge entry — which cannot introspect at all — it is never read, so a `DATABASE_URL` that happens to be set in the environment cannot break a declared-mode client. On the native entry, passing `schemas` without an explicit `databaseUrl` ignores the variable and warns that the declaration is unverified. The refusal of a `databaseUrl` on the edge entry now keys on the option you actually passed, so it can never fire for a value you did not write. Previously a stray variable silently exited declared mode — introspecting a database the caller never named on Node, and on the edge entry throwing "drop databaseUrl" about an option never passed. + + **The edge entry adapts the WASM client rather than casting to it.** The two engines are not drop-in for each other, and every difference is silent at construction — the entry would have built a client happily while each query through it failed. `decryptModel` / `bulkDecryptModels` require the table on WASM and derive it from the payloads on native (both call sites now pass it, which native ignores); WASM operations are plain Results with no `.withLockContext()` or `.audit()`, so both are attached and throw a sentence naming the gap rather than a bare `TypeError`; and `bulkEncrypt` is deliberately not forwarded, selecting the supported per-term fallback instead of a mismatched signature. Lock context is a real capability gap on the WASM engine (cipherstash/stack#797) — failing loudly is the only honest option, since silently dropping the claim would write values any keyset holder could decrypt. + + The edge entry's options are also typed for what it actually requires: `schemas` and a `WasmClientConfig` `config` are both mandatory (there is no `~/.cipherstash` to discover credentials from), and `databaseUrl` is absent from the type as well as refused at runtime. Previously the shared factory's erased config type let an edge caller omit credentials entirely and reach a `TypeError` from inside the engine. + +### Patch Changes + +- Updated dependencies [93d62c2] +- Updated dependencies [6c211f5] +- Updated dependencies [8dc2ebe] +- Updated dependencies [483a925] + - @cipherstash/stack@2.0.0 + ## 1.0.0 ### Major Changes diff --git a/packages/stack-supabase/package.json b/packages/stack-supabase/package.json index 84be7f0d2..1f4e73bb6 100644 --- a/packages/stack-supabase/package.json +++ b/packages/stack-supabase/package.json @@ -1,6 +1,6 @@ { "name": "@cipherstash/stack-supabase", - "version": "1.0.0", + "version": "2.0.0", "description": "CipherStash Stack Supabase integration: transparent, searchable field-level encryption for Supabase.", "keywords": [ "encrypted", diff --git a/packages/stack/CHANGELOG.md b/packages/stack/CHANGELOG.md index e00731a5e..1da33b0fb 100644 --- a/packages/stack/CHANGELOG.md +++ b/packages/stack/CHANGELOG.md @@ -1,5 +1,129 @@ # @cipherstash/stack +## 2.0.0 + +### Major Changes + +- 8dc2ebe: **Why this package went to 2.0.0.** The major version number comes from + `@cipherstash/stack-prisma`, which moves to Prisma Next 0.17 — a breaking change + for its consumers, with the upgrade steps in its own Major Changes entry. These + six packages share one version line, so a major in any of them takes all six to + the same number: + + - `stash` + - `@cipherstash/stack` + - `@cipherstash/stack-drizzle` + - `@cipherstash/stack-supabase` + - `@cipherstash/stack-prisma` + - `@cipherstash/wizard` + + They are versioned together on purpose. `stash init` pins the versions of the + packages it installs and the CLI embeds that map at build time, so a package + shipping alone would leave the CLI recommending versions that no longer match + what is published, and warning about a skew it had itself created. + + **This does not mean every package in the release is drop-in.** The version + number is shared; the changes are not. Two changes elsewhere in this release + need action from some users, and neither is filed under Major Changes — each is + recorded at the level its own author judged correct. They are named here so you + do not have to read six changelogs to find them: + + - **`@cipherstash/stack` — `clientKey` is hex-only.** A decoder fallback that + also accepted standard padded base64 is gone, and such a key is now rejected + at client construction with `invalid clientKey: expected a hex-encoded key`. + Hex is what `stash env` emits and what the docs have always specified, so most + callers are unaffected; a key pasted out of `~/.cipherstash/secretkey.json` + (which stores base64) is not. The full entry is "Adopt protect-ffi 0.31.0" in + the **`@cipherstash/stack`** changelog; it also narrows which `error.code` + values DynamoDB operations report. + - **`stash` — `stash eql validate` lost `--exclude-operator-family`,** and two + checks that used to exit 1 no longer do. A script passing that flag, or a CI + gate relying on those exit codes, needs updating. The full entry is under + `eql validate` in the **`stash`** changelog. + + If you use neither `@cipherstash/stack-prisma` nor either of those, upgrading + 1.x → 2.0.0 needs no code changes. + +### Minor Changes + +- 93d62c2: Add `EncryptionClient.getSchemas()` — the tables passed to + `Encryption({ schemas })`, returned by reference. + + This is the domain-bearing view of your schema. `getEncryptConfig()` returns + what the FFI consumes: each column builds to `{ cast_as, indexes }`, and the + concrete EQL v3 domain name is dropped. That makes `cast_as: 'number'` with an + `ope` index ambiguous across `eql_v3_integer_ord`, `smallint_ord`, `real_ord`, + `double_ord` and `numeric_ord` — so tooling that has to reason about the + _declared_ domain (schema linting, drift-checking a live database's + `information_schema.columns.domain_name`) could not recover it from a client + alone. + + `getSchemas()` closes that gap. Read a column's domain with + `column.getEqlType()`, its capabilities with `column.getQueryCapabilities()`, + and its DB name with `column.getName()`: + + ```typescript + for (const table of client.getSchemas()) { + for (const column of Object.values(table.columnBuilders)) { + console.log(table.tableName, column.getName(), column.getEqlType()); + } + } + ``` + + `stash eql validate` is the first consumer. + +- 483a925: Add a `@cipherstash/stack/diagnostics` subpath, for tooling that needs to prove the protect-ffi native binding is installed. + + It exports one function, `assertNativeBindingAvailable()`. Calling it forces the platform binary to load and throws the loader's own `MODULE_NOT_FOUND` — unwrapped, naming the missing `@cipherstash/protect-ffi--` package — if it is absent. Importing the subpath does not force anything, so the laziness that makes the native load cost nothing for callers that never encrypt is preserved. + + The subpath exists because there is no way to do this from outside: the package's loader is not in its `exports` map, and reading an export never reaches the `@neon-rs/load` proxy. Importing `@cipherstash/stack` itself is not a substitute either — the root entry re-exports the auth strategies, so evaluating it resolves `@cipherstash/auth`'s binding instead. This entry reaches protect-ffi and nothing else. + + It probes by calling `isEncrypted`, which has been published since 0.28.0, rather than protect-ffi's own `assertNativeBindingAvailable` — that export arrived with the lazy native load and is not in any released version, so re-exporting it would build here and fail wherever it shipped (a link-time error under ESM, an `undefined` under CJS). + + Available as both `import` and `require`. + +### Patch Changes + +- 6c211f5: Adopt protect-ffi 0.31.0. + + **`clientKey` is hex, and a decoder tolerance that accepted other spellings is + gone.** Hex has always been the documented and only supported encoding for + `config.clientKey` / `CS_CLIENT_KEY` — it is what `stash env` emits and what + the docs and skills have always shown. The decoder underneath happened to fall + back to standard padded base64, which is the encoding the Rust + `stash-profile` crate uses for `~/.cipherstash/secretkey.json` on disk; that + fallback was never part of this package's contract, and nothing in the + JavaScript stack ever produced or accepted a base64 key. It is now rejected at + client construction with `invalid clientKey: expected a hex-encoded key`. + + The message deliberately says nothing more, because the underlying decode error + names the offending character and its offset and would put part of a live key + into your logs. So if construction starts failing after this upgrade, the key + you supplied is not hex — re-encode it, or drop the explicit key and let the + native client read it from the profile store. + + Reading the key from `~/.cipherstash/secretkey.json` is unaffected — that path + still uses base64, and only an explicitly supplied key is hex-only. + + **DynamoDB errors no longer report foreign error codes as encryption codes.** + `handleError` accepted any string-valued `code` on a caught error and passed it + through as a `ProtectErrorCode`, so a Node or AWS SDK failure — `ECONNRESET`, + say — surfaced as though it were an encryption error code. Codes are now checked + against the set the encryption layer actually emits, and anything else becomes + `DYNAMODB_ENCRYPTION_ERROR`. If you branch on `error.code` for DynamoDB + operations, a branch that was matching transport errors will stop. + + Also in this release, with no action needed: the WASM entry passes credentials + under the option shape 0.31 expects and no longer pre-normalises `cast_as` + (the native layer does it on both bindings now), and bulk operations no longer + forward their internal correlation id across the FFI boundary, which 0.31 + rejects rather than ignores. + +- Updated dependencies [e77bfce] +- Updated dependencies [e77bfce] +- Updated dependencies [ba3830f] + - @cipherstash/protect-ffi@0.32.0 + ## 1.0.0 ### Major Changes diff --git a/packages/stack/package.json b/packages/stack/package.json index 233c4dd2b..c4e8a5eda 100644 --- a/packages/stack/package.json +++ b/packages/stack/package.json @@ -1,6 +1,6 @@ { "name": "@cipherstash/stack", - "version": "1.0.0", + "version": "2.0.0", "description": "CipherStash Stack for TypeScript and JavaScript", "keywords": [ "encrypted", diff --git a/packages/test-kit/CHANGELOG.md b/packages/test-kit/CHANGELOG.md index 1ed46a03c..025d11a02 100644 --- a/packages/test-kit/CHANGELOG.md +++ b/packages/test-kit/CHANGELOG.md @@ -1,5 +1,15 @@ # @cipherstash/test-kit +## 0.0.2 + +### Patch Changes + +- Updated dependencies [93d62c2] +- Updated dependencies [6c211f5] +- Updated dependencies [8dc2ebe] +- Updated dependencies [483a925] + - @cipherstash/stack@2.0.0 + ## 0.0.1 ### Patch Changes diff --git a/packages/test-kit/package.json b/packages/test-kit/package.json index c55ffbb86..b3de43358 100644 --- a/packages/test-kit/package.json +++ b/packages/test-kit/package.json @@ -1,6 +1,6 @@ { "name": "@cipherstash/test-kit", - "version": "0.0.1", + "version": "0.0.2", "private": true, "description": "Shared EQL v3 test harness: the domain catalog, the plaintext oracle, and the integration-suite driver. Consumed as TypeScript source — no build step.", "type": "module", diff --git a/packages/wizard/CHANGELOG.md b/packages/wizard/CHANGELOG.md index cad882c34..9ec03de7f 100644 --- a/packages/wizard/CHANGELOG.md +++ b/packages/wizard/CHANGELOG.md @@ -1,5 +1,49 @@ # @cipherstash/wizard +## 2.0.0 + +### Major Changes + +- 8dc2ebe: **Why this package went to 2.0.0.** The major version number comes from + `@cipherstash/stack-prisma`, which moves to Prisma Next 0.17 — a breaking change + for its consumers, with the upgrade steps in its own Major Changes entry. These + six packages share one version line, so a major in any of them takes all six to + the same number: + + - `stash` + - `@cipherstash/stack` + - `@cipherstash/stack-drizzle` + - `@cipherstash/stack-supabase` + - `@cipherstash/stack-prisma` + - `@cipherstash/wizard` + + They are versioned together on purpose. `stash init` pins the versions of the + packages it installs and the CLI embeds that map at build time, so a package + shipping alone would leave the CLI recommending versions that no longer match + what is published, and warning about a skew it had itself created. + + **This does not mean every package in the release is drop-in.** The version + number is shared; the changes are not. Two changes elsewhere in this release + need action from some users, and neither is filed under Major Changes — each is + recorded at the level its own author judged correct. They are named here so you + do not have to read six changelogs to find them: + + - **`@cipherstash/stack` — `clientKey` is hex-only.** A decoder fallback that + also accepted standard padded base64 is gone, and such a key is now rejected + at client construction with `invalid clientKey: expected a hex-encoded key`. + Hex is what `stash env` emits and what the docs have always specified, so most + callers are unaffected; a key pasted out of `~/.cipherstash/secretkey.json` + (which stores base64) is not. The full entry is "Adopt protect-ffi 0.31.0" in + the **`@cipherstash/stack`** changelog; it also narrows which `error.code` + values DynamoDB operations report. + - **`stash` — `stash eql validate` lost `--exclude-operator-family`,** and two + checks that used to exit 1 no longer do. A script passing that flag, or a CI + gate relying on those exit codes, needs updating. The full entry is under + `eql validate` in the **`stash`** changelog. + + If you use neither `@cipherstash/stack-prisma` nor either of those, upgrading + 1.x → 2.0.0 needs no code changes. + ## 1.0.0 ### Minor Changes diff --git a/packages/wizard/package.json b/packages/wizard/package.json index 48c52815d..bb3020917 100644 --- a/packages/wizard/package.json +++ b/packages/wizard/package.json @@ -1,6 +1,6 @@ { "name": "@cipherstash/wizard", - "version": "1.0.0", + "version": "2.0.0", "description": "AI-powered encryption setup for CipherStash. Reads your codebase, picks columns to encrypt, and wires everything up.", "repository": { "type": "git", From 6fcd7e6600ae29077ebc36cd42d369ff9f60f3d6 Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Wed, 19 Aug 2026 15:40:11 +1000 Subject: [PATCH 2/2] fix(skills): bump the shipped version pins to 2.0.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `skills/` ships inside the `stash` tarball and is copied into customer repos by `stash init`, and nothing in the build rewrites the version literals inside it — `tsup.config.ts` copies the directory verbatim, and the `__STASH_RUNTIME_VERSIONS__` embed only reaches compiled CLI code. So these three pins have to be bumped by hand on the version PR, which is exactly what `release-train.test.ts` fires on (#791). - `skills/stash-cli`: the bare-project `npx --package=stash@…` invocation. - `skills/stash-edge`: the Deno `npm:@cipherstash/stack@…/wasm-inline` import and its import-map equivalent. No changeset: the release branch has already consumed them, and adding one here would make the next `changeset version` run regenerate this branch and drop the commit. --- skills/stash-cli/SKILL.md | 2 +- skills/stash-edge/SKILL.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/skills/stash-cli/SKILL.md b/skills/stash-cli/SKILL.md index bd071b631..acdd25740 100644 --- a/skills/stash-cli/SKILL.md +++ b/skills/stash-cli/SKILL.md @@ -391,7 +391,7 @@ Gets a project from zero to a direct EQL v3 install. It loads an existing `stash The removed `--eql-version`, `--latest`, `--drizzle`, `--migration`, `--direct`, `--migrations-dir`, and `--exclude-operator-family` options fail clearly instead of being ignored. A request for EQL v2 points dump-recovery users to the upstream EQL 2.3.1 SQL release. New installs are EQL v3 only; its pinned bundle self-adapts when a database role cannot create the optional operator family. -**`--database-url` is a one-shot.** It installs against that database and leaves the project untouched — no config is loaded, and none is scaffolded, nor is an encryption client. This lets `npx --package=stash@1.0.0 stash eql install --database-url 'postgres://...'` run in a bare project with no CipherStash dependencies while pinning the CLI to this skill's release. It also means the flag always wins: loading a config could pick up a parent-directory `databaseUrl` literal and install against the wrong database. +**`--database-url` is a one-shot.** It installs against that database and leaves the project untouched — no config is loaded, and none is scaffolded, nor is an encryption client. This lets `npx --package=stash@2.0.0 stash eql install --database-url 'postgres://...'` run in a bare project with no CipherStash dependencies while pinning the CLI to this skill's release. It also means the flag always wins: loading a config could pick up a parent-directory `databaseUrl` literal and install against the wrong database. **The install verifies itself.** `eql install` ends by running the same surface check as `eql verify` (below) — on the fresh-install path *and* on the already-installed early exit, so a plain re-run over a damaged database fails rather than printing "Nothing to do." It exits 1 if the surface is incomplete; if the check itself cannot run (connection dropped mid-verify), it warns and points at `stash eql verify` instead of failing the committed install. A version mismatch with the pinned bundle also warns rather than fails there — nothing was actually checked, and a no-op re-run over an older EQL must stay exit 0 for idempotent provisioning scripts. (`eql verify` itself stays strict and exits 1 on a mismatch.) diff --git a/skills/stash-edge/SKILL.md b/skills/stash-edge/SKILL.md index 215420401..784e0f0b3 100644 --- a/skills/stash-edge/SKILL.md +++ b/skills/stash-edge/SKILL.md @@ -73,7 +73,7 @@ build step. ```ts import { Encryption, encryptedTable, types, isEncrypted, -} from 'npm:@cipherstash/stack@1.0.0/wasm-inline' +} from 'npm:@cipherstash/stack@2.0.0/wasm-inline' ``` **Pin an exact version.** Deno caches by specifier, so an unpinned import @@ -88,7 +88,7 @@ name everywhere: ```jsonc { "imports": { - "@cipherstash/stack/wasm-inline": "npm:@cipherstash/stack@1.0.0/wasm-inline" + "@cipherstash/stack/wasm-inline": "npm:@cipherstash/stack@2.0.0/wasm-inline" } } ```