email-compliance ③ RE-CERTIFY: correct stale 0018 bootstrap __drizzle… #11
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
| # web-ci — the apps/web test gate (launch-gate G4-1). | |
| # | |
| # WHY THIS EXISTS: `main` auto-deploys to Vercel production money traffic and | |
| # NOTHING else runs the apps/web vitest suite in CI (the other 9 workflows are | |
| # codemod/publish/reconcile/template jobs). This gate runs tsc + lint + vitest | |
| # on every change headed for `main`, so a logic regression that breaks an | |
| # EXISTING test can no longer ship silently. It does NOT prove untested new code | |
| # correct — there is no coverage floor; pinning a regression test stays each | |
| # money chunk's job. | |
| # | |
| # SCOPE OF THE GUARANTEE — read before trusting a green check: | |
| # * The green check is a POST-HOC SIGNAL, not deploy-containment. A GitHub | |
| # Actions run races Vercel's git auto-deploy; the YAML alone does NOT stop a | |
| # bad commit from deploying. Real enforcement = a branch-protection rule on | |
| # `main` requiring the `web-ci` check (+ require-PR + include-administrators | |
| # + non-git-bypass lockdown). That is an OPERATOR action — see the | |
| # "OPERATOR-ONLY" block in §12 of | |
| # docs/tech-debt/ci-test-gate-handoff-2026-06-30.md. Do not read "web-ci is | |
| # green" as "the deploy was gated". | |
| # * Coverage boundary: this gate exercises packages/** only insofar as | |
| # apps/web's own tests reach into them. It does NOT run the packages' own | |
| # suites, and it builds web's deps but does NOT run a full `next build` | |
| # (a next-build failure is fail-safe anyway: Vercel rejects the deploy). | |
| name: web-ci | |
| # BOTH triggers, by design (LBD-1): the team direct-pushes seals straight to | |
| # `main` (zero merge commits in history), so a pull_request-only trigger would | |
| # never fire on the real flow — push:[main] is what makes the gate run today. | |
| # pull_request:[main] covers the PR-merge flow that branch protection will later | |
| # require. The push leg produces a stable `web-ci` check that enforcement pins. | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| # Least privilege: this gate reads code and runs tests — it needs nothing else. | |
| # A secret-free, read-only job cannot leak credentials or mutate the repo. | |
| permissions: | |
| contents: read | |
| # Cancel a superseded run when a newer commit lands on the same ref. Keyed on | |
| # github.ref (correct for a combined push+PR workflow; pull_request.number is | |
| # empty on push). On rapid back-to-back direct-push seals this drops the older | |
| # run's verdict — tolerable for a signal gate; branch protection moots it. | |
| concurrency: | |
| group: web-ci-${{ github.ref }} | |
| cancel-in-progress: true | |
| # NO `paths:` filter, deliberately. apps/web imports packages/**, so an | |
| # apps/web/**-only filter would let a regression in a shared package through. | |
| # A paths filter on a REQUIRED check is also a known GitHub footgun: a PR that | |
| # touches none of the filtered paths leaves the required check stuck pending and | |
| # blocks the merge. Cost: every push (incl. docs-only seals) runs full CI | |
| # (~2-4 min) — accepted; it is what keeps the future required check non-vacuous. | |
| jobs: | |
| web-ci: # <- job id. Keep `name:` equal to it so the CHECK-RUN name is exactly "web-ci". | |
| # The check-run name (= this job `name:`) is what branch protection must require | |
| # as its context. It is deliberately kept identical to the job id "web-ci": a | |
| # descriptive job name would make the check-run name the longer string, and the | |
| # §P cue's `context: web-ci` would then silently attach enforcement to nothing | |
| # (LBD-1). Do not change this name without updating the branch-protection cue. | |
| name: web-ci | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| # Root install (npm workspaces). The whole job is secret-free below. | |
| - name: Install dependencies | |
| run: npm ci | |
| # LBD-2(b): apps/web imports four @settlegrid/* packages that resolve ONLY | |
| # via their built dist/ (dist-only `exports`, no src/dev condition). dist/ | |
| # is gitignored with no prepare/postinstall hook, so `npm ci` never builds | |
| # it — bare tsc/vitest in a fresh checkout fail with 62x TS2307 + | |
| # "Failed to resolve @settlegrid/mcp". Build web's DEPS (^...) only — not | |
| # web itself — to stay inside the apps/web scope boundary (~6s, secret-free). | |
| - name: Build workspace deps (dist/ for tsc + vitest) | |
| run: npx turbo run build --filter=@settlegrid/web^... | |
| # LBD-2(a): apps/web/next-env.d.ts references .next/types/routes.d.ts, | |
| # which a fresh checkout lacks -> tsc TS6053. typegen regenerates it | |
| # (fast, secret-free, no build artifact needed). | |
| - name: Generate Next.js route types | |
| run: npx next typegen | |
| working-directory: apps/web | |
| - name: Typecheck | |
| run: npx tsc --noEmit | |
| working-directory: apps/web | |
| # Warnings do NOT fail the gate: the current output is warnings-only | |
| # (img-element, exhaustive-deps, one unused eslint-disable). Failing on | |
| # pre-existing warnings would red the gate on day one for no regression. | |
| - name: Lint | |
| run: npm run lint | |
| working-directory: apps/web | |
| # Green only with the LBD-3 skip-if-absent guards in place: several tests | |
| # readFileSync deliberately-gitignored internal docs that ENOENT in any | |
| # fresh clone; the guards make those blocks SKIP (visibly) instead of fail. | |
| - name: Test (vitest) | |
| run: npx vitest run | |
| working-directory: apps/web |