feat(guard): add Eve 0.34+ request/response approval support #4860
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Guard | |
| on: | |
| push: | |
| branches: | |
| - main | |
| - release-please--branches--main | |
| paths: | |
| - arcjet-guard/** | |
| - .github/workflows/guard.yml | |
| pull_request: | |
| paths: | |
| - arcjet-guard/** | |
| - .github/workflows/guard.yml | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| DO_NOT_TRACK: "1" | |
| defaults: | |
| run: | |
| working-directory: arcjet-guard | |
| jobs: | |
| build: | |
| name: "Build & Unit Test (Node ${{ matrix.node }})" | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| node: [22, 24, 26] | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 | |
| with: | |
| disable-sudo-and-containers: true | |
| egress-policy: audit | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Setup Node.js | |
| uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 | |
| with: | |
| node-version: ${{ matrix.node }} | |
| - name: Pin npm | |
| uses: ./.github/actions/pin-npm | |
| - name: Install dependencies | |
| run: npm install | |
| working-directory: ${{ github.workspace }} | |
| # Build the whole workspace so @arcjet/guard's workspace dependencies | |
| # (e.g. @arcjet/analyze) have their `dist/` output for guard's | |
| # source tests and type checking. | |
| - name: Build | |
| run: npm run build | |
| working-directory: ${{ github.workspace }} | |
| - name: Unit tests (with coverage) | |
| run: npm run test-unit | |
| # The `eve` peer range advertises a floor, and development happens against | |
| # the newest release — so nothing would notice if the namespace quietly | |
| # started needing something the floor does not have. This pins the lowest | |
| # version the range claims and runs the namespace against it. | |
| # | |
| # The install itself is half the check: npm resolves the peer range while | |
| # installing, so a floor below what package.json advertises fails here | |
| # rather than in a user's project. | |
| # | |
| # Runs before the eve-absent step because that one deletes eve outright. | |
| - name: Unit tests at the lowest supported eve | |
| run: | | |
| floor=$(node -e " | |
| const r = require('./arcjet-guard/package.json').peerDependencies.eve; | |
| const m = /^>=\s*([0-9]+\.[0-9]+\.[0-9]+)/.exec(r); | |
| if (!m) { console.error('cannot read a floor from peer range: ' + r); process.exit(1) } | |
| process.stdout.write(m[1]); | |
| ") | |
| echo "lowest supported eve: $floor" | |
| npm install "eve@$floor" --no-save --workspace @arcjet/guard | |
| # Assert what the namespace itself resolves, not merely what landed on | |
| # disk: npm may nest the install, and eve's exports map does not expose | |
| # ./package.json, so walk up from a subpath it does export. | |
| node -e " | |
| const { createRequire } = require('node:module'); | |
| const { dirname, join } = require('node:path'); | |
| const { existsSync } = require('node:fs'); | |
| const resolver = createRequire(join(process.cwd(), 'arcjet-guard/src/vercel-eve/v0/index.ts')); | |
| let dir = dirname(resolver.resolve('eve/tools')); | |
| while (!existsSync(join(dir, 'package.json'))) dir = dirname(dir); | |
| const found = require(join(dir, 'package.json')).version; | |
| if (found !== process.argv[1]) { | |
| console.error('namespace resolves eve ' + found + ', expected the floor ' + process.argv[1]); | |
| process.exit(1); | |
| } | |
| console.log('namespace resolves eve ' + found); | |
| " "$floor" | |
| node --test 'arcjet-guard/src/vercel-eve/**/*.test.ts' | |
| working-directory: ${{ github.workspace }} | |
| # The claude-agent-sdk namespace imports the SDK for types only. A static | |
| # scan proves the imports are written `import type`; this proves the | |
| # consequence, catching a build step that re-emits one as a value import. | |
| # | |
| # Runs before the eve-absent step because that one deletes a different | |
| # optional peer. Typecheck legitimately fails without the SDK. | |
| - name: Unit tests with claude-agent-sdk absent | |
| run: | | |
| rm -rf node_modules/@anthropic-ai/claude-agent-sdk arcjet-guard/node_modules/@anthropic-ai/claude-agent-sdk | |
| node -e "try { require.resolve('@anthropic-ai/claude-agent-sdk', { paths: ['arcjet-guard/src/claude-agent-sdk/v0'] }); console.error('claude-agent-sdk still resolves'); process.exit(1) } catch (error) { if (error.code !== 'MODULE_NOT_FOUND') throw error }" | |
| count=$(find arcjet-guard/src/claude-agent-sdk -name '*.test.ts' | wc -l) | |
| test "$count" -ge 5 || { echo "only $count claude-agent-sdk test files matched; the glob has gone stale" >&2; exit 1; } | |
| node --test 'arcjet-guard/src/claude-agent-sdk/**/*.test.ts' | |
| working-directory: ${{ github.workspace }} | |
| # The langgraph namespace imports @langchain/langgraph and | |
| # @langchain/core for types only. A static scan proves the imports are | |
| # written `import type`; this proves the consequence, catching a build | |
| # step that re-emits one as a value import. | |
| # | |
| # Only `src/langgraph` is globbed. The suite that exercises the real | |
| # ToolNode value-imports both peers, so it lives in `test/langgraph` | |
| # and runs in the unit-test step above, where the peers are installed. | |
| # | |
| # Runs before the eve-absent step because that one deletes a different | |
| # optional peer. Typecheck legitimately fails without the peers. | |
| - name: Unit tests with langgraph absent | |
| run: | | |
| rm -rf node_modules/@langchain/langgraph node_modules/@langchain/core arcjet-guard/node_modules/@langchain/langgraph arcjet-guard/node_modules/@langchain/core | |
| node -e "try { require.resolve('@langchain/langgraph', { paths: ['arcjet-guard/src/langgraph/v1'] }); console.error('langgraph still resolves'); process.exit(1) } catch (error) { if (error.code !== 'MODULE_NOT_FOUND') throw error }" | |
| node -e "try { require.resolve('@langchain/core', { paths: ['arcjet-guard/src/langgraph/v1'] }); console.error('langchain core still resolves'); process.exit(1) } catch (error) { if (error.code !== 'MODULE_NOT_FOUND') throw error }" | |
| count=$(find arcjet-guard/src/langgraph -name '*.test.ts' | wc -l) | |
| test "$count" -ge 5 || { echo "only $count langgraph test files matched; the glob has gone stale" >&2; exit 1; } | |
| node --test 'arcjet-guard/src/langgraph/**/*.test.ts' | |
| working-directory: ${{ github.workspace }} | |
| # The mastra namespace imports @mastra/core for types only. A static scan | |
| # proves the imports are written `import type`; this proves the | |
| # consequence, catching a build step that re-emits one as a value import. | |
| # | |
| # Runs before the eve-absent step because that one deletes a different | |
| # optional peer. Typecheck legitimately fails without @mastra/core. | |
| - name: Unit tests with mastra absent | |
| run: | | |
| rm -rf node_modules/@mastra/core arcjet-guard/node_modules/@mastra/core | |
| node -e "try { require.resolve('@mastra/core', { paths: ['arcjet-guard/src/mastra/v1'] }); console.error('mastra still resolves'); process.exit(1) } catch (error) { if (error.code !== 'MODULE_NOT_FOUND') throw error }" | |
| count=$(find arcjet-guard/src/mastra -name '*.test.ts' | wc -l) | |
| test "$count" -ge 5 || { echo "only $count mastra test files matched; the glob has gone stale" >&2; exit 1; } | |
| node --test 'arcjet-guard/src/mastra/**/*.test.ts' | |
| working-directory: ${{ github.workspace }} | |
| # The vercel-eve namespace imports eve for types only, which is what lets | |
| # guard support Node 22 at all — eve declares engines.node ">=24". A static | |
| # scan proves the imports are written `import type`; this proves the | |
| # consequence, catching a build step that re-emits one as a value import. | |
| # | |
| # Runs on every leg of the matrix rather than just Node 22, because the | |
| # criterion is about all three. Runs last because it deletes a | |
| # devDependency: the typecheck legitimately fails without eve, since types | |
| # are a build-time dependency. | |
| - name: Unit tests with eve absent | |
| run: | | |
| rm -rf node_modules/eve arcjet-guard/node_modules/eve | |
| node -e "try { require.resolve('eve', { paths: ['arcjet-guard/src/vercel-eve/v0'] }); console.error('eve still resolves'); process.exit(1) } catch (error) { if (error.code !== 'MODULE_NOT_FOUND') throw error }" | |
| count=$(find arcjet-guard/src/vercel-eve -name '*.test.ts' | wc -l) | |
| test "$count" -ge 5 || { echo "only $count vercel-eve test files matched; the glob has gone stale" >&2; exit 1; } | |
| node --test 'arcjet-guard/src/vercel-eve/**/*.test.ts' | |
| working-directory: ${{ github.workspace }} | |
| lint: | |
| name: Lint & Typecheck | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 | |
| with: | |
| disable-sudo-and-containers: true | |
| egress-policy: audit | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Setup Node.js | |
| uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 | |
| with: | |
| node-version: 24 | |
| - name: Pin npm | |
| uses: ./.github/actions/pin-npm | |
| - name: Install dependencies | |
| run: npm install | |
| working-directory: ${{ github.workspace }} | |
| # Build the whole workspace so @arcjet/guard's workspace dependencies | |
| # (e.g. @arcjet/analyze) have their `dist/` output for guard's | |
| # source tests and type checking. | |
| - name: Build | |
| run: npm run build | |
| working-directory: ${{ github.workspace }} | |
| - name: Lint (oxlint) | |
| run: npm run lint | |
| - name: Type check (tsgo) | |
| run: npm run typecheck | |
| runtime: | |
| name: "Runtime (${{ matrix.runtime }}${{ matrix.node-version && format(' node{0}', matrix.node-version) || matrix.deno-version && format(' {0}', matrix.deno-version) || matrix.bun-version && format(' {0}', matrix.bun-version) || '' }})" | |
| runs-on: ubuntu-latest | |
| needs: build | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - runtime: node | |
| script: test-runtime-node | |
| node-version: 22 | |
| - runtime: node | |
| script: test-runtime-node | |
| node-version: 24 | |
| - runtime: node | |
| script: test-runtime-node | |
| node-version: 26 | |
| - runtime: fetch | |
| script: test-runtime-fetch | |
| node-version: 22 | |
| - runtime: fetch | |
| script: test-runtime-fetch | |
| node-version: 24 | |
| - runtime: fetch | |
| script: test-runtime-fetch | |
| node-version: 26 | |
| - runtime: cloudflare | |
| script: test-runtime-cloudflare | |
| - runtime: bun | |
| script: test-runtime-bun | |
| bun-version: 1.3.0 | |
| - runtime: bun | |
| script: test-runtime-bun | |
| bun-version: latest | |
| - runtime: deno | |
| script: test-runtime-deno | |
| deno-version: lts | |
| - runtime: deno | |
| script: test-runtime-deno | |
| deno-version: latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 | |
| with: | |
| disable-sudo-and-containers: true | |
| egress-policy: audit | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Setup Node.js | |
| uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 | |
| with: | |
| node-version: ${{ matrix.node-version || '24' }} | |
| - name: Pin npm | |
| uses: ./.github/actions/pin-npm | |
| - name: Setup Bun | |
| if: matrix.runtime == 'bun' | |
| uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2 | |
| with: | |
| bun-version: ${{ matrix.bun-version }} | |
| - name: Setup Deno | |
| if: matrix.runtime == 'deno' | |
| uses: denoland/setup-deno@e95548e56dfa95d4e1a28d6f422fafe75c4c26fb # v2.0.3 | |
| with: | |
| deno-version: ${{ matrix.deno-version }} | |
| - name: Install dependencies | |
| run: npm install | |
| working-directory: ${{ github.workspace }} | |
| # Build the whole workspace so guard's workspace dependencies are | |
| # available to the runtime tests. | |
| - name: Build | |
| run: npm run build | |
| working-directory: ${{ github.workspace }} | |
| - name: Runtime test (${{ matrix.runtime }}) | |
| run: npm run ${{ matrix.script }} |