Spaces:
Sleeping
Sleeping
Commit ·
68b3ca2
1
Parent(s): efd993b
Phase 3: overhaul CI pipeline
Browse filesReplace 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.
- .github/workflows/ci.yml +109 -48
- requirements.txt +1 -0
- 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 |
-
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
jobs:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
test:
|
| 11 |
runs-on: ubuntu-latest
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
| 13 |
steps:
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
run:
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
docker:
|
| 50 |
runs-on: ubuntu-latest
|
| 51 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
steps:
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 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 |
|