Skip to content

Merge pull request #5 from bugsee/fix/hyphenated-sensitive-keys #118

Merge pull request #5 from bugsee/fix/hyphenated-sensitive-keys

Merge pull request #5 from bugsee/fix/hyphenated-sensitive-keys #118

Workflow file for this run

name: CI
# Runs the full per-change gate on every push to main and every pull request:
# lint (biome) → typecheck (tsc via turbo) → dependency-cycle check (madge) →
# tests with the per-package coverage gate (vitest via turbo — each package
# enforces its own 100% line/fn/stmt + ≥90% branch threshold).
#
# ⚠️ BOTH JOBS RUN ON A SELF-HOSTED arm64 macOS RUNNER (hosted Actions minutes are
# exhausted — the appserver repo moved for the same reason). Three things follow, and
# every one of them has a counterpart below:
#
# 1. THE HOST OUTLIVES THE RUN. Leaked processes, held ports and stale directories
# survive into the NEXT run instead of dying with a VM, and show up there as
# flakiness. See the port-hygiene steps around the e2e suites.
# 2. THE HOST IS SHARED. A hung job holds the runner against every other repo
# queued behind it, so both jobs carry an explicit `timeout-minutes`.
# 3. IT IS FAST, and that is not purely good news. The old ubuntu runner was
# roughly 7-18x slower than a dev machine, which papered over races by never
# losing them; a runner at dev-machine speed can lose races the hosted one
# always won. (That is not hypothetical — it is exactly how a start-up race
# surfaced in the appserver deploy job after its move.) A test that newly fails
# here is more likely a real race than a bad runner.
#
# The platform changed with the move: linux-x64 → darwin-arm64. That is deliberate
# parity with how the gate is actually developed — `pnpm ci:local` has always run it
# on macOS — but it does mean CI no longer exercises Linux at all.
on:
push:
branches: [main]
pull_request:
# One in-flight run per ref; a new push cancels the previous run.
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
check:
name: Lint · Typecheck · Cycles · Coverage
runs-on: [self-hosted, macOS, ARM64]
# The runner is shared. Without a bound, a hung vitest worker holds it against
# every other repo's queue until someone notices by hand.
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
# pnpm first (setup-node's pnpm cache needs pnpm on PATH). Version comes from
# the root package.json `packageManager` field (pnpm@11.3.0).
- name: Install pnpm
uses: pnpm/action-setup@v4
# vitest 4 (rolldown) requires Node ≥ 20; the SDK targets Node ≥ 18 at runtime,
# but the toolchain runs on 22.
#
# NO `cache: pnpm` since the move. That option round-trips the pnpm store through
# GitHub's cache service, which only pays off when the machine is thrown away. Here
# the store lives at ~/Library/pnpm/store and survives every run — actions/checkout
# wipes node_modules (`git clean -ffdx`) but never touches the store — so the cache
# would be uploading and re-downloading something already on disk.
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Lint (biome)
run: pnpm lint
- name: Typecheck (tsc)
run: pnpm typecheck
- name: Dependency cycles (madge)
run: pnpm check:cycles
# Per-package coverage gate (turbo runs each package's own test:coverage,
# each enforcing 100% line/fn/stmt + ≥90% branch). NOT the root merged
# `pnpm test:coverage`, whose single aggregate threshold the scaffold-only
# stub packages would drag below 100%.
- name: Test + per-package coverage gate (vitest)
run: pnpm exec turbo run test:coverage
# Harness-owned unit tests (e.g. the bundle-assertion library's negative suite). These live in a
# package with no test:coverage script, so `turbo run test:coverage` skipped them silently — the same
# <NONEXISTENT> hole the e2e job exists to close.
- name: Harness unit tests (vitest)
run: pnpm exec turbo run test:unit
# End-to-end harnesses: the REAL SDK booted in real node/bun/deno processes against a
# mock collector, plus the real-framework suites (nuxt / sveltekit / astro / next).
#
# These existed but were OUTSIDE the gate until 2026-07-27: the `check` job runs
# `turbo run test:coverage`, and the four harness packages define only `test:e2e`, so
# turbo reported `<NONEXISTENT>` and skipped them silently. The adversarial review
# (docs/review/e2e-harnesses.md, SEV1 #1) identified that as the root cause of most of
# its ~90 SEV1 findings surviving ~100% unit coverage.
#
# Separate job so it runs alongside `check` and so its extra runtime setup
# (bun, deno) is isolated. Bun and Deno are installed deliberately: the harness SKIPS
# a runtime whose binary is absent (packages/instrumentation-tests/test/runtimes.ts),
# so without them CI would go green while testing strictly less.
#
# "Alongside" is now aspirational rather than guaranteed: two jobs only run at once if
# the pool has two free runners, and a single self-hosted runner serialises them. That
# costs wall-clock, not correctness, and adding a runner restores the parallelism
# without touching this file.
e2e:
name: E2E (node · bun · deno · real frameworks)
runs-on: [self-hosted, macOS, ARM64]
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
# No `cache: pnpm` — see the note in the `check` job.
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
- name: Setup Bun
uses: oven-sh/setup-bun@v2
- name: Setup Deno
uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- name: Install dependencies
run: pnpm install --frozen-lockfile
# Fail loudly if a runtime the harness expects is missing, rather than letting
# runtimes.ts silently skip it and report green.
#
# ⚠️ These three commands were never the thing that could go missing. What the node target
# actually needs is `tsx`, and the harness resolved it only from the WORKSPACE ROOT's
# node_modules/.bin — where pnpm does not put a package's own devDependency, and where
# `git clean -ffdx` removes the stale copy a developer's machine happens to have. So node
# resolved to `undefined` here for as long as this job has existed, its whole instrumentation
# suite was skipped, and the run went green on 133 tests where a developer ran 195. The real
# guard is now inside the harness (instrumentation.e2e.ts THROWS when node does not resolve);
# these version checks remain as a fast, readable failure for a genuinely missing runtime.
- name: Verify the runtime matrix is complete
run: |
node --version
bun --version
deno --version
# Playwright ships a downloader, not the browsers: `pnpm install` does not fetch them, so
# `browser.e2e.ts` fails with "Executable doesn't exist at .../chrome-headless-shell".
# Scoped to chromium — it is the only engine the browser harness drives. Like the pnpm store,
# ~/Library/Caches/ms-playwright survives on this self-hosted machine, so after the first run
# this step is a no-op rather than a download.
- name: Install Playwright's Chromium
run: pnpm --filter @bugsee/instrumentation-tests exec playwright install chromium
# ⚠️ SELF-HOSTED HYGIENE. The nuxt/sveltekit/astro harnesses each boot a real dev
# server on a FIXED port. On a hosted VM a leaked server died with the VM; here the
# machine persists, and this workflow sets `cancel-in-progress`, so a superseded
# run's dev-server child can outlive its parent and keep holding the port. The next
# run then fails with EADDRINUSE on a change that is perfectly fine — leftover state
# wearing the costume of a flaky suite. The script reads the ports out of the
# harnesses and fails if it can no longer find them, so moving a port cannot quietly
# disarm it.
- name: Free the harness ports
run: scripts/free-e2e-ports.sh
- name: E2E suites (turbo run test:e2e)
run: pnpm exec turbo run test:e2e
# `always()` so a CANCELLED or failed run cleans up after itself instead of leaving
# the mess for whoever runs next — which is the run that would get blamed for it.
- name: Release the harness ports
if: always()
run: scripts/free-e2e-ports.sh