-
Notifications
You must be signed in to change notification settings - Fork 1
CI Pipeline
This page describes every GitHub Actions workflow in openrunic, what each one gates, and how the single required check is assembled. It is for contributors whose pull request is red and for anyone changing the pipeline itself.
Workflow files live in .github/workflows/. Helper scripts live in scripts/ci/. The shared toolchain step is a composite action at .github/actions/setup/action.yml.
| Workflow | File | Trigger | What it does |
|---|---|---|---|
| CI | .github/workflows/ci.yaml |
pull request, push to main/dev, merge group, manual |
Orchestrator. Calls the four reusable stages and publishes the CI Required aggregate. |
| Core | .github/workflows/_core.yaml |
called by CI | Detects affected workspaces, builds shared packages once, runs lint / type-check / build per workspace. |
| Test | .github/workflows/_test.yaml |
called by CI | Runs each affected suite sharded, merges shard coverage, enforces per-app coverage floors. |
| Migration | .github/workflows/_migration.yaml |
called by CI when @openrunic/database is affected |
Applies the migration history to a real Postgres 17 and checks for schema drift. |
| Sonar | .github/workflows/_sonar.yaml |
called by CI | Scan only. Consumes the merged coverage artifact. |
| PR Governance | .github/workflows/pr-governance.yml |
pull request opened/edited/synchronized/reopened | Validates the PR title and the commit messages in the PR range. |
| Secret Scan | .github/workflows/secret-scan.yml |
pull request, push to main/dev
|
Gitleaks over the commit range. |
| CodeQL Advanced | .github/workflows/codeql.yml |
push, pull request, weekly cron | Static analysis for javascript-typescript and actions. |
| Dependency Review | .github/workflows/dependency-review.yml |
pull request to main/dev
|
Fails on a newly introduced advisory of moderate severity or worse. |
| Supply Chain | .github/workflows/supply-chain.yml |
pull request, push, tags, release, manual | SBOM generation, vulnerability scan, licence policy check. |
| Scorecard | .github/workflows/scorecard.yml |
push to main, branch protection change, weekly cron |
OpenSSF Scorecard analysis, published to the public API. |
The Scorecard workflow is on the
feat/emr-appbranch and has not merged todevyet. Everything else in this table is ondev.
flowchart TD
A[ci.yaml] --> B[core: detect affected workspaces]
B --> C[core: build shared packages]
C --> D[core: static matrix<br/>lint, type-check, build]
B --> E{database affected?}
E -- yes --> F[migration: apply history<br/>to Postgres 17, drift check]
E -- no --> G[skipped]
C --> H[test: sharded vitest]
H --> I[test: merge coverage,<br/>enforce floors]
I --> J{coverage produced<br/>and Sonar enabled?}
J -- yes --> K[sonar: scan]
J -- no --> L[skipped]
D --> M[CI Required]
F --> M
G --> M
I --> M
K --> M
L --> M
CI Required is the aggregate job at the end of ci.yaml. Per ADR-0003, it is intended to be the only required status check on the branch rulesets, so the matrix can be reshaped without editing repository settings.
It fails closed on failure and on cancelled. It treats skipped as a pass, because three stages legitimately skip: migration only runs for database changes, test produces no coverage set for a packages-only change, and sonar is skipped while its kill switch is set.
The supply-chain.yml workflow has its own aggregate, Supply Chain Required, built on the same fail-closed pattern.
_core.yaml computes a base SHA with scripts/ci/compute-base-sha.sh, then asks turbo which workspaces are affected:
pnpm exec turbo run build test --filter="...[$BASE_SHA]" --dry=jsonThe result is turned into a job matrix by scripts/ci/affected-matrix.mjs. Detection widens to the whole repository whenever it cannot answer the question with confidence:
- No trustworthy base SHA, or the turbo dry run failed.
- A change to
pnpm-lock.yaml,pnpm-workspace.yaml, rootpackage.json, orturbo.json, because any of those can alter a workspace's dependency graph. - A change under
.github/workflows/,.github/actions/, orscripts/ci/, because none of those belong to a workspace and a pipeline-only change would otherwise test nothing.
_test.yaml expands the core matrix into test legs with scripts/ci/test-matrix.mjs. Each leg runs vitest directly rather than through the package script, so every leg carries identical flags:
pnpm --filter <workspace> exec vitest run \
--coverage --coverage.reporter=json \
--coverage.reportsDirectory=coverage \
--maxWorkers=2 --shard=<n>/<total>Shards emit istanbul JSON rather than lcov on purpose. Merging lcov text loses function coverage data, so scripts/ci/merge-coverage.mjs merges the coverage maps and generates lcov once from the merged result.
Coverage floors are declared in the COVERAGE_FLOORS map in _test.yaml. The values merged to dev today are:
| App | statements | branches | functions | lines |
|---|---|---|---|---|
web |
80 | 70 | 75 | 80 |
api |
85 | 75 | 85 | 85 |
The
feat/emr-appbranch raises these to 95 for statements, functions, and lines on both apps, with branches at 90 forweband 95 forapi. That change has not merged todev. Treat 95 as the bar the project is moving to and the numbers above as what CI enforces today.
Branch coverage is set below the other three on purpose. Both apps carry defensive code whose second arm is unreachable through the product, such as a fallback behind a bounds check or a component prop default React always supplies. A branch floor level with the rest would only be reachable by writing tests that call code no user path calls.
Shared packages run their suites without coverage and need no entry. The merge job fails when an app that reports coverage has no floor in the map. A missing floor is treated as a hole in the gate, not a default pass.
After merging, _test.yaml rewrites the SF: prefixes in lcov.info to be app-relative and runs scripts/ci/lcov-check.mjs --resolve to prove every path resolves against the source tree. Sonar treats unresolvable coverage paths as zero percent and passes its gate without complaint, so this check exists to catch that before the scan.
_migration.yaml starts a postgres:17-alpine service container and then, in order:
- On pull requests, diffs
packages/database/prisma/migrationsagainst the base and fails if anything other than an addition appears. Renames are caught because--no-renamesis passed. - Creates a shadow database.
- Runs
prisma validate. - Runs
prisma migrate deployagainst an empty database, which proves the whole history applies in order. - Runs
prisma migrate diff --from-migrations ./prisma/migrations --to-schema ./prisma/schema.prisma --scriptand fails if anything but comments and blank lines remain. That is the drift check: it catches a schema change with no migration, and a migration that never made it into the schema.
pr-governance.yml has two jobs.
Validate PR title reads the allowed types and scopes out of commitlint.config.cjs at runtime and passes them to the semantic-pull-request action with requireScope: true. Reading the config at runtime is what stops the PR gate and the commit gate from drifting apart.
Validate commit messages runs commitlint over the PR commit range. It is skipped for promotion PRs (dev into main), which replay already-merged history, and for Dependabot, whose generated commit bodies exceed the conventional line-length rules.
The Sonar stage scans only. It downloads the merged coverage artifact and does no install, no prisma generate, and no test run. Per-project configuration lives in apps/web/sonar-project.properties and apps/api/sonar-project.properties, keyed yosemitecrew_openrunic_Web and yosemitecrew_openrunic_Api.
The stage is skipped in several situations, all of them deliberate:
- The repository variable
DISABLE_SONARis set totrue. It is set today because the SonarCloud projects do not exist yet. - The pull request author is Dependabot, whose secret store does not carry the Sonar tokens.
- The event is neither a pull request nor a push to
main. The plan in use analyses each project's main branch only, so any other ref cannot publish.
Turning the stage on requires more than deleting the variable: create the projects, confirm each project's main branch is named main, add the SONAR_TOKEN_WEB and SONAR_TOKEN_API secrets, and only then remove the variable.
Every third-party action is pinned to a commit SHA with the version in a trailing comment. Checkout steps pass persist-credentials: false so the default token is not left in .git/config. Workflows declare permissions: contents: read at the top level and widen only on the specific jobs that need more.
pnpm verify # lint + type-check + test + build
pnpm --filter <workspace> lint
pnpm --filter <workspace> type-check
pnpm --filter <workspace> test
pnpm exec commitlint --from origin/dev --to HEAD --verbose
pnpm check:secretsThe husky hooks cover part of this already: pre-commit runs lint-staged (Prettier plus secretlint), commit-msg runs commitlint, and pre-push runs pnpm run lint && pnpm run type-check.
openrunic is an open-source operating system for human health. Pre-alpha: do not run it in production, and never put real patient data into it.
Repository · Licence (AGPL-3.0-only) · Security policy · Contributing · Code of conduct
Where this wiki and the repository disagree, the repository is right.