> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stackra.figentra.com/llms.txt
> Use this file to discover all available pages before exploring further.

# CI

> One required check that runs pnpm verify, how to reproduce it locally with zero secrets, and what to do when CI is down.

> [!NOTE]
>
> Status: partly live. `.github/workflows/ci.yml` runs the `Quality Gate` job (toolchain from mise,
> actions pinned by SHA, `contents: read`), `release.yml` runs the release job and `linear-sync.yml`
> reconciles Linear. Not built: a workflow linter, a pin test, changeset status in the gate, and any
> deploy (the `Deploy Staging` job is a no-op until the deploy questions DP1–DP7 are decided). The
> code host is GitHub ([ADR-0012](/adr/0012-code-host-and-ci)).

## The model

* **One required check**, named `Quality Gate`, runs `pnpm verify` ([0119](/adr/0119-merge-policy-and-release-authority)). Branch protection on `main` requires it and nothing else.
* **Logic lives in pnpm scripts, not in workflow YAML.** The workflow installs the toolchain and calls a script. A step that exists only in YAML cannot be reproduced locally, and moving to another CI host stays cheap.
* **Local and remote run the same command.** If `pnpm verify` is green on a clean clone, the check is green. The exceptions are listed under [What green does not prove](#what-green-does-not-prove).
* **Verify needs zero secrets.** A pull request from a fork has no vault access, and a verify stage that needs one cannot check a fork's work at all.

## What `pnpm verify` runs

| Step | Command | Catches |
| - | - | - |
| Format | `turbo run format:check` | Unformatted code (oxfmt) |
| Lint | `turbo run lint` | oxlint findings, including the import rules that enforce package boundaries |
| Typecheck | `turbo run typecheck` | Type errors |
| Test | `turbo run test` | Failing or empty vitest suites |
| Build | `turbo run build` | A member that no longer builds |
| Validate | `pnpm validate` | Repository shape: required files, catalogs-only versions, tier tags, tsconfig presets, forbidden dependencies; also runs the `tools/scripts/*.test.mjs` suites |
| Boundaries | `pnpm boundaries` | A tier importing against the dependency direction (`turbo boundaries`) |
| Knip | `pnpm knip` | Unused files, exports and dependencies |

`pnpm verify:affected` runs the turbo part for members changed since `main` only. Use it while iterating; the required check runs the full `pnpm verify`.

Checks CI adds beyond `pnpm verify`:

| Check | Command | Why it is separate |
| - | - | - |
| Secret scan | `pnpm secrets:scan` (gitleaks over history) | Needs history, not a working tree |
| Commit messages | `pnpm exec commitlint --from <base> --to HEAD` | Hooks are advisory and can be skipped locally |
| Changeset status | `pnpm changeset:status` | A changed member without a changeset (target: chain it into `verify`) |
| Dependency review | the host's dependency-review action | New dependencies, advisories and licences on the pull request |

## Run it locally

```bash theme={"system"}
mise install            # node, pnpm and gitleaks at the pinned versions
pnpm install --frozen-lockfile
pnpm verify
```

To reproduce one failing step, run exactly that script: `pnpm turbo run test --filter=<package-name>`, `pnpm validate`, `pnpm knip`. The workflow's job log prints the command it ran; copy that line.

Nothing in `pnpm verify` reads a vault, a `.env` or a token. If a step seems to need a credential, that is a bug in the step (see [Secrets](/runbooks/secrets)).

## Rules for the workflow file

**1. Toolchain from mise.toml**

One setup step installs what `mise.toml` pins, asserts that the pnpm pin equals `packageManager`
in `package.json`, caches the pnpm store and runs `pnpm install --frozen-lockfile`. It installs
tools and decides nothing.

**2. Pin every action by SHA**

Each `uses:` carries a full commit SHA and a semver comment. [Renovate](/runbooks/renovate)
keeps them current. Add a workflow lint (zizmor or equivalent) to catch unpinned actions and
injection patterns.

**3. Least privilege**

Top-level `permissions` are empty or `contents: read`. A job asks for more only when it must,
and `id-token: write` only on a job that logs in to the vault.

**4. No secrets in verify**

The `verify` job has no vault identity and no stored secret. Only deploy and release jobs may
log in, through OIDC ([Secrets](/runbooks/secrets#ci-reads-ci-through-oidc-one-step-at-a-time)).

**5. Scope with affected, never skip**

A pull request may use `--affected` for turbo steps. `main` and release tags run everything.
`pnpm validate`, `pnpm boundaries` and `pnpm knip` never scope: they check repository-wide
facts.

## A check that cannot run is a failure

A step that is skipped because its tool is missing, its secret is absent or its input is empty must **fail** in CI, never pass. A local run may report a skip, with the reason; the CI run may not. Do not resolve a red gate by weakening it. Fix the thing the failure names, or change the gate through its own record (an ADR or the standard that created it), never in the same commit as the code that was failing it.

## What green does not prove

* **No service is started.** `pnpm verify` builds and tests; it does not run a deployed system.
* **The runner image is only proven for the jobs that use it.** A tool present on your machine and absent in CI fails in CI only. Keep tools in `mise.toml`.
* **The vault and deploy paths are unexercised by verify.** The first real deploy is the first time that code runs.

Say what you ran when you report. A green `verify` shows the repository conforms to its checks; it does not show the change is correct.

## When CI is down

Use this only when the host cannot run workflows at all (billing lock, outage, startup failure). It is a break-glass path for **gating**, not a way to skip the gate.

**1. Gate the exact commit locally**

From a clean worktree of the commit, never a checkout with local edits: `git fetch origin && git worktree add ../gate-<sha7> <sha>`, then `cd` into it and run `pnpm install --frozen-lockfile && pnpm verify && pnpm secrets:scan`. Record the full SHA and the result. Do not ship a commit whose gate you did not see pass.

**2. Ship it by hand, as the owner**

Follow [Deploys](/runbooks/deploys): the owner runs the deploy command for that commit, and a production deploy needs the owner's explicit approval. Never use `--no-verify` or skip a step to save time.

**3. Smoke test and record**

Run the deploy's smoke test. Append a row to the table below in the same pull request or a docs follow-up: date, environment, full SHA, who deployed, gate result, smoke result, reason.

**4. Reconcile**

When CI is back, push one normal commit to `main` so the pipeline's view matches what is running.

| Date | Env | SHA | By | Gate | Smoke | Reason |
| - | - | - | - | - | - | - |
| *none yet* | | | | | | |

## Enforcement status

| Rule | Check | State |
| - | - | - |
| `pnpm verify` is the single gate | `ci.yml` `Quality Gate` | Live |
| Hooks are advisory | `lefthook.yml` (check-only, no auto-fix) | Live |
| Conventional commits | commitlint in `commit-msg`; CI range check | Live |
| Secrets scan | gitleaks in `pre-commit`, `pnpm secrets:scan` | Live |
| Actions pinned by SHA, least privilege | Done by hand in every workflow; workflow lint (zizmor) and a pin test | Pins live, lint not built |

See also [Definition of done](/guides/definition-of-done), [Release](/runbooks/release) and [Testing](/standards/testing).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.