Skip to content

fix(stack-drizzle): preserve per-column types in extractEncryptionSch… #351

fix(stack-drizzle): preserve per-column types in extractEncryptionSch…

fix(stack-drizzle): preserve per-column types in extractEncryptionSch… #351

name: Integration — Drizzle (EQL v3)
# Real ZeroKMS ciphertext against a real Postgres, on BOTH database variants.
#
# The Drizzle adapter talks straight to the database, so it does not need
# PostgREST — but it does need to work on managed Postgres, where the `postgres`
# role is not a superuser, the EQL install takes its self-skipping path, and the
# ORE domains cannot hold data. The Supabase compose file brings up PostgREST
# too; the Drizzle suites simply ignore it.
#
# Separate from `tests.yml` on purpose: these suites need CipherStash credentials
# and a database, and they THROW rather than skip when unconfigured. Keeping them
# out of the unit job is what lets `pnpm test` stay runnable with neither.
on:
push:
branches: [main]
paths:
- 'packages/stack/src/eql/v3/**'
- 'packages/stack-drizzle/**'
# Source layers the adapter's encoding/round-trip rests on: a break here
# (not just under src/eql/v3) can produce wrong rows, so trigger the live
# suite that would catch it.
- 'packages/stack/src/encryption/**'
- 'packages/stack/src/schema/**'
- 'packages/stack/integration/**'
# The WASM family suite (integration/wasm/**) exercises this entry:
- 'packages/stack/src/wasm-inline.ts'
# The DynamoDB adapter, and the entry/type modules the suites import
# directly. `integration/shared/v2-decrypt-compat` and its
# `integration/wasm/` twin (both selected below) are the repo's only live
# EQL v2 read coverage, and they exercise the DynamoDB legacy path, so a
# change here must run them. Pinned by
# scripts/__tests__/integration-workflow-paths.test.mjs.
- 'packages/stack/src/dynamodb/**'
- 'packages/stack/src/index.ts'
- 'packages/stack/src/types.ts'
# Those same v2 suites mint their fixtures by importing
# `@cipherstash/protect-ffi` directly. A native-module bump is the change
# most able to break v2 payload deserialization and it touches NO source
# directory, so without these two entries the only suites that would catch
# it never start. They are the files a bump actually edits: exact pins
# (`protect-ffi`, `@cipherstash/eql`) live in the package manifest,
# `catalog:` ones (`@cipherstash/auth`, which moves in lockstep with
# protect-ffi for the WASM entry) in the workspace catalog.
#
# `pnpm-lock.yaml` is deliberately NOT listed. It changes on roughly every
# dependency bump in the monorepo — far more often than either file here —
# and these are credentialed, database-backed jobs. Nothing is lost: a
# protect-ffi or auth version change cannot reach the lockfile without
# editing one of the two manifests below first.
- 'packages/stack/package.json'
- 'pnpm-workspace.yaml'
- 'packages/test-kit/**'
- 'packages/cli/src/installer/**'
- 'local/docker-compose.postgres.yml'
- 'local/docker-compose.supabase.yml'
- 'local/supabase-init.sql'
- '.github/workflows/integration-drizzle.yml'
- '.github/actions/integration-setup/**'
- '.github/actions/integration-db/**'
pull_request:
branches: ['**']
# Repeated verbatim: GitHub Actions does not support YAML anchors/aliases.
paths:
- 'packages/stack/src/eql/v3/**'
- 'packages/stack-drizzle/**'
# Source layers the adapter's encoding/round-trip rests on: a break here
# (not just under src/eql/v3) can produce wrong rows, so trigger the live
# suite that would catch it.
- 'packages/stack/src/encryption/**'
- 'packages/stack/src/schema/**'
- 'packages/stack/integration/**'
# The WASM family suite (integration/wasm/**) exercises this entry:
- 'packages/stack/src/wasm-inline.ts'
# The DynamoDB adapter, and the entry/type modules the suites import
# directly. `integration/shared/v2-decrypt-compat` and its
# `integration/wasm/` twin (both selected below) are the repo's only live
# EQL v2 read coverage, and they exercise the DynamoDB legacy path, so a
# change here must run them. Pinned by
# scripts/__tests__/integration-workflow-paths.test.mjs.
- 'packages/stack/src/dynamodb/**'
- 'packages/stack/src/index.ts'
- 'packages/stack/src/types.ts'
# Those same v2 suites mint their fixtures by importing
# `@cipherstash/protect-ffi` directly. A native-module bump is the change
# most able to break v2 payload deserialization and it touches NO source
# directory, so without these two entries the only suites that would catch
# it never start. They are the files a bump actually edits: exact pins
# (`protect-ffi`, `@cipherstash/eql`) live in the package manifest,
# `catalog:` ones (`@cipherstash/auth`, which moves in lockstep with
# protect-ffi for the WASM entry) in the workspace catalog.
#
# `pnpm-lock.yaml` is deliberately NOT listed. It changes on roughly every
# dependency bump in the monorepo — far more often than either file here —
# and these are credentialed, database-backed jobs. Nothing is lost: a
# protect-ffi or auth version change cannot reach the lockfile without
# editing one of the two manifests below first.
- 'packages/stack/package.json'
- 'pnpm-workspace.yaml'
- 'packages/test-kit/**'
- 'packages/cli/src/installer/**'
- 'local/docker-compose.postgres.yml'
- 'local/docker-compose.supabase.yml'
- 'local/supabase-init.sql'
- '.github/workflows/integration-drizzle.yml'
- '.github/actions/integration-setup/**'
- '.github/actions/integration-db/**'
jobs:
integration:
name: Drizzle v3 integration (db=${{ matrix.db }})
runs-on: blacksmith-4vcpu-ubuntu-2404
# No concurrency group: `integration-db` gives each job its own compose
# project and ephemeral host ports, so live-DB jobs no longer contend and do
# not need serialising. See that action for why the old
# `integration-live-db-<db>` group had to go (it cancelled a third
# contender rather than queueing it).
#
# 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 }}
strategy:
fail-fast: false
# Drizzle talks straight to Postgres, so it runs against BOTH databases.
# The Supabase variant is not a formality: its `postgres` role is not a
# superuser, so the EQL install takes its self-skipping path and the ORE
# domains become unusable. A suite that passes on a superuser database can
# still fail there.
# (The supabase compose file starts PostgREST anyway. Drizzle does not use
# it, but `integration-db` supplies the URL regardless, which lets the
# harness assert the `anon` path on the database it is actually running
# against.)
matrix:
db: [postgres, supabase]
env:
CS_WORKSPACE_CRN: ${{ vars.CS_WORKSPACE_CRN }}
CS_CLIENT_ID: ${{ vars.CS_CLIENT_ID }}
CS_CLIENT_KEY: ${{ secrets.CS_CLIENT_KEY }}
CS_CLIENT_ACCESS_KEY: ${{ secrets.CS_CLIENT_ACCESS_KEY }}
# The identity suites federate a freshly-minted Clerk M2M JWT into a CTS
# token. Only they read these; the other suites ignore them. If one is
# unset the relevant suite fails loudly (throw, never skip) rather than
# taking the whole job down, so they are NOT in the require-cs-secrets
# preflight. `_B` is a SECOND machine (a distinct `sub`) in the same Clerk
# instance, used only by the cross-identity test.
CLERK_MACHINE_TOKEN: ${{ secrets.CLERK_MACHINE_TOKEN }}
CLERK_MACHINE_TOKEN_B: ${{ secrets.CLERK_MACHINE_TOKEN_B }}
# EXPLICIT, never inferred. The variant decides whether EQL is installed
# with `--supabase` (and therefore whether the role grants are applied).
# Inferring it from `PGRST_URL` reported `postgres` for this job's Supabase
# cell and silently skipped the grants.
CS_IT_DB_VARIANT: ${{ matrix.db }}
# Scoped by directory, never by named file, so a renamed suite cannot
# silently drop from CI. `integration/shared/` holds the adapter-agnostic
# suites (harness, bloom, ope-term, the crypto/SQL matrices);
# `integration/identity/` holds the Clerk-federated lock-context suites
# (they need CLERK_MACHINE_TOKEN + a workspace with the Clerk issuer
# registered); the Supabase adapter suites are not run here — they have
# their own job.
# The Drizzle adapter suites (incl. the Clerk-federated lock-context one)
# now live in @cipherstash/stack-drizzle (run below via its own
# test:integration). This glob scopes what still lives in @cipherstash/stack:
# the adapter-agnostic `shared/` core suites, the model-path
# `identity/matrix-identity` suite, and the `wasm/` family suite (the
# `@cipherstash/stack/wasm-inline` adapter over the shared v3 matrix).
CS_IT_SUITE: >-
integration/shared/**/*.integration.test.ts,
integration/identity/**/*.integration.test.ts,
integration/wasm/**/*.integration.test.ts
steps:
- uses: actions/checkout@v6
- uses: ./.github/actions/integration-setup
# Fast pre-flight: fail in seconds if a secret was rotated or cleared,
# before the docker pull. The in-test `requireIntegrationEnv` is the
# correctness guarantee; this is the cheap one.
- name: Require CipherStash secrets
uses: ./.github/actions/require-cs-secrets
with:
workspace-crn: ${{ vars.CS_WORKSPACE_CRN }}
client-id: ${{ vars.CS_CLIENT_ID }}
client-key: ${{ secrets.CS_CLIENT_KEY }}
client-access-key: ${{ secrets.CS_CLIENT_ACCESS_KEY }}
# No pre-`up` cleanup step any more: the project name is unique per job, so
# a container leaked by a hard-killed prior run cannot hold this job's
# name or its (ephemeral) port. Blanket-pruning would now be actively
# unsafe — without the concurrency group, another job's stack may be live
# on this runner.
- name: Start ${{ matrix.db }}
id: db
uses: ./.github/actions/integration-db
with:
db: ${{ matrix.db }}
# `globalSetup` installs EQL v3 by shelling out to the real
# `stash eql install --eql-version 3`, so an installer regression fails
# here rather than hiding behind a test-only SQL apply.
#
# Step env, not a `.env` file: `dotenv/config` does not override an
# already-set `process.env`, so these win and no secret is written to disk.
# Through turbo, not a bare `pnpm --filter`: `test:integration` declares
# `dependsOn: ["^build", "build"]`, and the only build in this job is the
# setup action's `--filter stash`, which reaches these packages purely by
# coincidence of stash's own dependency graph. `--env-mode=loose` because
# turbo defaults to strict and would otherwise withhold the step env below
# from the test process (#787 review follow-up).
- name: Drizzle v3 integration suites
run: pnpm exec turbo run test:integration --filter @cipherstash/stack-drizzle --env-mode=loose
env:
DATABASE_URL: ${{ steps.db.outputs.database-url }}
PGRST_URL: ${{ steps.db.outputs.pgrest-url }}
# A second vitest invocation (stack's shared/ + identity suites live in a
# different package now). Its globalSetup calls the same EQL v3 install, but
# `isInstalled` short-circuits against the DB the first invocation already
# provisioned — a fast no-op check, not a second schema apply.
- name: Shared core + identity + wasm integration suites
run: pnpm exec turbo run test:integration --filter @cipherstash/stack --env-mode=loose
env:
DATABASE_URL: ${{ steps.db.outputs.database-url }}
PGRST_URL: ${{ steps.db.outputs.pgrest-url }}
# Guarded on the project being set: if the stack never came up, there is
# nothing to tear down and an unguarded `-p ""` would fail the job with a
# confusing error that masks the real one.
- name: Stop ${{ matrix.db }}
if: always() && env.CS_COMPOSE_PROJECT != ''
run: docker compose -p "$CS_COMPOSE_PROJECT" -f "$CS_COMPOSE_FILE" down -v