Repository navigation
391 lines (372 loc) · 21.8 KB
/
Copy pathintegration-protect-ffi.yml
File metadata and controls
391 lines (372 loc) · 21.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
name: Integration — protect-ffi (native + WASM)
# The `languages/typescript/packages/protect-ffi/integration-tests/` suite: 19 files of live
# encrypt / decrypt / lock-context / keyset / JSON-SteVec / Postgres /
# WASM-round-trip coverage against real ZeroKMS and a real Postgres.
#
# WHY THIS EXISTS: upstream (`cipherstash/protectjs-ffi`) ran this suite on
# EVERY pull request from `.github/workflows/test.yml` — `mise setup`, then
# `mise run test:integration:all`. The absorption copied the suite in intact and
# left that workflow behind under `languages/typescript/packages/protect-ffi/.github/`, where GitHub
# never looks: it reads workflows from the repository root alone. So from the
# day protect-ffi landed here, none of it ran, and a suite that never starts
# reads exactly like a suite that passes.
# `languages/typescript/packages/protect-ffi/src/integrationSuiteCi.test.ts` asserts this file (or a
# successor) still invokes it.
#
# WHERE THE SUITE'S DEPENDENCIES COME FROM. `integration-tests/` is a pnpm
# workspace member (CIP-3744) and installs with the repo's own
# `pnpm install --frozen-lockfile`, like everything else here. It used to sit
# outside the workspace with its own `package-lock.json` and an `npm ci`, pinning
# `@cipherstash/auth ^0.39.0`, `vitest ^3.1.3` and `@cipherstash/eql 3.0.2`
# against the repo catalog. That last pin was the point: `tests/postgres-v3.test.ts`
# queried SQL installed from the PUBLISHED EQL bundle while the payloads under
# test were emitted by the in-tree `eql-bindings`, so the two halves of EQL could
# disagree — and would have disagreed in a database, not in CI. `@cipherstash/eql`
# now resolves `workspace:^`, `@cipherstash/auth` `workspace:*`, and `vitest`
# `catalog:repo`.
#
# Separate from `tests.yml` on purpose, and separate from `tests-rust.yml`: this
# needs CipherStash credentials AND Docker, and the suites 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:
# The suite itself: tests, fixtures, its compose file, its `tasks.toml`
# and its manifest.
- 'languages/typescript/packages/protect-ffi/integration-tests/**'
# The suite's dependency versions, now that it is a pnpm workspace member:
# `vitest` and `typescript` reach it through
# `catalog:repo`, so a catalog bump changes what this job runs while
# editing no file under the suite. Same entry, same reason, as the other
# integration workflows — and like them, `pnpm-lock.yaml` is deliberately
# NOT listed: it moves on roughly every dependency bump in the monorepo,
# and a catalog version cannot reach it without this file changing first.
- 'pnpm-workspace.yaml'
# The EQL v3 SQL this suite installs into its database, which
# `tests/postgres-v3.test.ts` then queries. It came from a published
# tarball until the suite joined the workspace; it now resolves
# `workspace:^`, which is what makes it an input to this job rather than a
# pinned external. Not narrowed to `sql/` — `tasks.toml` reads the bundle
# through `@cipherstash/eql/sql`, i.e. through the package's tsup build,
# so the build config and its asset copier are inputs too.
- 'packages/eql/packages/eql/**'
# The Rust the suite round-trips every payload through, and the manifests
# that change what cargo builds without touching a .rs file. Matching the
# filter in tests-rust.yml.
- 'languages/typescript/packages/protect-ffi/crates/**'
- 'languages/typescript/packages/protect-ffi/Cargo.toml'
- 'languages/typescript/packages/protect-ffi/Cargo.lock'
# `src/**` is `lib/`, which is the module every test imports, plus
# `load.cts` — the loader that finds `index.node`.
- 'languages/typescript/packages/protect-ffi/src/**'
# `scripts/inline-wasm.mjs` emits `dist/wasm/protect_ffi_inline.js`, which
# `tests/wasm-round-trip.test.ts` loads at module-graph time. An edit
# there breaks that suite while touching no Rust and no `src/`.
- 'languages/typescript/packages/protect-ffi/scripts/**'
# The other half of the same build: `build:wasm` ends in `tsc -p
# tsconfig.wasm-errors.json`, and the inliner re-exports what it emits.
# `scripts/**` above does not reach a root tsconfig, so without this an
# edit to it rebuilds the bundle that suite loads and starts no job.
- 'languages/typescript/packages/protect-ffi/tsconfig.wasm-errors.json'
# package.json carries the build scripts and the exports map the suite
# resolves through; mise.toml carries the `tasks.toml` include, the
# toolchain pins, and the PG* connection env.
- 'languages/typescript/packages/protect-ffi/package.json'
- 'languages/typescript/packages/protect-ffi/mise.toml'
# Out of this package, and a real compile input: the cdylib crate carries
# `eql-bindings = { path = "../../../../../../packages/eql/crates/eql-bindings" }`, so the
# EQL wire types are linked into `index.node` AND into the wasm32 build
# this suite's `wasm-round-trip` test loads. Both cache keys in
# `.github/actions/build-ffi-binding` hash these, so an edit misses the
# cache and rebuilds the binding — without these entries it would be
# rebuilt and no suite that loads it would start. Pinned by
# scripts/__tests__/wasm-build-inputs-paths-filter.test.mjs.
- 'packages/eql/crates/**'
- 'packages/eql/Cargo.toml'
- '.github/workflows/integration-protect-ffi.yml'
- '.github/actions/build-ffi-binding/**'
- '.github/actions/build-auth-binding/**'
# `@cipherstash/auth` is a workspace package, and the SDK loads its napi
# module (and, for `wasm-inline`, its wasm) on import, so a change to what
# it ships arrives here rather than in `pnpm-workspace.yaml`. The paths of
# require-auth-npm-changeset.yml.
- 'packages/stack-auth/Cargo.toml'
- 'packages/stack-auth/src/**'
- 'languages/typescript/packages/auth/**'
- 'languages/typescript/packages/stack-auth-wasm/**'
- '.github/actions/require-cs-secrets/**'
pull_request:
branches: ['**']
# Repeated verbatim: GitHub Actions does not support YAML anchors/aliases.
# A one-sided edit disables the job on pull requests while leaving it green
# on `main` — the exact inversion of what you want.
paths:
- 'languages/typescript/packages/protect-ffi/integration-tests/**'
- 'pnpm-workspace.yaml'
- 'packages/eql/packages/eql/**'
- 'languages/typescript/packages/protect-ffi/crates/**'
- 'languages/typescript/packages/protect-ffi/Cargo.toml'
- 'languages/typescript/packages/protect-ffi/Cargo.lock'
- 'languages/typescript/packages/protect-ffi/src/**'
- 'languages/typescript/packages/protect-ffi/scripts/**'
- 'languages/typescript/packages/protect-ffi/tsconfig.wasm-errors.json'
- 'languages/typescript/packages/protect-ffi/package.json'
- 'languages/typescript/packages/protect-ffi/mise.toml'
# Out of this package, and a real compile input: the cdylib crate carries
# `eql-bindings = { path = "../../../../../../packages/eql/crates/eql-bindings" }`, so the
# EQL wire types are linked into `index.node` AND into the wasm32 build
# this suite's `wasm-round-trip` test loads. Both cache keys in
# `.github/actions/build-ffi-binding` hash these, so an edit misses the
# cache and rebuilds the binding — without these entries it would be
# rebuilt and no suite that loads it would start. Pinned by
# scripts/__tests__/wasm-build-inputs-paths-filter.test.mjs.
- 'packages/eql/crates/**'
- 'packages/eql/Cargo.toml'
- '.github/workflows/integration-protect-ffi.yml'
- '.github/actions/build-ffi-binding/**'
- '.github/actions/build-auth-binding/**'
# `@cipherstash/auth` is a workspace package, and the SDK loads its napi
# module (and, for `wasm-inline`, its wasm) on import, so a change to what
# it ships arrives here rather than in `pnpm-workspace.yaml`. The paths of
# require-auth-npm-changeset.yml.
- 'packages/stack-auth/Cargo.toml'
- 'packages/stack-auth/src/**'
- 'languages/typescript/packages/auth/**'
- 'languages/typescript/packages/stack-auth-wasm/**'
- '.github/actions/require-cs-secrets/**'
workflow_dispatch: {}
permissions:
contents: read
defaults:
run:
shell: bash
# Ref-scoped, so the only thing it cancels is a superseded commit on the same
# branch — which is what you want for a job that holds live credentials and a
# database for tens of minutes. This is NOT the cross-workflow job-level group
# that `.github/actions/integration-db` was written to remove: that one was
# shared by three workflows, and GitHub cancels a third contender outright
# rather than queueing it, which dropped whole signals.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
integration:
name: protect-ffi integration suite
runs-on: blacksmith-4vcpu-ubuntu-2404
# Generous, because a cold `build-ffi-binding` compiles the cipherstash
# client graph from scratch. It is also the backstop for `mise run
# start-db`, whose readiness loop retries `pg_isready` forever rather than
# giving up.
timeout-minutes: 45
# Everything runs EXCEPT a fork PR, which has no secrets: skip that cleanly
# rather than fail on something the contributor cannot fix — `tests.yml`
# still gives them a green signal.
#
# Stated as "not a fork PR", NOT as a list of the events allowed to run.
# The previous form (`github.event_name == 'push' || <same-repo check>`)
# made the `workflow_dispatch:` above decorative: on a manual dispatch the
# event name is neither `push` nor `pull_request`, and the payload carries
# no `pull_request` object to reach through, so both operands were false —
# the run was created, the only job was skipped, and the run reported
# success having executed nothing. This is the one credentialed suite with
# no other way to be re-run on demand: its `paths:` filter means an
# unrelated commit will not start it either. An allowlist of event names
# would break again the day someone adds `schedule:`.
# Enforced by scripts/__tests__/workflow-dispatch-job-conditions.test.mjs.
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
env:
# Same split as the other integration workflows: the workspace CRN and
# client id are repo `vars`, the two keys are `secrets`.
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 }}
#
# CS_ZEROKMS_HOST / CS_CTS_HOST are deliberately NOT set. Upstream's
# workflow pinned both to `ap-southeast-2`, which was right for the
# workspace whose credentials it held and is not portable: the client
# derives both hosts from the workspace CRN, so hardcoding a region here
# would silently point this repo's shared credentials at the wrong
# deployment. No other credentialed workflow in this repo sets them.
#
# The PG* block is how `tests/postgres*.test.ts` reach the database:
# they construct `new Client()` with no arguments, so libpq's environment
# is the entire connection string.
#
# 5436, not 5432. `integration-tests/docker-compose.yml` publishes
# `5436:5432`, so 5436 is the only port that reaches the container from
# the runner. Upstream's workflow said 5432 and worked anyway because
# every invocation went through mise, and `[env]` in
# languages/typescript/packages/protect-ffi/mise.toml (which says 5436) overrides an inherited
# value — verified, not assumed. The suite step below runs vitest
# directly, outside mise, so that safety net is gone and this block has to
# be right. The values here are identical to mise.toml's, which makes the
# override a no-op for the steps that do go through mise.
PGHOST: localhost
PGPORT: '5436'
PGDATABASE: cipherstash
PGUSER: cipherstash
PGPASSWORD: password
steps:
# Not `./.github/actions/integration-setup`, and the difference is
# deliberate: that action builds the `stash` CLI because the stack
# integration harness installs EQL v3 by shelling out to it. This suite
# reads the SQL straight out of the workspace `@cipherstash/eql` instead
# (see `eql:v3:install` in integration-tests/tasks.toml), so the CLI build
# would be minutes of runner time for nothing.
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: pnpm/action-setup@v6.1.0
with:
run_install: false
# Node 22, not the 24 `.github/actions/integration-setup` defaults to.
# This used to be an npm-version argument — the suite installed with
# `npm ci` against a lockfile npm 11 reads more strictly than npm 10 — and
# that argument died with the lockfile. What is left is the plainer one:
# 22 is `engines`' floor and what every other workflow in this repo pins,
# so it is the version this job's Rust binding and WASM artifacts are
# built and cached against everywhere else. 24 is covered for the JS
# surface by `tests.yml`'s matrix.
- uses: actions/setup-node@v6.5.0
with:
node-version: 22
cache: 'pnpm'
# node-pty's install hook falls back to `node-gyp rebuild` when no
# linux-x64 prebuild matches. pnpm/action-setup v6 no longer ships
# node-gyp on PATH, so install it explicitly.
#
# Nothing in this job uses a pty. node-pty is a dev-dependency of the
# `stash` CLI and the repo's one entry in `pnpm.onlyBuiltDependencies`, so
# its install hook fires on every workspace install — and the 1.1.0
# tarball ships prebuilds for darwin and win32 ONLY, making the fallback
# unconditional on a Linux runner. Without this step the job dies in
# `Install dependencies` with `sh: 1: node-gyp: not found`, before the
# secrets pre-flight, the binding build or a single test.
# Enforced by scripts/__tests__/workflow-node-gyp.test.mjs.
- name: Install node-gyp
run: npm install -g node-gyp
# The one install for this job, and it now covers the suite too: since
# CIP-3744 `integration-tests` is a pnpm workspace member, so this is what
# puts its `vitest`, `pg` and `dotenv` on disk and symlinks
# `@cipherstash/protect-ffi` and `@cipherstash/eql` to the packages in
# this tree. It is also required before the binding build —
# `build-ffi-binding` runs `pnpm --filter @cipherstash/protect-ffi run
# build` — and is what puts `@neon-rs/load` in
# languages/typescript/packages/protect-ffi/node_modules.
#
# `mise run setup` below runs the same command again, deliberately: the
# task has to stand on its own for a contributor running it locally. On
# the runner it is a no-op.
- name: Install dependencies
run: pnpm install --frozen-lockfile
# Fast pre-flight: fail in seconds if a secret was rotated or cleared,
# before the binding build and the docker pull. Ordered ahead of anything
# expensive on purpose — a pre-flight that runs after a cold Rust build is
# not a pre-flight. Enforced by
# scripts/__tests__/ffi-binding-step-order.test.mjs.
- 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 }}
# `wasm: 'true'` because `tests/wasm-round-trip.test.ts` and
# `tests/wasm-error-codes.test.ts` load `dist/wasm/protect_ffi_inline.js`
# from the built package, and both FAIL rather than skip when it is
# missing (they `existsSync` it and throw a build hint — the dynamic
# `import` is deliberate, so the artifact is needed by the time the suite
# runs rather than at collection). No other step in this job produces it.
#
# This replaces upstream's `mise run build:debug`, and the profile change
# is the point. The loader's fallback is `require('../index.node')` at the
# package root (see src/load.cts), which is exactly where this action's
# `neon dist` puts the RELEASE build — so a release binary satisfies the
# suite, and this action caches it on a content hash of the Rust inputs.
# Going through the mise task instead would recompile the client SDK in
# the debug profile on every run and overwrite the artifact the rest of
# CI already paid for. The action also proves both artifacts LOAD, so a
# restore that landed nothing fails here naming the file rather than as
# 19 unrelated encryption errors.
- name: Build the protect-ffi binding
uses: ./.github/actions/build-ffi-binding
with:
wasm: 'true'
- name: Build the @cipherstash/auth binding
uses: ./.github/actions/build-auth-binding
with:
wasm: 'true'
# `working_directory` is load-bearing, not tidiness. mise reads config
# from the current directory and its PARENTS, so an action running at the
# repo root never sees languages/typescript/packages/protect-ffi/mise.toml — it would install
# nothing and leave the config untrusted, and the later `mise run` fails
# with "Config files ... are not trusted", which reads as a toolchain
# problem rather than a trust one.
#
# A bare `install: true`, unlike `.github/actions/build-ffi-binding` which
# narrows to wasm-pack with `install_args`. Narrowing would not help here:
# `mise run` auto-installs any declared tool that is missing (verified),
# so it would only move the zig / cargo-zigbuild build into the middle of
# `mise run setup` instead of removing it. Installing up front is what
# mise-action's cache can absorb.
#
# SHA-pinned rather than `@v3`, same pin as
# .github/actions/build-ffi-binding and .github/workflows/tests-rust.yml:
# mise-action is a third-party trust dependency the absorption introduced,
# the tag is mutable, and this job holds live CipherStash credentials.
# Dependabot moves the pin and the comment together.
- uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db # v3.6.3
with:
install: true
working_directory: languages/typescript/packages/protect-ffi
# Must name runs-on's image; see scripts/__tests__/workflow-mise-cache-image.test.mjs
cache_key_prefix: mise-v1-ubuntu-2404
# One task, four things the suite cannot run without (see
# integration-tests/tasks.toml): `pnpm install --frozen-lockfile` (a
# no-op here — the step above already ran it), `docker compose up` for
# Postgres, the EQL **v2** bundle from a GitHub release, and EQL **v3**
# built from the in-tree `@cipherstash/eql`.
#
# BOTH EQL versions are required and nothing else in this repo installs
# v2: `tests/postgres.test.ts` creates `eql_v2_encrypted` columns and
# calls `eql_v2.add_encrypted_constraint`, while
# `tests/postgres-v3.test.ts` needs the `eql_v3_*` domains. The repo's
# `.github/actions/integration-db` supplies neither — it brings up a
# compose stack and hands back a URL, leaving the schema to each suite's
# own setup (stack's harness shells out to `stash eql install`).
#
# That is also why this job does NOT use `integration-db`: the two EQL
# install tasks pipe SQL through `docker exec -i protect-ffi-postgres`,
# hardwired to the container name in the suite's own compose file, which
# `integration-db`'s per-job project names deliberately do not produce.
# The fixed host port (5436) that comes with it cannot collide with the
# shared stacks, which publish on ephemeral ports.
- name: Provision the suite (Postgres, EQL v2 + v3)
working-directory: languages/typescript/packages/protect-ffi
run: mise run setup
# vitest directly rather than `mise run test:integration:all`, because
# that task's first two steps are the debug cargo build and a wasm-pack
# rebuild that `build-ffi-binding` has already done (above). What it runs
# after those is exactly this: vitest over the whole directory, INCLUDING
# `tests/lock-context.test.ts`. Its sibling `test:integration` excludes
# that file; upstream CI ran `:all` for a reason, and an invocation that
# inherits the exclusion silently drops the identity-aware coverage.
#
# `pnpm exec`, which resolves the catalog `vitest` the workspace install
# put in this package's `node_modules/.bin`. It was `npx` against the
# suite's own `package-lock.json` until CIP-3744 retired that lockfile.
- name: Integration suite (live ZeroKMS + Postgres, native and WASM)
working-directory: languages/typescript/packages/protect-ffi/integration-tests
run: pnpm exec vitest run
# `-v` drops the volume too. The runner is ephemeral, so this is not about
# reclaiming it: it is so a re-run on a warm runner cannot inherit a
# half-installed EQL schema and report a failure that only reproduces
# there. The default compose project name is the directory name, which is
# what `mise run start-db` brought the stack up under.
- name: Stop Postgres
if: always()
working-directory: languages/typescript/packages/protect-ffi/integration-tests
run: docker compose down -v