rogerdemello commited on
Commit
68b3ca2
·
1 Parent(s): efd993b

Phase 3: overhaul CI pipeline

Browse files

Replace the minimal workflow with parallel jobs:
- lint: ruff check + ruff format --check
- test: Python 3.10/3.11/3.12 matrix, pytest + coverage gate (--cov-fail-under=70,
current 77%), Codecov upload (non-blocking)
- typecheck: mypy (informational/continue-on-error)
- security: bandit (-ll, blocking) + pip-audit (informational)
- frontend: npm ci, tsc --noEmit, build
- docker: build image + smoke /health, /docs, /dashboard/
- inference-smoke: baseline-fallback run with no provider key

Add pytest-cov to requirements. Restrict alert webhooks to http(s) schemes
(fixes bandit B310) so the security job passes.

Files changed (3) hide show
  1. .github/workflows/ci.yml +109 -48
  2. requirements.txt +1 -0
  3. telemetry/alerts.py +4 -1
.github/workflows/ci.yml CHANGED
@@ -4,60 +4,121 @@ on:
4
  push:
5
  branches: [main, master]
6
  pull_request:
7
- branches: [main, master]
 
 
 
8
 
9
  jobs:
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  test:
11
  runs-on: ubuntu-latest
12
-
 
 
 
13
  steps:
14
- - uses: actions/checkout@v4
15
-
16
- - name: Set up Python
17
- uses: actions/setup-python@v5
18
- with:
19
- python-version: '3.12'
20
-
21
- - name: Cache pip packages
22
- uses: actions/cache@v4
23
- with:
24
- path: ~/.cache/pip
25
- key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt', 'pyproject.toml') }}
26
- restore-keys: |
27
- ${{ runner.os }}-pip-
28
-
29
- - name: Install dependencies
30
- run: |
31
- python -m pip install --upgrade pip
32
- pip install -r requirements.txt
33
-
34
- - name: Verify dependency lock
35
- run: |
36
- pip freeze > requirements.lock
37
- diff -q requirements.txt requirements.lock || echo "WARNING: requirements.txt and lock mismatch"
38
- cat requirements.lock
39
-
40
- - name: Run tests
41
- run: python -m pytest -q --tb=short
42
-
43
- - name: Run smoke test inference
44
- env:
45
- OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY || 'sk-test-dummy' }}
46
- run: |
47
- python -c "import os; os.environ.setdefault('OPENAI_API_KEY', 'sk-test-dummy'); os.environ.setdefault('API_BASE_URL', 'https://api.openai.com/v1'); os.environ.setdefault('MODEL_NAME', 'gpt-4o-mini'); from inference import run_inference; result = run_inference('easy_classification', 'openenv', 'gpt-4o-mini', seed=42); print(f'Smoke test passed: score={result.get(\"score\", 0):.2f}')"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
 
49
  docker:
50
  runs-on: ubuntu-latest
51
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  steps:
53
- - uses: actions/checkout@v4
54
-
55
- - name: Build Docker image
56
- run: docker build -t exec-email-copilot .
57
-
58
- - name: Test container health
59
- run: |
60
- docker run -d --name test-container -p 7861:7860 exec-email-copilot
61
- sleep 5
62
- docker logs test-container
63
- docker rm -f test-container
 
4
  push:
5
  branches: [main, master]
6
  pull_request:
7
+
8
+ concurrency:
9
+ group: ci-${{ github.ref }}
10
+ cancel-in-progress: true
11
 
12
  jobs:
13
+ lint:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: actions/setup-python@v5
18
+ with:
19
+ python-version: '3.12'
20
+ - run: pip install ruff
21
+ - name: ruff lint
22
+ run: ruff check .
23
+ - name: ruff format check
24
+ run: ruff format --check .
25
+
26
  test:
27
  runs-on: ubuntu-latest
28
+ strategy:
29
+ fail-fast: false
30
+ matrix:
31
+ python-version: ['3.10', '3.11', '3.12']
32
  steps:
33
+ - uses: actions/checkout@v4
34
+ - uses: actions/setup-python@v5
35
+ with:
36
+ python-version: ${{ matrix.python-version }}
37
+ cache: pip
38
+ - run: pip install -r requirements.txt
39
+ - name: pytest + coverage gate
40
+ run: >-
41
+ python -m pytest -q
42
+ --cov=env --cov=baseline --cov=benchmark --cov=reports --cov=telemetry --cov=server
43
+ --cov-report=xml --cov-report=term-missing --cov-fail-under=70
44
+ - name: Upload coverage to Codecov
45
+ if: matrix.python-version == '3.12'
46
+ uses: codecov/codecov-action@v4
47
+ with:
48
+ files: coverage.xml
49
+ continue-on-error: true # don't fail CI when no token is configured
50
+
51
+ typecheck:
52
+ runs-on: ubuntu-latest
53
+ continue-on-error: true # informational until the codebase is fully typed
54
+ steps:
55
+ - uses: actions/checkout@v4
56
+ - uses: actions/setup-python@v5
57
+ with:
58
+ python-version: '3.12'
59
+ - run: pip install mypy
60
+ - run: mypy env --ignore-missing-imports --no-strict-optional || true
61
+
62
+ security:
63
+ runs-on: ubuntu-latest
64
+ steps:
65
+ - uses: actions/checkout@v4
66
+ - uses: actions/setup-python@v5
67
+ with:
68
+ python-version: '3.12'
69
+ - run: pip install bandit pip-audit
70
+ - name: bandit (SAST)
71
+ run: bandit -r env baseline benchmark reports telemetry server -ll
72
+ - name: pip-audit (dependency vulnerabilities)
73
+ run: pip-audit -r requirements.txt
74
+ continue-on-error: true # advisories may be unfixable upstream; informational
75
+
76
+ frontend:
77
+ runs-on: ubuntu-latest
78
+ defaults:
79
+ run:
80
+ working-directory: dashboard
81
+ steps:
82
+ - uses: actions/checkout@v4
83
+ - uses: actions/setup-node@v4
84
+ with:
85
+ node-version: '20'
86
+ cache: npm
87
+ cache-dependency-path: dashboard/package-lock.json
88
+ - run: npm ci || npm install
89
+ - name: TypeScript check
90
+ run: npx tsc --noEmit
91
+ - name: Build dashboard
92
+ run: npm run build
93
 
94
  docker:
95
  runs-on: ubuntu-latest
96
+ needs: [test, frontend]
97
+ steps:
98
+ - uses: actions/checkout@v4
99
+ - name: Build image
100
+ run: docker build -t exec-email-copilot .
101
+ - name: Smoke test container
102
+ run: |
103
+ docker run -d --name app -p 7860:7860 exec-email-copilot
104
+ ok=0
105
+ for _ in $(seq 1 30); do
106
+ if curl -fsS http://localhost:7860/health > /dev/null; then ok=1; break; fi
107
+ sleep 2
108
+ done
109
+ if [ "$ok" != "1" ]; then docker logs app; exit 1; fi
110
+ curl -fsS http://localhost:7860/docs > /dev/null
111
+ curl -fsS http://localhost:7860/dashboard/ > /dev/null
112
+ docker rm -f app
113
+
114
+ inference-smoke:
115
+ runs-on: ubuntu-latest
116
  steps:
117
+ - uses: actions/checkout@v4
118
+ - uses: actions/setup-python@v5
119
+ with:
120
+ python-version: '3.12'
121
+ cache: pip
122
+ - run: pip install -r requirements.txt
123
+ - name: Inference (baseline fallback, no provider key)
124
+ run: python inference.py --task easy_classification --max-steps 20
 
 
 
requirements.txt CHANGED
@@ -7,6 +7,7 @@ pyyaml
7
  httpx
8
  scipy
9
  pytest
 
10
  streamlit
11
  openai
12
  sqlalchemy
 
7
  httpx
8
  scipy
9
  pytest
10
+ pytest-cov
11
  streamlit
12
  openai
13
  sqlalchemy
telemetry/alerts.py CHANGED
@@ -57,6 +57,9 @@ class AlertManager:
57
  return triggered
58
 
59
  def _send_webhook(self, webhook: str, alert: Alert) -> None:
 
 
 
60
  try:
61
  payload = json.dumps(
62
  {
@@ -70,7 +73,7 @@ class AlertManager:
70
  data=payload,
71
  headers={"Content-Type": "application/json"},
72
  )
73
- urllib.request.urlopen(req, timeout=5)
74
  except Exception:
75
  pass
76
 
 
57
  return triggered
58
 
59
  def _send_webhook(self, webhook: str, alert: Alert) -> None:
60
+ # Only POST to http(s) webhooks; reject file:// and other schemes.
61
+ if not webhook.lower().startswith(("http://", "https://")):
62
+ return
63
  try:
64
  payload = json.dumps(
65
  {
 
73
  data=payload,
74
  headers={"Content-Type": "application/json"},
75
  )
76
+ urllib.request.urlopen(req, timeout=5) # nosec B310 - scheme restricted to http(s) above
77
  except Exception:
78
  pass
79