Some checks failed
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / loc-budget (push) Successful in 25s
CI / secret-scan (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / go-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 2m51s
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / test-go (push) Successful in 47s
CI / test-python-backend (push) Failing after 43s
CI / test-python-document-crawler (push) Successful in 30s
CI / test-python-dsms-gateway (push) Successful in 22s
CI / validate-canonical-controls (push) Successful in 22s
Trigger changes: - Remove dead 'develop' branch trigger - PR gate runs full suite; push-to-main re-runs tests + build only New jobs: - branch-name: enforce feat/*/feature/*/fix/*/hotfix/* naming on PRs - secret-scan: gitleaks v8 — blocks secrets from merging - nodejs-build: 'next build' for admin-compliance + developer-portal (catches webpack/TS errors like the duplicate-export that broke CI) - dep-audit: pip-audit (Python), npm audit --moderate (Node), govulncheck (Go, non-blocking until modules are pinned) Existing job improvements: - go-lint: add 'go build ./...' compile check - python-lint: add import sanity check (catches NameError at collection) - Rename test jobs for consistency [guardrail-change] Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
333 lines
14 KiB
YAML
333 lines
14 KiB
YAML
# BreakPilot Compliance — CI Pipeline
|
|
#
|
|
# Feature branch workflow:
|
|
# feat/* | feature/* | fix/* | hotfix/* | chore/* | refactor/* | docs/* | test/* | ci/*
|
|
# → open PR targeting main
|
|
# → all jobs run as PR gates
|
|
# → squash merge to main
|
|
# → subset of jobs re-run on main to catch merge surprises
|
|
#
|
|
# Deploy is handled by build-push-deploy.yml on push to main.
|
|
|
|
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
pull_request:
|
|
branches: [main]
|
|
|
|
jobs:
|
|
|
|
# ── Branch naming convention (PR only) ──────────────────────────────────
|
|
branch-name:
|
|
runs-on: docker
|
|
container: alpine:3.20
|
|
if: github.event_name == 'pull_request'
|
|
steps:
|
|
- name: Validate branch name
|
|
run: |
|
|
BRANCH="${GITHUB_HEAD_REF}"
|
|
if ! echo "$BRANCH" | grep -qE '^(feat|feature|fix|hotfix|chore|refactor|docs|test|ci)/.+'; then
|
|
echo "::error::Branch '$BRANCH' does not follow naming convention."
|
|
echo "Required prefix: feat/ feature/ fix/ hotfix/ chore/ refactor/ docs/ test/ ci/"
|
|
exit 1
|
|
fi
|
|
echo "Branch name OK: $BRANCH"
|
|
|
|
# ── Guardrail integrity (PR only) ────────────────────────────────────────
|
|
guardrail-integrity:
|
|
runs-on: docker
|
|
container: alpine:3.20
|
|
if: github.event_name == 'pull_request'
|
|
steps:
|
|
- name: Checkout
|
|
run: |
|
|
apk add --no-cache git bash
|
|
git clone --depth 20 --branch ${GITHUB_REF_NAME} ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git .
|
|
git fetch origin ${GITHUB_BASE_REF}:base
|
|
- name: Require [guardrail-change] in commits touching guardrails
|
|
run: |
|
|
changed=$(git diff --name-only base...HEAD)
|
|
echo "$changed" | grep -qE '^(\.claude/settings\.json|\.claude/rules/loc-exceptions\.txt|scripts/check-loc\.sh|scripts/githooks/pre-commit|AGENTS\.(python|go|typescript)\.md)$' || exit 0
|
|
if ! git log base..HEAD --format=%B | grep -q '\[guardrail-change\]'; then
|
|
echo "::error::Guardrail files modified without [guardrail-change] in any commit message."
|
|
exit 1
|
|
fi
|
|
|
|
# ── LOC budget (always) ──────────────────────────────────────────────────
|
|
loc-budget:
|
|
runs-on: docker
|
|
container: alpine:3.20
|
|
steps:
|
|
- name: Checkout
|
|
run: |
|
|
apk add --no-cache git bash
|
|
git clone --depth 50 --branch ${GITHUB_REF_NAME} ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git .
|
|
- name: Enforce 500-line hard cap
|
|
run: |
|
|
chmod +x scripts/check-loc.sh
|
|
scripts/check-loc.sh
|
|
|
|
# ── Secret scanning (PR only) ────────────────────────────────────────────
|
|
secret-scan:
|
|
runs-on: docker
|
|
container: zricethezav/gitleaks:v8.21.2
|
|
if: github.event_name == 'pull_request'
|
|
steps:
|
|
- name: Checkout
|
|
run: |
|
|
apk add --no-cache git
|
|
git clone --depth 50 --branch ${GITHUB_REF_NAME} ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git .
|
|
- name: Scan for secrets
|
|
run: |
|
|
gitleaks detect --source . --no-git \
|
|
--exit-code 1 \
|
|
--redact \
|
|
|| { echo "::error::Secrets detected — remove them before merging."; exit 1; }
|
|
|
|
# ── Go lint + build (PR only) ────────────────────────────────────────────
|
|
go-lint:
|
|
runs-on: docker
|
|
if: github.event_name == 'pull_request'
|
|
container: golangci/golangci-lint:v1.62-alpine
|
|
steps:
|
|
- name: Checkout
|
|
run: |
|
|
apk add --no-cache git
|
|
git clone --depth 1 --branch ${GITHUB_REF_NAME} ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git .
|
|
- name: Lint ai-compliance-sdk
|
|
run: |
|
|
[ -d "ai-compliance-sdk" ] || exit 0
|
|
cd ai-compliance-sdk
|
|
golangci-lint run --timeout 5m ./...
|
|
- name: Build ai-compliance-sdk
|
|
run: |
|
|
[ -d "ai-compliance-sdk" ] || exit 0
|
|
cd ai-compliance-sdk
|
|
go build ./...
|
|
|
|
# ── Python lint + import check (PR only) ────────────────────────────────
|
|
python-lint:
|
|
runs-on: docker
|
|
if: github.event_name == 'pull_request'
|
|
container: python:3.12-slim
|
|
steps:
|
|
- name: Checkout
|
|
run: |
|
|
apt-get update -qq && apt-get install -y -qq git > /dev/null 2>&1
|
|
git clone --depth 1 --branch ${GITHUB_REF_NAME} ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git .
|
|
- name: Lint (ruff) + type-check (mypy)
|
|
run: |
|
|
pip install --quiet ruff mypy
|
|
fail=0
|
|
for svc in backend-compliance document-crawler dsms-gateway compliance-tts-service; do
|
|
[ -d "$svc" ] || continue
|
|
echo "=== ruff: $svc ===" && ruff check "$svc/" --output-format=github || fail=1
|
|
done
|
|
if [ -f "backend-compliance/mypy.ini" ]; then
|
|
cd backend-compliance && mypy compliance/ || fail=1
|
|
fi
|
|
exit $fail
|
|
- name: Import sanity check (catches NameError at collection time)
|
|
run: |
|
|
cd backend-compliance
|
|
pip install --quiet --no-cache-dir -r requirements.txt 2>/dev/null || true
|
|
export PYTHONPATH="$(pwd):${PYTHONPATH:-}"
|
|
python -c "import compliance; print('Import OK')" \
|
|
|| { echo "::error::compliance package fails to import — missing import or syntax error."; exit 1; }
|
|
|
|
# ── Node.js lint + type-check (PR only) ─────────────────────────────────
|
|
nodejs-lint:
|
|
runs-on: docker
|
|
if: github.event_name == 'pull_request'
|
|
container: node:20-alpine
|
|
steps:
|
|
- name: Checkout
|
|
run: |
|
|
apk add --no-cache git
|
|
git clone --depth 1 --branch ${GITHUB_REF_NAME} ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git .
|
|
- name: Lint + type-check
|
|
run: |
|
|
fail=0
|
|
for svc in admin-compliance developer-portal; do
|
|
[ -d "$svc" ] || continue
|
|
echo "=== $svc: install ===" && (cd "$svc" && npm ci --silent 2>/dev/null || npm install --silent)
|
|
echo "=== $svc: next lint ===" && (cd "$svc" && npx next lint) || fail=1
|
|
echo "=== $svc: tsc ===" && (cd "$svc" && npx tsc --noEmit) || fail=1
|
|
done
|
|
exit $fail
|
|
|
|
# ── Node.js build — next build (PR + push to main) ───────────────────────
|
|
nodejs-build:
|
|
runs-on: docker
|
|
container: node:20-alpine
|
|
steps:
|
|
- name: Checkout
|
|
run: |
|
|
apk add --no-cache git
|
|
git clone --depth 1 --branch ${GITHUB_REF_NAME} ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git .
|
|
- name: Build Next.js services
|
|
run: |
|
|
fail=0
|
|
for svc in admin-compliance developer-portal; do
|
|
[ -d "$svc" ] || continue
|
|
echo "=== $svc: install ==="
|
|
(cd "$svc" && npm ci --silent 2>/dev/null || npm install --silent)
|
|
echo "=== $svc: next build ==="
|
|
(cd "$svc" && \
|
|
NEXT_PUBLIC_API_URL=https://api-dev.breakpilot.ai \
|
|
NEXT_PUBLIC_SDK_URL=https://sdk-dev.breakpilot.ai \
|
|
npm run build) || fail=1
|
|
done
|
|
exit $fail
|
|
|
|
# ── Dependency audit (PR only) ───────────────────────────────────────────
|
|
dep-audit:
|
|
runs-on: docker
|
|
if: github.event_name == 'pull_request'
|
|
container: python:3.12-slim
|
|
steps:
|
|
- name: Checkout
|
|
run: |
|
|
apt-get update -qq && apt-get install -y -qq git curl > /dev/null 2>&1
|
|
git clone --depth 1 --branch ${GITHUB_REF_NAME} ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git .
|
|
- name: Install Node.js + Go
|
|
run: |
|
|
curl -fsSL https://deb.nodesource.com/setup_20.x | bash - > /dev/null 2>&1
|
|
apt-get install -y nodejs golang-go > /dev/null 2>&1
|
|
- name: Python — pip-audit
|
|
run: |
|
|
pip install --quiet pip-audit
|
|
fail=0
|
|
for svc in backend-compliance document-crawler dsms-gateway compliance-tts-service; do
|
|
[ -f "$svc/requirements.txt" ] || continue
|
|
echo "=== pip-audit: $svc ==="
|
|
pip-audit -r "$svc/requirements.txt" --skip-editable -f columns || fail=1
|
|
done
|
|
exit $fail
|
|
- name: Node.js — npm audit
|
|
run: |
|
|
fail=0
|
|
for svc in admin-compliance developer-portal; do
|
|
[ -d "$svc" ] || continue
|
|
echo "=== npm audit: $svc ==="
|
|
(cd "$svc" && npm audit --audit-level=moderate --json 2>/dev/null | \
|
|
node -e "const d=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')); \
|
|
const hi=Object.values(d.vulnerabilities||{}).filter(v=>['high','critical'].includes(v.severity)).length; \
|
|
if(hi>0){console.error('HIGH/CRITICAL: '+hi);process.exit(1)}") || fail=1
|
|
done
|
|
exit $fail
|
|
- name: Go — govulncheck
|
|
run: |
|
|
[ -d "ai-compliance-sdk" ] || exit 0
|
|
go install golang.org/x/vuln/cmd/govulncheck@latest 2>/dev/null
|
|
cd ai-compliance-sdk && govulncheck ./... || true
|
|
# Non-blocking until Go module versions are pinned
|
|
|
|
# ── SBOM + vulnerability scan (PR only) ─────────────────────────────────
|
|
sbom-scan:
|
|
runs-on: docker
|
|
if: github.event_name == 'pull_request'
|
|
container: alpine:3.20
|
|
steps:
|
|
- name: Checkout
|
|
run: |
|
|
apk add --no-cache git curl bash
|
|
git clone --depth 1 --branch ${GITHUB_REF_NAME} ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git .
|
|
- name: Install syft + grype
|
|
run: |
|
|
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
|
|
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin
|
|
- name: Generate SBOM
|
|
run: mkdir -p sbom-out && syft dir:. -o cyclonedx-json=sbom-out/sbom.cdx.json -q
|
|
- name: Vulnerability scan (fail on high+)
|
|
run: grype sbom:sbom-out/sbom.cdx.json --fail-on high -q
|
|
|
|
# ── Tests (PR + push to main) ─────────────────────────────────────────────
|
|
test-go:
|
|
runs-on: docker
|
|
container: golang:1.24-alpine
|
|
env:
|
|
CGO_ENABLED: "0"
|
|
steps:
|
|
- name: Checkout
|
|
run: |
|
|
apk add --no-cache git
|
|
git clone --depth 1 --branch ${GITHUB_REF_NAME} ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git .
|
|
- name: Test ai-compliance-sdk
|
|
run: |
|
|
[ -d "ai-compliance-sdk" ] || exit 0
|
|
cd ai-compliance-sdk
|
|
go test -v -coverprofile=coverage.out ./...
|
|
go tool cover -func=coverage.out | tail -1
|
|
|
|
test-python-backend:
|
|
runs-on: docker
|
|
container: python:3.12-slim
|
|
env:
|
|
CI: "true"
|
|
steps:
|
|
- name: Checkout
|
|
run: |
|
|
apt-get update -qq && apt-get install -y -qq git > /dev/null 2>&1
|
|
git clone --depth 1 --branch ${GITHUB_REF_NAME} ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git .
|
|
- name: Test backend-compliance
|
|
run: |
|
|
[ -d "backend-compliance" ] || exit 0
|
|
cd backend-compliance
|
|
export PYTHONPATH="$(pwd):${PYTHONPATH:-}"
|
|
pip install --quiet --no-cache-dir -r requirements.txt 2>/dev/null || true
|
|
pip install --quiet --no-cache-dir pytest pytest-asyncio
|
|
python -m pytest compliance/tests/ -v --tb=short
|
|
|
|
test-python-document-crawler:
|
|
runs-on: docker
|
|
container: python:3.12-slim
|
|
env:
|
|
CI: "true"
|
|
steps:
|
|
- name: Checkout
|
|
run: |
|
|
apt-get update -qq && apt-get install -y -qq git > /dev/null 2>&1
|
|
git clone --depth 1 --branch ${GITHUB_REF_NAME} ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git .
|
|
- name: Test document-crawler
|
|
run: |
|
|
[ -d "document-crawler" ] || exit 0
|
|
cd document-crawler
|
|
export PYTHONPATH="$(pwd):${PYTHONPATH:-}"
|
|
pip install --quiet --no-cache-dir -r requirements.txt 2>/dev/null || true
|
|
pip install --quiet --no-cache-dir pytest pytest-asyncio
|
|
python -m pytest tests/ -v --tb=short
|
|
|
|
test-python-dsms-gateway:
|
|
runs-on: docker
|
|
container: python:3.12-slim
|
|
env:
|
|
CI: "true"
|
|
steps:
|
|
- name: Checkout
|
|
run: |
|
|
apt-get update -qq && apt-get install -y -qq git > /dev/null 2>&1
|
|
git clone --depth 1 --branch ${GITHUB_REF_NAME} ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git .
|
|
- name: Test dsms-gateway
|
|
run: |
|
|
[ -d "dsms-gateway" ] || exit 0
|
|
cd dsms-gateway
|
|
export PYTHONPATH="$(pwd):${PYTHONPATH:-}"
|
|
pip install --quiet --no-cache-dir -r requirements.txt 2>/dev/null || true
|
|
pip install --quiet --no-cache-dir pytest pytest-asyncio
|
|
python -m pytest test_main.py -v --tb=short
|
|
|
|
# ── OpenAPI contract validation (always) ─────────────────────────────────
|
|
validate-canonical-controls:
|
|
runs-on: docker
|
|
container: python:3.12-slim
|
|
steps:
|
|
- name: Checkout
|
|
run: |
|
|
apt-get update -qq && apt-get install -y -qq git > /dev/null 2>&1
|
|
git clone --depth 1 --branch ${GITHUB_REF_NAME} ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git .
|
|
- name: Validate controls
|
|
run: python scripts/validate-controls.py
|