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/uvand.venvkeyed onpyproject.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_runuses 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.