Skip to content

CI/CD pipelines

Two workflows gate code quality; deploy listens only to CI on main.

Workflow Trigger Target time Gates
CI (Fast) Pull requests to main ~4–6 min ruff, format, mypy, pytest (-n auto, no coverage)
CI Push to main, workflow_dispatch ~5–8 min Same + invariant lints + coverage ≥50%
Security Scan Daily 06:30 UTC, Dockerfile/pyproject changes, manual ~7 min SBOM, Grype (critical fails scan job, not deploy)
Nightly Daily 06:00 UTC varies Golden backtest regression

Path filters

Docs-only and research-only changes skip CI and CI (Fast) via paths-ignore. Workflow-only edits under ignored paths also skip. Mixed commits (code + docs) still run full gates.

Self-hosted runner (qgtm-do)

  • Single job per workflow avoids double uv venv + serial queue on one machine.
  • setup-qgtm-python caches ~/.cache/uv and .venv keyed on pyproject.toml.
  • Tests use pytest -n auto --dist loadscope (pytest-xdist in dev extras).

Second runner (optional infra)

Register another runner with labels [self-hosted, qgtm-do-2] on the same or a second droplet, then set runs-on for Security Scan to qgtm-do-2 so SBOM Docker builds do not block CI on qgtm-do.

Branch protection

  • PRs: require CI (Fast) → job Lint, Types & Tests.
  • main: require CI → job Lint, Types, Tests & Coverage (deploy workflow_run uses this).

Local equivalents

make format          # fix format + ruff
make lint            # ruff + mypy (subset matches CI)
pytest -n auto -m "not slow and not paper" -q   # PR-equivalent tests
pytest --cov -m "not slow and not paper"        # main-equivalent

Deploy coupling

Deploy runs after CI succeeds on main (workflow_run). It does not wait for Security Scan; supply-chain checks run on schedule instead of blocking every merge.