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-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/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/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/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-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-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-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..5a270f07e 100644 --- a/e2e/CHANGELOG.md +++ b/e2e/CHANGELOG.md @@ -1,5 +1,33 @@ # @cipherstash/e2e +## 0.0.4 + +### Patch Changes + +- Updated dependencies [a1b53af] +- Updated dependencies [c82c59d] +- Updated dependencies [71c802a] +- 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 [d473745] +- Updated dependencies [d348f84] +- Updated dependencies [3c7ed59] +- Updated dependencies [68b4d68] +- 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..b644e44e6 100644 --- a/examples/basic/CHANGELOG.md +++ b/examples/basic/CHANGELOG.md @@ -1,5 +1,15 @@ # @cipherstash/basic-example +## 1.2.15 + +### Patch Changes + +- Updated dependencies [93d62c2] +- Updated dependencies [6c211f5] +- 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..942e9a1d4 100644 --- a/examples/prisma/CHANGELOG.md +++ b/examples/prisma/CHANGELOG.md @@ -1,5 +1,16 @@ # @cipherstash/prisma-next-example +## 0.1.1 + +### Patch Changes + +- Updated dependencies [93d62c2] +- Updated dependencies [6c211f5] +- Updated dependencies [c795225] +- 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..7e4ebfd42 100644 --- a/packages/bench/CHANGELOG.md +++ b/packages/bench/CHANGELOG.md @@ -1,5 +1,15 @@ # @cipherstash/bench +## 0.0.6 + +### Patch Changes + +- Updated dependencies [93d62c2] +- Updated dependencies [6c211f5] +- 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 488315751..21a05d872 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,285 @@ # @cipherstash/cli +## 2.0.0 + +### 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. + +- 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. + +- 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. + +- 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 11be50ed5..8676e8d3e 100644 --- a/packages/protect-ffi/CHANGELOG.md +++ b/packages/protect-ffi/CHANGELOG.md @@ -1,5 +1,31 @@ # 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. + All notable changes to this project are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), @@ -25,13 +51,13 @@ its own format. Write a changeset, not a section here. ```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 @@ -52,7 +78,7 @@ its own format. Write a changeset, not a section here. 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 @@ -74,7 +100,7 @@ its own format. Write a changeset, not a section here. - **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]) @@ -107,41 +133,49 @@ its own format. Write a changeset, not a section here. 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 @@ -170,7 +204,7 @@ its own format. Write a changeset, not a section here. - **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. @@ -206,19 +240,19 @@ its own format. Write a changeset, not a section here. ([#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; } ``` @@ -236,7 +270,7 @@ its own format. Write a changeset, not a section here. ### 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. @@ -256,7 +290,7 @@ its own format. Write a changeset, not a section here. 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 @@ -277,8 +311,8 @@ its own format. Write a changeset, not a section here. 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 @@ -495,7 +529,7 @@ its own format. Write a changeset, not a section here. - 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 e24b2ced8..c0726a68e 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..e2ef839f5 --- /dev/null +++ b/packages/protect-ffi/platforms/darwin-arm64/CHANGELOG.md @@ -0,0 +1,3 @@ +# @cipherstash/protect-ffi-darwin-arm64 + +## 0.32.0 diff --git a/packages/protect-ffi/platforms/darwin-arm64/package.json b/packages/protect-ffi/platforms/darwin-arm64/package.json index 2dfa7aff0..4ff3c84f5 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..321e432e0 --- /dev/null +++ b/packages/protect-ffi/platforms/darwin-x64/CHANGELOG.md @@ -0,0 +1,3 @@ +# @cipherstash/protect-ffi-darwin-x64 + +## 0.32.0 diff --git a/packages/protect-ffi/platforms/darwin-x64/package.json b/packages/protect-ffi/platforms/darwin-x64/package.json index 9f288cdcd..f34f379e5 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..c3d9d21ee --- /dev/null +++ b/packages/protect-ffi/platforms/linux-arm64-gnu/CHANGELOG.md @@ -0,0 +1,3 @@ +# @cipherstash/protect-ffi-linux-arm64-gnu + +## 0.32.0 diff --git a/packages/protect-ffi/platforms/linux-arm64-gnu/package.json b/packages/protect-ffi/platforms/linux-arm64-gnu/package.json index 3b4933598..cc4303b42 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..c11df94a9 --- /dev/null +++ b/packages/protect-ffi/platforms/linux-x64-gnu/CHANGELOG.md @@ -0,0 +1,3 @@ +# @cipherstash/protect-ffi-linux-x64-gnu + +## 0.32.0 diff --git a/packages/protect-ffi/platforms/linux-x64-gnu/package.json b/packages/protect-ffi/platforms/linux-x64-gnu/package.json index 44c695c09..dff12110a 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..0abe14877 --- /dev/null +++ b/packages/protect-ffi/platforms/linux-x64-musl/CHANGELOG.md @@ -0,0 +1,3 @@ +# @cipherstash/protect-ffi-linux-x64-musl + +## 0.32.0 diff --git a/packages/protect-ffi/platforms/linux-x64-musl/package.json b/packages/protect-ffi/platforms/linux-x64-musl/package.json index 0c427e61d..7a99221cf 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..5383b51e3 --- /dev/null +++ b/packages/protect-ffi/platforms/win32-x64-msvc/CHANGELOG.md @@ -0,0 +1,3 @@ +# @cipherstash/protect-ffi-win32-x64-msvc + +## 0.32.0 diff --git a/packages/protect-ffi/platforms/win32-x64-msvc/package.json b/packages/protect-ffi/platforms/win32-x64-msvc/package.json index 37116ed34..116d2eb6e 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..50b4cb345 100644 --- a/packages/stack-drizzle/CHANGELOG.md +++ b/packages/stack-drizzle/CHANGELOG.md @@ -1,5 +1,14 @@ # @cipherstash/stack-drizzle +## 2.0.0 + +### Patch Changes + +- Updated dependencies [93d62c2] +- Updated dependencies [6c211f5] +- 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 b67ee7bf7..a5d911411 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 6a06cbe06..30f349012 100644 --- a/packages/stack-prisma/CHANGELOG.md +++ b/packages/stack-prisma/CHANGELOG.md @@ -1,5 +1,28 @@ # @cipherstash/prisma-next +## 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 + +- Updated dependencies [93d62c2] +- Updated dependencies [6c211f5] +- 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 f7114280f..d99abcd8d 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..00d27ca2e 100644 --- a/packages/stack-supabase/CHANGELOG.md +++ b/packages/stack-supabase/CHANGELOG.md @@ -1,5 +1,14 @@ # @cipherstash/stack-supabase +## 2.0.0 + +### Patch Changes + +- Updated dependencies [93d62c2] +- Updated dependencies [6c211f5] +- 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 5520ba9d7..0b06511bc 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..ab7a41a44 100644 --- a/packages/stack/CHANGELOG.md +++ b/packages/stack/CHANGELOG.md @@ -1,5 +1,86 @@ # @cipherstash/stack +## 2.0.0 + +### 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] + - @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..dcfad9400 100644 --- a/packages/test-kit/CHANGELOG.md +++ b/packages/test-kit/CHANGELOG.md @@ -1,5 +1,14 @@ # @cipherstash/test-kit +## 0.0.2 + +### Patch Changes + +- Updated dependencies [93d62c2] +- Updated dependencies [6c211f5] +- 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..f4a892796 100644 --- a/packages/wizard/CHANGELOG.md +++ b/packages/wizard/CHANGELOG.md @@ -1,5 +1,7 @@ # @cipherstash/wizard +## 2.0.0 + ## 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",