ci: run the test suites (pytest + frontend) on every PR (#452) #1
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
| # The lint + test gate, run on every pull request and on push to `main`. | |
| # | |
| # WHY: until now the lint/test pipeline was local-only — `scripts/lint.sh` | |
| # + the pre-commit hooks, run on a contributor's laptop before the Merger | |
| # pulled a PR (the deliberate "no CI in pre-alpha" posture). With many | |
| # agents merging in parallel and only CodeQL gating server-side, test | |
| # regressions slipped onto `main` green (e.g. #401 broke | |
| # `tests/test_logentry.py`, undetected until a later full local run — | |
| # #451). This workflow makes the gate enforced server-side so a red suite | |
| # can't merge. Resolves #452; the no-CI revisit recorded in | |
| # `SECURITY.md` §8 / `docs/agents/decisions.md` / OQ-A-001. | |
| # | |
| # SOURCE OF TRUTH: the backend job runs `scripts/lint.sh` itself (with | |
| # `LINT_PY_ONLY=1`) so CI runs the *exact* gate a developer runs locally — | |
| # including the pre-commit security hooks (bandit + the house-rule pygrep | |
| # checks: no-objects-all, no-csrf-exempt, no-user-has-perm, | |
| # no-dar-api-from-pages, no-partial-tokens). The frontend job mirrors | |
| # #452's acceptance explicitly (typecheck + lint + test + build), because | |
| # `scripts/lint.sh`'s frontend block intentionally stops at lint and does | |
| # not run vitest or the Vite build. | |
| # | |
| # SECURITY POSTURE: | |
| # - Least-privilege: top-level `contents: read`; no job needs write. | |
| # - All third-party actions are pinned to a full commit SHA (a tag can | |
| # be moved, a SHA cannot) — consistent with codeql.yml / release.yml | |
| # and the supply-chain hardening in #331. | |
| # - This is a *gate*, not a publisher: it has no access to PyPI, no | |
| # `id-token`, and no stored secrets. | |
| # | |
| # NOTE: making these checks *required* (branch protection) is a separate | |
| # owner action in repo Settings → Branches — see #452 / #331. | |
| name: ci | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| permissions: | |
| contents: read | |
| # A newer push to the same ref supersedes an in-flight run — don't burn | |
| # minutes finishing a run whose commit is already stale. | |
| concurrency: | |
| group: ci-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| backend: | |
| name: Backend (lint + pytest) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| - name: Set up Python | |
| uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 | |
| with: | |
| python-version: "3.12" | |
| - name: Install Poetry + pre-commit | |
| run: pipx install poetry && pipx install pre-commit | |
| - name: Install dependencies (locked) | |
| run: poetry install --no-interaction | |
| # Runs the same gate as a local `LINT_PY_ONLY=1 bash scripts/lint.sh`: | |
| # pre-commit (bandit + the security pygrep hooks) then ruff / ruff | |
| # format / black / isort / flake8 / pylint -E / mypy (package, | |
| # blocking) / bandit / pytest (with coverage, per pyproject addopts). | |
| - name: Python gate (scripts/lint.sh) | |
| run: LINT_PY_ONLY=1 bash scripts/lint.sh | |
| frontend: | |
| name: Frontend (typecheck + lint + test + build) | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: frontend | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| - name: Set up pnpm | |
| uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0 | |
| with: | |
| version: 9 | |
| - name: Set up Node | |
| uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 | |
| with: | |
| node-version: 22 | |
| cache: pnpm | |
| cache-dependency-path: frontend/pnpm-lock.yaml | |
| - name: Install dependencies (frozen lockfile) | |
| run: pnpm install --frozen-lockfile | |
| - name: Typecheck (tsc --noEmit, every package) | |
| run: pnpm -r typecheck | |
| - name: Lint (eslint --max-warnings 0 + stylelint + dark-mode coverage) | |
| run: pnpm lint | |
| - name: Unit tests (vitest) | |
| run: pnpm test | |
| - name: Build (Vite bundle, every package) | |
| run: pnpm -r build |