Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
d02bf63
ci(integration): serialize live-DB jobs by compose to stop host-port …
coderdan Jul 13, 2026
134fd43
feat(cli): anonymous opt-out usage analytics + `stash telemetry`
coderdan Jul 14, 2026
a4b9746
Merge pull request #641 from cipherstash/ci/integration-port-collisio…
coderdan Jul 14, 2026
ad77675
fix(cli): address telemetry review + Copilot feedback
coderdan Jul 14, 2026
7752cae
Merge pull request #535 from cipherstash/feat/eql-v3-text-search-schema
coderdan Jul 14, 2026
9048fc6
fix(cli): scrub ambient CI markers in the pty e2e harness
coderdan Jul 14, 2026
7c7dbca
chore: enter pre-release mode (rc) and bump Stack family to 1.0.0
coderdan Jul 14, 2026
4ed9db5
Merge pull request #647 from cipherstash/chore/enter-1.0.0-rc
coderdan Jul 14, 2026
a5aa686
Version Packages (rc)
github-actions[bot] Jul 14, 2026
a9f480f
Merge pull request #597 from cipherstash/changeset-release/main
coderdan Jul 14, 2026
59b994e
feat(stack-drizzle): EQL v3 JSON selector-with-constraint querying
coderdan Jul 14, 2026
64e8998
refactor(stack-drizzle): ciphertext-free v3 JSON selector-with-constr…
coderdan Jul 14, 2026
b5a5f26
fix(stack-drizzle): v3 JSON selector — storage-needle RHS (interim)
coderdan Jul 15, 2026
f45963f
docs(skills): document v3 JSON selector-with-constraint (#623)
coderdan Jul 15, 2026
b5b18c3
fix(stack-drizzle): address CodeRabbit review on v3 JSON selector
coderdan Jul 15, 2026
36ecb3b
fix(stack-drizzle): address freshtonic + Toby review on v3 JSON selector
coderdan Jul 15, 2026
a3a328d
Merge pull request #651 from cipherstash/feat/eql-v3-json-selector
coderdan Jul 15, 2026
92bedb9
feat(cli): make telemetry PostHog host overridable (STASH_POSTHOG_HOST)
coderdan Jul 15, 2026
ed08f4c
feat(cli): classify caller (agent harness vs interactive) in telemetry
coderdan Jul 15, 2026
931b2b6
chore(cli): move telemetry proxy to the platform (cipherstash-suite) …
coderdan Jul 15, 2026
1b6b3e4
feat(cli): embed the PostHog key at release via build-time define
coderdan Jul 15, 2026
4e26a6b
fix(cli): address code-review findings on telemetry
coderdan Jul 15, 2026
d8e0c1d
fix(cli): replace the process.exit override with cooperative CliExit;…
coderdan Jul 15, 2026
fcc3047
docs(cli): disclose the random install identifier in the telemetry docs
coderdan Jul 15, 2026
a8c9602
Merge pull request #644 from cipherstash/feat/cli-anonymous-telemetry
coderdan Jul 15, 2026
5fe9a2f
feat(stack-supabase): EQL v3 encrypted-JSON querying (containment + s…
coderdan Jul 15, 2026
b257953
fix(stack-supabase): address code-review findings on encrypted-JSON q…
coderdan Jul 15, 2026
1d5addf
fix(stack-supabase): address Copilot feedback on the JSON querying su…
coderdan Jul 15, 2026
109bad3
docs(skills): un-stale the v3 ORDER BY bullet in stash-supabase
coderdan Jul 15, 2026
9bf2a56
Merge pull request #653 from cipherstash/feat/eql-v3-supabase-json
coderdan Jul 15, 2026
9a7e0f9
docs: EQL v3 as the sole documented approach (skills + READMEs)
coderdan Jul 15, 2026
7c004ec
Merge remote-tracking branch 'origin/main' into docs/eql-v3-sole-appr…
coderdan Jul 15, 2026
e297f64
docs: changeset for the EQL v3 sole-approach docs restructure
coderdan Jul 15, 2026
3fdd740
docs: address code-review findings on the v3-sole-approach restructure
coderdan Jul 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .changeset/cli-anonymous-telemetry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
'stash': minor
---

Add anonymous, opt-out usage analytics to the `stash` CLI, plus a
`stash telemetry [status|enable|disable]` command to manage it.

Only coarse events are collected — command name, CLI version, OS/arch, Node
version, success/failure, duration, and a coarse caller class (e.g.
`claude-code`, `cursor`, `interactive`) derived from environment markers so we
can gauge agent- vs human-driven usage. Events carry a random install
identifier (a locally generated UUID, not derived from any machine or user
attribute) used only to de-duplicate events in aggregate. Plaintext, schema,
table/column names,
connection strings, argument values, and any session/trace identifier are never
collected — enforced by a property-key allowlist at the emitter boundary plus
closed-vocabulary coercion of every argv- or error-derived value (unrecognised
commands, subcommands, and error class names all collapse to `<other>`). A
one-time notice is shown on first run, and nothing is sent on that run.

Telemetry is off by default in CI and can be disabled with `DO_NOT_TRACK=1`
(the cross-tool standard), `STASH_TELEMETRY_DISABLED=1`, or
`stash telemetry disable` (persisted to `~/.cipherstash/telemetry.json`).

Events are sent via a first-party proxy and never block or slow the CLI. The
feature ships dormant — no events are sent until a PostHog project key is
embedded at release. Updates the `stash-cli` skill to document the command and
opt-out controls.
7 changes: 7 additions & 0 deletions .changeset/drizzle-legacy-readme-pointer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@cipherstash/drizzle': patch
---

Docs: the README's "for new projects" pointer named the removed
`@cipherstash/stack/drizzle` subpath; it now points at the separate
`@cipherstash/stack-drizzle` package (EQL v3 on its `/v3` subpath).
26 changes: 26 additions & 0 deletions .changeset/eql-v3-json-selector.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
'@cipherstash/stack-drizzle': minor
'stash': patch
---

Add EQL v3 JSON **selector-with-constraint** querying to the Drizzle integration
(#623). `ops.selector(col, '$.path')` returns comparison methods bound to a
JSONPath into a `types.Json` column — `eq`/`ne`/`gt`/`gte`/`lt`/`lte` — emitting
`col->'<selector>' <op> <value>` over the encrypted document. Its unique power
over `contains` is **ordering at a path** (`col->'$.age' > 21`), which
containment cannot express.

Complements the existing `contains` (JSONB `@>`) containment operator. Core
`@cipherstash/stack` needs no change — the selector hash and comparison entry are
produced by `encryptQuery`/`encrypt` on the existing `types.Json` surface. v1
supports dot-notation object paths; array-index/wildcard paths are rejected with
a clear error. The Supabase adapter is tracked separately.

The right-hand comparison operand is currently a storage-encrypted needle (its
ste_vec entry carries the ordering term), pending a ciphertext-free ordering
query needle from protect-ffi (cipherstash/protectjs-ffi#137); until then the
value's ciphertext appears in the WHERE clause.

The bundled `stash-encryption` and `stash-drizzle` skills document the new
`ops.selector(...)` surface (they previously said JSONPath selector queries were
not yet implemented).
16 changes: 16 additions & 0 deletions .changeset/eql-v3-sole-docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
'stash': patch
'@cipherstash/stack': patch
---

Docs: EQL v3 is now the sole documented approach. The `stash-encryption`,
`stash-drizzle`, and `stash-supabase` skills and the `@cipherstash/stack`
README teach only the v3 typed surface (`EncryptionV3`, `types.*` concrete
domains, `@cipherstash/stack-drizzle/v3`, `encryptedSupabaseV3`); EQL v2
shrinks to one short Legacy section per document. Two explicit exceptions are
called out: DynamoDB still requires the v2 schema surface (#657), and the
encrypt rollout tooling (`stash encrypt backfill`/`cutover`,
`@cipherstash/migrate`) currently targets v2 columns (#648) — its guidance is
kept under a version callout. Also corrects the legacy `@cipherstash/drizzle`
README's pointer to the removed `@cipherstash/stack/drizzle` subpath (now the
separate `@cipherstash/stack-drizzle` package).
64 changes: 64 additions & 0 deletions .changeset/pre.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"mode": "pre",
"tag": "rc",
"initialVersions": {
"@cipherstash/e2e": "0.0.2",
"@cipherstash/basic-example": "1.2.13",
"@cipherstash/prisma-next-example": "0.0.5",
"@cipherstash/supabase-worker-example": "0.0.0",
"@cipherstash/bench": "0.0.4",
"stash": "0.17.1",
"@cipherstash/drizzle": "3.0.3",
"@cipherstash/migrate": "0.2.0",
"@cipherstash/nextjs": "4.1.1",
"@cipherstash/prisma-next": "0.3.2",
"@cipherstash/protect": "12.0.1",
"@cipherstash/protect-dynamodb": "12.0.1",
"@cipherstash/schema": "3.0.1",
"@cipherstash/stack": "0.19.0",
"@cipherstash/stack-drizzle": "0.0.0",
"@cipherstash/stack-supabase": "0.0.0",
"@cipherstash/test-kit": "0.0.0",
"@cipherstash/wizard": "0.4.0"
},
"changesets": [
"adapter-package-split",
"adapter-split-skills",
"cli-eql-v3-single-bundle",
"eql-v3-adapter-type-robustness",
"eql-v3-bigint-domains",
"eql-v3-bundle-from-package",
"eql-v3-cli-install",
"eql-v3-drizzle-encrypt-query",
"eql-v3-drizzle-fail-open-guards",
"eql-v3-drizzle",
"eql-v3-ffi-0-28-concrete-types",
"eql-v3-ga-rebaseline",
"eql-v3-json-skills",
"eql-v3-json",
"eql-v3-public-domains",
"eql-v3-rename-contains-to-matches",
"eql-v3-supabase-adapter",
"eql-v3-text-search",
"eql-v3-typed-client",
"eql-v3-typed-schema",
"eql-v3-wasm-inline",
"prisma-next-0-14",
"remove-secrets-leftovers",
"rename-db-install-to-eql-install",
"schema-stevec-standard-pin",
"stack-1-0-0-rc",
"stack-adapter-kit",
"stash-cli-eql-v3-default",
"stash-cli-skill-refresh",
"stash-drizzle-skill-encrypt-query",
"stash-skills-contains-to-matches",
"stash-supabase-contains-substrings",
"supabase-encryption-error",
"supabase-in-list-operands",
"supabase-is-null-operands",
"supabase-or-string-parser",
"supabase-v3-order-by-ope-term",
"wizard-allow-env-templates"
]
}
13 changes: 13 additions & 0 deletions .changeset/stack-1-0-0-rc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
'@cipherstash/stack': major
'@cipherstash/stack-drizzle': major
'@cipherstash/stack-supabase': major
'stash': major
---

CipherStash Stack 1.0 (release candidate).

This is the first 1.0-line release of `@cipherstash/stack`, the first published
release of the split-out EQL v3 adapters `@cipherstash/stack-drizzle` and
`@cipherstash/stack-supabase`, and moves the `stash` CLI to 1.0 alongside them.
These four packages now version together as the Stack 1.0 family.
28 changes: 28 additions & 0 deletions .changeset/supabase-v3-json-querying.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
'@cipherstash/stack-supabase': minor
'@cipherstash/stack': minor
'stash': patch
---

Encrypted-JSON querying on the v3 Supabase surface (#650). A `types.Json`
column now supports exact encrypted containment — `contains(col, subDocument)`
(ste_vec `@>` via PostgREST `cs`, with the sub-document storage-encrypted
against the column) — and JSONPath selector predicates: `selectorEq(col, path,
value)` and `selectorNe(col, path, value)` (dot-notation paths; `ne` includes
rows where the path is absent, mirroring the Drizzle selector's semantics).
Raw `.filter(col, 'cs', subDocument)` and `not(col, 'contains', …)` route
through the same encrypted path. Selector ordering is not expressible over
PostgREST yet (needs an EQL-bundle overload — see
cipherstash/encrypt-query-language#407); the Drizzle integration's
`ops.selector()` covers ordering today.

In core, `QueryTypesForColumn` gains the `searchableJson` arm (a `types.Json`
column no longer resolves to `never`, so typed adapter key sets can include
it), and the JSONPath selector-path helpers the Drizzle adapter introduced in
#651 moved to `@cipherstash/stack/adapter-kit` so both adapters share one
validation surface (`@cipherstash/stack-drizzle` re-exports them unchanged).

The bundled `stash-supabase` and `stash-encryption` skills are updated to
document the new querying surface (including the array-leaf and SQL-NULL
semantics, and the operand-exposure caveat) — skills ship inside the `stash`
tarball, hence the patch.
11 changes: 11 additions & 0 deletions .changeset/wizard-analytics-privacy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@cipherstash/wizard': patch
---

Align the wizard's analytics with the `stash` CLI's telemetry privacy contract.
The wizard now honors `DO_NOT_TRACK`, `STASH_TELEMETRY_DISABLED`, and CI
auto-detection; uses a random per-session identifier instead of one derived
from username@hostname; disables IP→geo resolution; and reports error events as
fixed labels / error class names instead of raw messages (which could embed
schema names or connection details). Analytics remain dormant unless a PostHog
key is configured at build time.
20 changes: 20 additions & 0 deletions .github/workflows/integration-drizzle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,18 @@ jobs:
integration:
name: Drizzle v3 integration (db=${{ matrix.db }})
runs-on: blacksmith-4vcpu-ubuntu-2404
# Serialize every job that brings up the SAME docker-compose stack, so no two
# bind the same fixed host ports on a shared runner at once (the "address
# already in use" flake). Keyed by the compose variant, NOT the ref (two PRs
# contend for a host port just as much as two pushes to one PR), so it
# serializes across refs. The `supabase` leg uses the SAME supabase compose
# (55430 / 55433) as the Supabase workflow and shares its key
# (`integration-live-db-supabase`), so the two workflows queue rather than
# collide; the `postgres` leg (55432) has its own key and still runs in
# parallel with them.
concurrency:
group: integration-live-db-${{ matrix.db }}
cancel-in-progress: false
# Fork PRs have no secrets. Skip cleanly rather than fail on something the
# contributor cannot fix — `tests.yml` still gives them a green signal.
if: ${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository }}
Expand Down Expand Up @@ -134,6 +146,14 @@ jobs:
client-key: ${{ secrets.CS_CLIENT_KEY }}
client-access-key: ${{ secrets.CS_CLIENT_ACCESS_KEY }}

# Belt-and-braces for the concurrency guard: a run that was hard-killed
# (runner crash / forced cancel) can skip its `down` and leak a container
# holding the host port onto a REUSED runner. Tear any prior stack down
# before starting a fresh one. `|| true` so a clean runner (nothing to
# remove) is not an error.
- name: Clear any leaked containers from a prior run
run: docker compose -f local/docker-compose.${{ matrix.db }}.yml down -v --remove-orphans || true

- name: Start ${{ matrix.db }}
run: docker compose -f local/docker-compose.${{ matrix.db }}.yml up -d --wait

Expand Down
20 changes: 20 additions & 0 deletions .github/workflows/integration-supabase.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,18 @@ jobs:
integration:
name: Supabase v3 integration (db=${{ matrix.db }})
runs-on: blacksmith-4vcpu-ubuntu-2404
# Serialize every job that brings up the SAME docker-compose stack, so no two
# bind the same fixed host ports (55430 / 55433) on a shared runner at once —
# the "address already in use" flake. The group is keyed by the compose
# variant, NOT the ref: two different PRs contend for the same host port just
# as much as two pushes to one PR, so it must serialize across refs. It is the
# SAME key the Drizzle workflow's matching leg uses (`integration-live-db-<db>`),
# so those two workflows also queue against each other rather than colliding.
# `cancel-in-progress: false` queues (a live-DB job that shares one ZeroKMS
# workspace should finish, not be interrupted mid-run).
concurrency:
group: integration-live-db-${{ matrix.db }}
cancel-in-progress: false
# Fork PRs have no secrets. Skip cleanly rather than fail loudly on something
# the contributor cannot fix — `tests.yml` still gives them a green signal.
if: ${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository }}
Expand Down Expand Up @@ -106,6 +118,14 @@ jobs:
client-key: ${{ secrets.CS_CLIENT_KEY }}
client-access-key: ${{ secrets.CS_CLIENT_ACCESS_KEY }}

# Belt-and-braces for the concurrency guard: a run that was hard-killed
# (runner crash / forced cancel) can skip its `down` and leak a container
# holding the host port onto a REUSED runner. Tear any prior stack down
# before starting a fresh one. `|| true` so a clean runner (nothing to
# remove) is not an error.
- name: Clear any leaked containers from a prior run
run: docker compose -f local/docker-compose.${{ matrix.db }}.yml down -v --remove-orphans || true

- name: Start ${{ matrix.db }}
run: docker compose -f local/docker-compose.${{ matrix.db }}.yml up -d --wait

Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,10 @@ jobs:
# changesets/action writes a token .npmrc that shadows OIDC and
# every publish fails with E404 (see npm/cli#8976).
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Embeds the CLI's PostHog project key at build time (see
# packages/cli/tsup.config.ts). A repo *variable*, not a secret: the
# key is public and write-only (like a web SDK key). Unset until GA, so
# every release before it is flipped ships telemetry-dormant. This is
# the single go-live switch — set it with:
# gh variable set STASH_POSTHOG_KEY --repo cipherstash/stack --body '<phc_...>'
STASH_POSTHOG_KEY: ${{ vars.STASH_POSTHOG_KEY }}
11 changes: 11 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,17 @@ jobs:
- name: Type tests (test-kit — enforces v3 domain coverage)
run: pnpm --filter @cipherstash/test-kit run test:types

# The adapter packages carry their own `.test-d.ts` type-contract guards
# (the M1 client-surface / #622 erasure guards, the Supabase key-set
# gating). They moved here from `@cipherstash/stack` in the #627 split but
# were never wired into CI, so type-level regressions in them went
# undetected — run them explicitly.
- name: Type tests (stack-drizzle)
run: pnpm --filter @cipherstash/stack-drizzle run test:types

- name: Type tests (stack-supabase)
run: pnpm --filter @cipherstash/stack-supabase run test:types

- name: Lint — no hardcoded package-manager runners
run: pnpm run lint:runners

Expand Down
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,8 @@ Three rules to remember when editing CI or pnpm config:

## Key Concepts and APIs

- **Initialization**: `Encryption({ schemas })` returns an initialized `EncryptionClient`. Provide at least one `encryptedTable`.
- **Schema**: Define tables/columns with `encryptedTable` and `encryptedColumn` from `@cipherstash/stack/schema`. Add `.freeTextSearch().equality().orderAndRange()` to enable searchable encryption on PostgreSQL. Use `.searchableJson()` for encrypted JSONB queries. Use `encryptedField` for nested object encryption (DynamoDB).
- **Initialization**: `EncryptionV3({ schemas })` returns the typed EQL v3 client. (`Encryption({ schemas })` is the legacy v2 client.) Provide at least one `encryptedTable`.
- **Schema (EQL v3, the documented approach)**: Define tables/columns with `encryptedTable` and the `types.*` concrete-domain factories from `@cipherstash/stack/eql/v3` (`types.TextSearch`, `types.IntegerOrd`, `types.Json`, …) — each domain's query capabilities are fixed by its type; there are no chainable capability tuners. Build the client with `EncryptionV3` from `@cipherstash/stack/v3`. (Legacy EQL v2 — `Encryption` + `encryptedColumn(...).equality().freeTextSearch().orderAndRange().searchableJson()` from `@cipherstash/stack/schema` — remains for existing deployments and is what DynamoDB (`encryptedField`) still requires; see #657.)
- **Operations** (all return Result-like objects and support chaining `.withLockContext(lockContext)` and `.audit()` when applicable):
- `encrypt(plaintext, { table, column })`
- `decrypt(encryptedPayload)`
Expand Down
44 changes: 44 additions & 0 deletions e2e/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,49 @@
# @cipherstash/e2e

## 0.0.3-rc.0

### Patch Changes

- Updated dependencies [31ca318]
- Updated dependencies [31ca318]
- Updated dependencies [229ce59]
- Updated dependencies [c4787c0]
- Updated dependencies [66a0e02]
- Updated dependencies [cfd46ee]
- Updated dependencies [0b9b192]
- Updated dependencies [7eba32d]
- Updated dependencies [0ebf57e]
- Updated dependencies [d73a03c]
- Updated dependencies [89b903f]
- Updated dependencies [229ce59]
- Updated dependencies [82f2e69]
- Updated dependencies [50c0a9c]
- Updated dependencies [63ca540]
- Updated dependencies [5d23e80]
- Updated dependencies [1aa9a11]
- Updated dependencies [af2d04e]
- Updated dependencies [b8a3d20]
- Updated dependencies [a0f3b2c]
- Updated dependencies [f23f952]
- Updated dependencies [0b9b192]
- Updated dependencies [7c7dbca]
- Updated dependencies [5411a13]
- Updated dependencies [e25eb22]
- Updated dependencies [1a9d190]
- Updated dependencies [161f17b]
- Updated dependencies [e40c3da]
- Updated dependencies [58d7439]
- Updated dependencies [99f8b0a]
- Updated dependencies [fd33aad]
- Updated dependencies [8cd485d]
- Updated dependencies [9b65ae8]
- Updated dependencies [9c673bb]
- @cipherstash/stack@1.0.0-rc.0
- stash@1.0.0-rc.0
- @cipherstash/wizard@0.5.0-rc.0
- @cipherstash/drizzle@3.0.4-rc.0
- @cipherstash/protect@12.0.2-rc.0

## 0.0.2

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion e2e/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cipherstash/e2e",
"version": "0.0.2",
"version": "0.0.3-rc.0",
"private": true,
"description": "End-to-end tests that exercise built CipherStash binaries and cross-package behaviour.",
"type": "module",
Expand Down
Loading
Loading