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/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/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/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/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..02870ec4d 100644 --- a/e2e/CHANGELOG.md +++ b/e2e/CHANGELOG.md @@ -1,5 +1,26 @@ # @cipherstash/e2e +## 0.0.4 + +### Patch Changes + +- Updated dependencies [a1b53af] +- Updated dependencies [7785643] +- Updated dependencies [66ba41a] +- Updated dependencies [05f84bc] +- Updated dependencies [93d62c2] +- Updated dependencies [6c211f5] +- Updated dependencies [41c4531] +- Updated dependencies [d473745] +- Updated dependencies [d348f84] +- Updated dependencies [483a925] +- Updated dependencies [483a925] +- Updated dependencies [1d10003] +- Updated dependencies [cf08c3b] + - stash@1.1.0 + - @cipherstash/stack@1.1.0 + - @cipherstash/wizard@1.1.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..84a65ea4c 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@1.1.0 + - @cipherstash/stack-drizzle@1.1.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..6ed312a93 100644 --- a/examples/prisma/CHANGELOG.md +++ b/examples/prisma/CHANGELOG.md @@ -1,5 +1,15 @@ # @cipherstash/prisma-next-example +## 0.1.1 + +### Patch Changes + +- Updated dependencies [93d62c2] +- Updated dependencies [6c211f5] +- Updated dependencies [483a925] + - @cipherstash/stack@1.1.0 + - @cipherstash/stack-prisma@1.1.0 + ## 0.1.0 ### Minor Changes diff --git a/examples/prisma/package.json b/examples/prisma/package.json index c51e477f4..7d421da92 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..8b2c9ac92 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@1.1.0 + - @cipherstash/stack-drizzle@1.1.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..240a244cd 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,222 @@ # @cipherstash/cli +## 1.1.0 + +### Minor Changes + +- 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. +- 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. + +- 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. +- 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..c4343aabf 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "stash", - "version": "1.0.0", + "version": "1.1.0", "description": "CipherStash CLI — the one stash command for auth, init, encryption schema, database setup, and secrets.", "repository": { "type": "git", diff --git a/packages/stack-drizzle/CHANGELOG.md b/packages/stack-drizzle/CHANGELOG.md index cc6b8a902..7c7e04f74 100644 --- a/packages/stack-drizzle/CHANGELOG.md +++ b/packages/stack-drizzle/CHANGELOG.md @@ -1,5 +1,14 @@ # @cipherstash/stack-drizzle +## 1.1.0 + +### Patch Changes + +- Updated dependencies [93d62c2] +- Updated dependencies [6c211f5] +- Updated dependencies [483a925] + - @cipherstash/stack@1.1.0 + ## 1.0.0 ### Major Changes diff --git a/packages/stack-drizzle/package.json b/packages/stack-drizzle/package.json index b67ee7bf7..926328f53 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": "1.1.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..046d9a5de 100644 --- a/packages/stack-prisma/CHANGELOG.md +++ b/packages/stack-prisma/CHANGELOG.md @@ -1,5 +1,14 @@ # @cipherstash/prisma-next +## 1.1.0 + +### Patch Changes + +- Updated dependencies [93d62c2] +- Updated dependencies [6c211f5] +- Updated dependencies [483a925] + - @cipherstash/stack@1.1.0 + ## 1.0.0 ### Major Changes diff --git a/packages/stack-prisma/package.json b/packages/stack-prisma/package.json index 8844b5129..99623dfe1 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": "1.1.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..91ebb89cc 100644 --- a/packages/stack-supabase/CHANGELOG.md +++ b/packages/stack-supabase/CHANGELOG.md @@ -1,5 +1,14 @@ # @cipherstash/stack-supabase +## 1.1.0 + +### Patch Changes + +- Updated dependencies [93d62c2] +- Updated dependencies [6c211f5] +- Updated dependencies [483a925] + - @cipherstash/stack@1.1.0 + ## 1.0.0 ### Major Changes diff --git a/packages/stack-supabase/package.json b/packages/stack-supabase/package.json index 5520ba9d7..55d14ee74 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": "1.1.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..89d02690c 100644 --- a/packages/stack/CHANGELOG.md +++ b/packages/stack/CHANGELOG.md @@ -1,5 +1,82 @@ # @cipherstash/stack +## 1.1.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. + ## 1.0.0 ### Major Changes diff --git a/packages/stack/package.json b/packages/stack/package.json index 233c4dd2b..dd3ddd9dd 100644 --- a/packages/stack/package.json +++ b/packages/stack/package.json @@ -1,6 +1,6 @@ { "name": "@cipherstash/stack", - "version": "1.0.0", + "version": "1.1.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..808ad0e31 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@1.1.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..966a6b899 100644 --- a/packages/wizard/CHANGELOG.md +++ b/packages/wizard/CHANGELOG.md @@ -1,5 +1,7 @@ # @cipherstash/wizard +## 1.1.0 + ## 1.0.0 ### Minor Changes diff --git a/packages/wizard/package.json b/packages/wizard/package.json index 48c52815d..477b546b3 100644 --- a/packages/wizard/package.json +++ b/packages/wizard/package.json @@ -1,6 +1,6 @@ { "name": "@cipherstash/wizard", - "version": "1.0.0", + "version": "1.1.0", "description": "AI-powered encryption setup for CipherStash. Reads your codebase, picks columns to encrypt, and wires everything up.", "repository": { "type": "git",