fix: Close the five Codex findings on PR #437, one of which was mine … #1403
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: CI | |
| on: | |
| push: | |
| branches: [ main, master, develop, 'claude/**' ] | |
| pull_request: | |
| branches: [ main, master, develop ] | |
| # Least privilege (audit H47). Every job in this workflow reads the repository, | |
| # runs `npm ci` and uploads artifacts; none of it writes anything back to | |
| # GitHub. Declaring that here is what stops the workflow inheriting the | |
| # repository's *default* `GITHUB_TOKEN` — a setting that lives in repository | |
| # administration and is therefore invisible in the pull request that would | |
| # widen it. It matters most on this workflow and on `e2e.yml` precisely because | |
| # they are the two that run `npm ci`, i.e. the two that execute dependency | |
| # lifecycle scripts, in a repository that deploys to production from its own | |
| # workflows. | |
| permissions: | |
| contents: read | |
| jobs: | |
| lint-and-test: | |
| name: Lint, Type-Check & Test | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| node-version: [22.x, 26.x] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Setup Node.js ${{ matrix.node-version }} | |
| uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: ${{ matrix.node-version }} | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Run ESLint | |
| run: npm run lint | |
| continue-on-error: false | |
| - name: Run TypeScript type-check | |
| run: npm run type-check | |
| continue-on-error: false | |
| - name: Run tests | |
| run: npm test | |
| continue-on-error: false | |
| - name: Run tests with coverage | |
| run: npm run test:coverage | |
| continue-on-error: false | |
| - name: Upload coverage to artifacts | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| if: matrix.node-version == '22.x' | |
| with: | |
| name: coverage-report | |
| path: coverage/ | |
| retention-days: 30 | |
| build: | |
| name: Build Production | |
| runs-on: ubuntu-latest | |
| needs: lint-and-test | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: '22.x' | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Build application | |
| run: npm run build | |
| - name: Upload build artifacts | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: dist | |
| path: dist/ | |
| retention-days: 7 | |
| coverage-scope: | |
| name: Coverage (whole codebase) | |
| runs-on: ubuntu-latest | |
| # Runs in parallel with the matrix rather than as an extra step inside it, | |
| # so the honest repo-wide figure costs no wall-clock time on the critical | |
| # path and is measured once instead of once per Node version. | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: '22.x' | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: npm ci | |
| # `npm run test:coverage` gates the layer unit tests own (~45% of | |
| # production statements). This one measures everything and enforces a | |
| # floor, so the percentage the project quotes cannot drift back into | |
| # describing a subset while reading like a whole. | |
| - name: Measure coverage across the whole codebase | |
| run: npm run test:coverage:all | |
| security-audit: | |
| name: Security Audit | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: '22.x' | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Run npm audit (all dependencies) | |
| run: npm audit --audit-level=moderate | |
| continue-on-error: true | |
| - name: Check for high/critical vulnerabilities (production only) | |
| run: npm audit --omit=dev --audit-level=high | |
| # Stable aggregate gate. Branch protection should require THIS single check | |
| # ("CI Success") instead of the individual matrix legs. Because it depends on | |
| # every other CI job and re-derives its result from theirs, the Node matrix | |
| # (and the rest of this workflow) can change freely in this file without ever | |
| # touching the required-status-checks list in repo settings. `if: always()` | |
| # guarantees the job always reports a status, and it fails unless every | |
| # dependency succeeded (a failed/cancelled/skipped leg is not a success). | |
| ci-success: | |
| name: CI Success | |
| runs-on: ubuntu-latest | |
| if: always() | |
| needs: [lint-and-test, build, security-audit, coverage-scope] | |
| steps: | |
| - name: Verify all CI jobs succeeded | |
| env: | |
| LINT_AND_TEST_RESULT: ${{ needs.lint-and-test.result }} | |
| BUILD_RESULT: ${{ needs.build.result }} | |
| SECURITY_AUDIT_RESULT: ${{ needs.security-audit.result }} | |
| COVERAGE_SCOPE_RESULT: ${{ needs.coverage-scope.result }} | |
| run: | | |
| echo "Lint, Type-Check & Test: $LINT_AND_TEST_RESULT" | |
| echo "Build Production: $BUILD_RESULT" | |
| echo "Security Audit: $SECURITY_AUDIT_RESULT" | |
| echo "Coverage (whole codebase): $COVERAGE_SCOPE_RESULT" | |
| if [ "$LINT_AND_TEST_RESULT" != "success" ] \ | |
| || [ "$BUILD_RESULT" != "success" ] \ | |
| || [ "$SECURITY_AUDIT_RESULT" != "success" ] \ | |
| || [ "$COVERAGE_SCOPE_RESULT" != "success" ]; then | |
| echo "::error::One or more CI jobs did not succeed — failing the CI Success gate." | |
| exit 1 | |
| fi | |
| echo "All CI jobs succeeded." |