|
| 1 | +# CI — initial scaffold landed by BUG-T-001 (Tier 1 / Day 1 PM) from audit/BUGS.md. |
| 2 | +# |
| 3 | +# Closes BLOCK findings T-001 (no CI pipeline) and prepares the ground for S-004 |
| 4 | +# (secret-scan gate, Tier 2 / Day 5) which appends to this file as another step. |
| 5 | +# See audit/REPORT.md "Top fixes" + audit/BUGS.md (BUG-T-001 acceptance criteria: |
| 6 | +# .github/workflows/ci.yml exists; status check is required for merge). |
| 7 | +# |
| 8 | +# Triggers: PRs targeting main, plus pushes to main (audit gate on direct merges). |
| 9 | +# |
| 10 | +# Job design rationale: |
| 11 | +# - tests-unit pytest tests/ (10 baseline pass per audit CMD-029). |
| 12 | +# Required gate from day one. |
| 13 | +# - tests-mocks pytest tests/mocks/ (1203 pass + 7 pre-existing |
| 14 | +# failures, see HOLD finding T-002 + audit/BUGS.md). |
| 15 | +# Known-broken APIs excluded via -k expression so this |
| 16 | +# job is required without first having to fix T-002. |
| 17 | +# Stays in lockstep: remove an exclusion ONLY after the |
| 18 | +# underlying API mock is fixed. |
| 19 | +# - lint-ruff ruff check (26 baseline issues per audit CMD-011). |
| 20 | +# Initially non-blocking (continue-on-error) so CI lands |
| 21 | +# green today; tighten to required after baseline is |
| 22 | +# zero per Q-003. |
| 23 | +# - sast-bandit bandit -r . (301 baseline issues per audit CMD-012). |
| 24 | +# Same staging: --severity-level medium surfaces the |
| 25 | +# 24 MEDIUMs first; non-blocking until those are |
| 26 | +# triaged. |
| 27 | +# - deps-cve pip-audit -r requirements.txt. Now unblocked by |
| 28 | +# BUG-D-001 fix (click 8.1.8 → 8.2.1). Surfaces the |
| 29 | +# 27 CVE list per finding D-002. Non-blocking until |
| 30 | +# the new D-006+ tickets are addressed (Tier 2/Day 3). |
| 31 | +# |
| 32 | +# All jobs use Python 3.12 to match the production deployment target |
| 33 | +# (environment/*-api/Dockerfile pins FROM python:3.12-slim — see S-002). |
| 34 | +# Do NOT raise to 3.14 (the audit-host version) until the Dockerfile base |
| 35 | +# bumps in lockstep, otherwise CI passes against an unshipped runtime. |
| 36 | + |
| 37 | +name: CI |
| 38 | + |
| 39 | +on: |
| 40 | + pull_request: |
| 41 | + branches: [main] |
| 42 | + push: |
| 43 | + branches: [main] |
| 44 | + |
| 45 | +# Cancel in-progress runs when a new commit lands on the same ref — |
| 46 | +# avoids wasting compute on stale PR pushes. |
| 47 | +concurrency: |
| 48 | + group: ci-${{ github.workflow }}-${{ github.ref }} |
| 49 | + cancel-in-progress: true |
| 50 | + |
| 51 | +permissions: |
| 52 | + contents: read |
| 53 | + |
| 54 | +jobs: |
| 55 | + |
| 56 | + tests-unit: |
| 57 | + name: pytest tests/ (host-only) |
| 58 | + runs-on: ubuntu-latest |
| 59 | + timeout-minutes: 10 |
| 60 | + steps: |
| 61 | + - uses: actions/checkout@v4 |
| 62 | + |
| 63 | + - name: Set up Python 3.12 |
| 64 | + uses: actions/setup-python@v5 |
| 65 | + with: |
| 66 | + python-version: "3.12" |
| 67 | + cache: pip |
| 68 | + |
| 69 | + - name: Install runtime requirements |
| 70 | + run: | |
| 71 | + python -m pip install --upgrade pip |
| 72 | + # tests/ has no docker dependency; install the root requirements only. |
| 73 | + # Per audit/BUGS.md BUG-D-001, click is now 8.2.1 which resolves the |
| 74 | + # pip-audit ResolutionImpossible blocker. |
| 75 | + pip install -r requirements.txt |
| 76 | + pip install pytest |
| 77 | +
|
| 78 | + - name: Run pytest tests/ (ignore tests/mocks) |
| 79 | + run: pytest tests/ -q --ignore=tests/mocks |
| 80 | + |
| 81 | + tests-mocks: |
| 82 | + name: pytest tests/mocks/ (excluding known-broken APIs) |
| 83 | + runs-on: ubuntu-latest |
| 84 | + timeout-minutes: 15 |
| 85 | + steps: |
| 86 | + - uses: actions/checkout@v4 |
| 87 | + |
| 88 | + - name: Set up Python 3.12 |
| 89 | + uses: actions/setup-python@v5 |
| 90 | + with: |
| 91 | + python-version: "3.12" |
| 92 | + cache: pip |
| 93 | + |
| 94 | + - name: Install runtime requirements |
| 95 | + run: | |
| 96 | + python -m pip install --upgrade pip |
| 97 | + pip install -r requirements.txt |
| 98 | + pip install pytest fastapi httpx pyyaml |
| 99 | +
|
| 100 | + # The -k expression below excludes the 7 pre-existing failing mocks |
| 101 | + # tracked under HOLD finding T-002 (see audit/findings.json) so the |
| 102 | + # CI gate is required without first needing to fix T-002. Each |
| 103 | + # exclusion MUST be paired with an open ticket; remove the entry the |
| 104 | + # moment the underlying API mock is fixed. |
| 105 | + - name: Run pytest tests/mocks/ (skip T-002 baseline failures) |
| 106 | + run: | |
| 107 | + pytest tests/mocks/ -q \ |
| 108 | + -k "not (contentful or doordash or dropbox or google_analytics or pagerduty or pinterest or spotify)" |
| 109 | +
|
| 110 | + lint-ruff: |
| 111 | + name: ruff lint (non-blocking baseline) |
| 112 | + runs-on: ubuntu-latest |
| 113 | + timeout-minutes: 5 |
| 114 | + # NON-BLOCKING per audit findings.json Q-003 — there are 26 baseline |
| 115 | + # ruff issues today. Flip to required (`continue-on-error: false`) after |
| 116 | + # the baseline is at zero. Tracked under Q-003. |
| 117 | + continue-on-error: true |
| 118 | + steps: |
| 119 | + - uses: actions/checkout@v4 |
| 120 | + |
| 121 | + - name: Set up Python 3.12 |
| 122 | + uses: actions/setup-python@v5 |
| 123 | + with: |
| 124 | + python-version: "3.12" |
| 125 | + cache: pip |
| 126 | + |
| 127 | + - name: Install ruff |
| 128 | + run: pip install ruff==0.15.16 |
| 129 | + |
| 130 | + - name: ruff check (report-only) |
| 131 | + run: ruff check . --output-format=github |
| 132 | + |
| 133 | + sast-bandit: |
| 134 | + name: bandit SAST (non-blocking baseline) |
| 135 | + runs-on: ubuntu-latest |
| 136 | + timeout-minutes: 5 |
| 137 | + # NON-BLOCKING per audit finding bandit baseline (301 issues, 24 MEDIUM, |
| 138 | + # 277 LOW per CMD-012). --severity-level medium surfaces the 24 MEDIUMs |
| 139 | + # first; flip to required after those are triaged. |
| 140 | + continue-on-error: true |
| 141 | + steps: |
| 142 | + - uses: actions/checkout@v4 |
| 143 | + |
| 144 | + - name: Set up Python 3.12 |
| 145 | + uses: actions/setup-python@v5 |
| 146 | + with: |
| 147 | + python-version: "3.12" |
| 148 | + cache: pip |
| 149 | + |
| 150 | + - name: Install bandit |
| 151 | + run: pip install bandit==1.9.4 |
| 152 | + |
| 153 | + - name: bandit scan (medium+) |
| 154 | + # Scan src/, eval/, scripts/, environment/_mutable_store.py and |
| 155 | + # environment/admin_plane.py — the per-API server.py files are |
| 156 | + # intentional mock surfaces and would flood the report; their |
| 157 | + # subprocess/tmp/etc. risk is covered by C-001 (false positives in |
| 158 | + # container context) per audit findings.json. |
| 159 | + run: | |
| 160 | + bandit -r src/ eval/ scripts/ \ |
| 161 | + environment/_mutable_store.py \ |
| 162 | + environment/admin_plane.py \ |
| 163 | + --severity-level medium \ |
| 164 | + --confidence-level medium |
| 165 | +
|
| 166 | + deps-cve: |
| 167 | + name: pip-audit CVE scan (non-blocking baseline) |
| 168 | + runs-on: ubuntu-latest |
| 169 | + timeout-minutes: 5 |
| 170 | + # NON-BLOCKING until BUG-D-002 follow-up tickets (D-006+) bump the 27 |
| 171 | + # CVE-affected packages identified by the cascade unblock from |
| 172 | + # BUG-D-001. Tracked in audit/BUGS.md and the Tier 2 / Day 3 work item. |
| 173 | + continue-on-error: true |
| 174 | + steps: |
| 175 | + - uses: actions/checkout@v4 |
| 176 | + |
| 177 | + - name: Set up Python 3.12 |
| 178 | + uses: actions/setup-python@v5 |
| 179 | + with: |
| 180 | + python-version: "3.12" |
| 181 | + cache: pip |
| 182 | + |
| 183 | + - name: Install pip-audit |
| 184 | + run: pip install pip-audit==2.10.0 |
| 185 | + |
| 186 | + - name: pip-audit requirements.txt |
| 187 | + run: pip-audit -r requirements.txt --strict |
| 188 | + |
| 189 | + secret-scan: |
| 190 | + name: gitleaks (block on detect) |
| 191 | + runs-on: ubuntu-latest |
| 192 | + timeout-minutes: 5 |
| 193 | + # REQUIRED gate — closes BUG-S-004 (CVSS 6.5, CWE-200, audit/BUGS.md). |
| 194 | + # The audit established zero secret leaks in working tree (CMD-014) and |
| 195 | + # bounded git-history (CMD-018, CMD-020) so the baseline is clean; this |
| 196 | + # job is required from day one so any future leak hard-fails the PR. |
| 197 | + # |
| 198 | + # fetch-depth: 0 is load-bearing — gitleaks scans the full commit history, |
| 199 | + # not just the PR diff. A shallow clone (the runner default) would miss |
| 200 | + # any secret introduced more than a few commits back. |
| 201 | + steps: |
| 202 | + - name: Checkout (full history for history scan) |
| 203 | + uses: actions/checkout@v4 |
| 204 | + with: |
| 205 | + fetch-depth: 0 |
| 206 | + |
| 207 | + - name: gitleaks scan |
| 208 | + uses: gitleaks/gitleaks-action@v2 |
| 209 | + env: |
| 210 | + # Suppress the optional Pro-license notice; we use the OSS scanner. |
| 211 | + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
0 commit comments