Implement M46: Operational maturity #84
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| workflow_dispatch: | |
| concurrency: | |
| group: ci-${{ github.ref }} | |
| cancel-in-progress: true | |
| # Minimal permissions by default | |
| permissions: | |
| contents: read | |
| jobs: | |
| go-build-and-test: | |
| name: Go Build & Test | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| strategy: | |
| matrix: | |
| service: [airlock, registry, tool-firewall, gpu-integrity-watch, mcp-firewall, policy-engine, runtime-attestor, integrity-monitor, incident-recorder] | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 | |
| with: | |
| go-version: "1.23" | |
| cache-dependency-path: services/${{ matrix.service }}/go.sum | |
| - name: Build | |
| working-directory: services/${{ matrix.service }} | |
| run: CGO_ENABLED=0 go build -ldflags="-s -w" -o /dev/null . | |
| - name: Test | |
| working-directory: services/${{ matrix.service }} | |
| run: go test -v -race -count=1 ./... | |
| - name: Vet | |
| working-directory: services/${{ matrix.service }} | |
| run: go vet ./... | |
| python-test: | |
| name: Python Test & Lint | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 | |
| with: | |
| python-version: "3.12" | |
| - name: Install dependencies | |
| run: pip install pyyaml flask requests pytest ruff bandit | |
| - name: Lint (syntax check) | |
| run: | | |
| python -m py_compile services/ui/ui/app.py | |
| python -m py_compile services/diffusion-worker/app.py | |
| python -m py_compile services/common/audit_chain.py | |
| python -m py_compile services/common/auth.py | |
| python -m py_compile services/common/mlock_helper.py | |
| python -m py_compile services/agent/agent/app.py | |
| python -m py_compile services/agent/agent/models.py | |
| python -m py_compile services/agent/agent/policy.py | |
| python -m py_compile services/agent/agent/planner.py | |
| python -m py_compile services/agent/agent/executor.py | |
| python -m py_compile services/agent/agent/storage.py | |
| python -m py_compile services/agent/agent/capabilities.py | |
| python -m py_compile services/agent/agent/sandbox.py | |
| - name: Ruff lint | |
| run: ruff check services/ tests/ --select E,F,W --ignore E501,E402 | |
| - name: Bandit security scan | |
| run: | | |
| bandit -r services/ -ll --skip B101,B404,B603 -f txt || { | |
| echo "::warning::Bandit found potential security issues (see above)" | |
| true | |
| } | |
| - name: Test (unit + integration) | |
| env: | |
| PYTHONPATH: services | |
| run: python -m pytest tests/ -v --ignore=tests/test_adversarial.py --ignore=tests/test_m5_acceptance.py -x | |
| - name: Test (adversarial + acceptance) | |
| env: | |
| PYTHONPATH: services | |
| run: python -m pytest tests/test_adversarial.py tests/test_m5_acceptance.py -v --tb=short | |
| shellcheck: | |
| name: Shell Script Lint | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Lint shell scripts | |
| run: | | |
| shellcheck -s bash \ | |
| files/system/usr/libexec/secure-ai/*.sh \ | |
| files/scripts/build-services.sh \ | |
| files/scripts/generate-mok.sh \ | |
| files/scripts/first-boot-check.sh \ | |
| files/scripts/verify-release.sh | |
| policy-validate: | |
| name: Validate YAML configs | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 | |
| with: | |
| python-version: "3.12" | |
| - name: Install pyyaml | |
| run: pip install pyyaml | |
| - name: Validate YAML files | |
| run: | | |
| python -c " | |
| import yaml, sys, glob | |
| errors = 0 | |
| for pattern in ['files/system/etc/secure-ai/**/*.yaml', 'recipes/*.yml']: | |
| for f in glob.glob(pattern, recursive=True): | |
| try: | |
| with open(f) as fh: | |
| yaml.safe_load(fh) | |
| print(f'OK: {f}') | |
| except Exception as e: | |
| print(f'FAIL: {f}: {e}') | |
| errors += 1 | |
| sys.exit(errors) | |
| " | |
| check-pins: | |
| name: Verify action pins | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - run: bash .github/scripts/check-action-pins.sh | |
| supply-chain-verify: | |
| name: Supply Chain & SBOM Verification | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 | |
| with: | |
| go-version: "1.23" | |
| - name: Install Syft (SBOM generator) | |
| run: curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin | |
| - name: Install cosign (signing & attestation) | |
| run: | | |
| COSIGN_VERSION="v2.4.3" | |
| curl -sSfL "https://github.com/sigstore/cosign/releases/download/${COSIGN_VERSION}/cosign-linux-amd64" \ | |
| -o /usr/local/bin/cosign | |
| chmod +x /usr/local/bin/cosign | |
| - name: Verify SBOM generation (Go services) | |
| run: | | |
| echo "=== SBOM generation verification ===" | |
| for svc in airlock registry tool-firewall gpu-integrity-watch mcp-firewall \ | |
| policy-engine runtime-attestor integrity-monitor incident-recorder; do | |
| echo "--- ${svc} ---" | |
| syft dir:services/${svc} -o cyclonedx-json=/dev/null | |
| echo "OK: ${svc} SBOM generated" | |
| done | |
| - name: Verify SBOM generation (Python services) | |
| run: | | |
| for svc in agent ui quarantine common diffusion-worker search-mediator; do | |
| if [ -d "services/${svc}" ]; then | |
| syft dir:services/${svc} -o cyclonedx-json=/dev/null | |
| echo "OK: ${svc} SBOM generated" | |
| fi | |
| done | |
| - name: Verify cosign is functional | |
| run: | | |
| cosign version | |
| echo "OK: cosign available for signing and attestation" | |
| - name: Verify release workflow has provenance steps | |
| run: | | |
| echo "=== Checking release.yml provenance pipeline ===" | |
| # Verify release workflow exists and contains required supply-chain steps | |
| test -f .github/workflows/release.yml || { echo "FAIL: release.yml missing"; exit 1; } | |
| for keyword in "sbom-action" "attest-build-provenance" "cosign" "cyclonedx" "SHA256SUMS"; do | |
| grep -q "${keyword}" .github/workflows/release.yml || \ | |
| { echo "FAIL: release.yml missing '${keyword}'"; exit 1; } | |
| echo "OK: release.yml contains '${keyword}'" | |
| done | |
| # Verify build workflow has SBOM attestation | |
| test -f .github/workflows/build.yml || { echo "FAIL: build.yml missing"; exit 1; } | |
| for keyword in "sbom-action" "cosign attest" "cyclonedx"; do | |
| grep -q "${keyword}" .github/workflows/build.yml || \ | |
| { echo "FAIL: build.yml missing '${keyword}'"; exit 1; } | |
| echo "OK: build.yml contains '${keyword}'" | |
| done | |
| echo "=== Supply chain verification passed ===" | |
| security-regression: | |
| name: Security Regression Tests | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 | |
| with: | |
| python-version: "3.12" | |
| - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 | |
| with: | |
| go-version: "1.23" | |
| - name: Install Python dependencies | |
| run: pip install pyyaml flask requests pytest | |
| - name: Run adversarial Python tests | |
| run: python -m pytest tests/test_adversarial.py -v --tb=short | |
| - name: Run MCP firewall adversarial tests | |
| working-directory: services/mcp-firewall | |
| run: go test -v -race -run TestAdversarial ./... | |
| - name: Run policy-engine adversarial tests | |
| working-directory: services/policy-engine | |
| run: go test -v -race -run TestAdversarial ./... | |
| - name: Run incident-recorder recovery tests | |
| working-directory: services/incident-recorder | |
| run: go test -v -race -run "TestRecovery|TestEscalation|TestForensic|TestLatched" ./... | |
| test-count-check: | |
| name: Test Count Drift Check | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 | |
| with: | |
| go-version: "1.23" | |
| - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 | |
| with: | |
| python-version: "3.12" | |
| - name: Install Python dependencies | |
| run: pip install pyyaml flask requests pytest | |
| - name: Check test counts for drift | |
| run: bash .github/scripts/check-test-counts.sh | |
| dependency-audit: | |
| name: Dependency Vulnerability Audit | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 | |
| with: | |
| go-version: "1.23" | |
| - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 | |
| with: | |
| python-version: "3.12" | |
| - name: Install govulncheck | |
| run: go install golang.org/x/vuln/cmd/govulncheck@latest | |
| - name: Go vulnerability scan | |
| run: | | |
| echo "=== Go Dependency Vulnerability Scan ===" | |
| VULN_ERRORS=0 | |
| for svc in airlock registry tool-firewall gpu-integrity-watch mcp-firewall \ | |
| policy-engine runtime-attestor integrity-monitor incident-recorder; do | |
| echo "--- ${svc} ---" | |
| cd "services/${svc}" | |
| govulncheck ./... || VULN_ERRORS=$((VULN_ERRORS + 1)) | |
| cd ../.. | |
| done | |
| if [ $VULN_ERRORS -gt 0 ]; then | |
| echo "WARNING: $VULN_ERRORS service(s) have known vulnerabilities" | |
| fi | |
| - name: Python dependency audit | |
| run: | | |
| pip install pip-audit pyyaml flask requests | |
| echo "=== Python Dependency Audit ===" | |
| pip-audit --strict --desc || echo "WARNING: Python dependencies have known vulnerabilities" | |
| docs-validation: | |
| name: Documentation Validation | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Check for broken internal links | |
| run: | | |
| echo "=== Checking internal doc links ===" | |
| ERRORS=0 | |
| # Find all markdown links to local files | |
| for md in $(find docs/ README.md CONTRIBUTING.md SECURITY.md -name '*.md' 2>/dev/null); do | |
| # Extract relative links (not URLs, not anchors) | |
| grep -oP '\[([^\]]*)\]\((?!https?://|#)([^)]+)\)' "$md" 2>/dev/null | \ | |
| grep -oP '\(([^)]+)\)' | tr -d '()' | while read -r link; do | |
| # Strip anchor fragments | |
| target="${link%%#*}" | |
| [ -z "$target" ] && continue | |
| # Resolve relative to file's directory | |
| dir=$(dirname "$md") | |
| resolved="${dir}/${target}" | |
| if [ ! -f "$resolved" ] && [ ! -d "$resolved" ]; then | |
| echo "BROKEN: ${md} -> ${link} (resolved: ${resolved})" | |
| ERRORS=$((ERRORS + 1)) | |
| fi | |
| done | |
| done | |
| if [ "$ERRORS" -gt 0 ]; then | |
| echo "FAIL: ${ERRORS} broken internal links found" | |
| exit 1 | |
| fi | |
| echo "OK: All internal doc links valid" | |
| - name: Verify required docs exist | |
| run: | | |
| echo "=== Checking required documentation ===" | |
| REQUIRED_DOCS=( | |
| "docs/threat-model.md" | |
| "docs/architecture.md" | |
| "docs/api.md" | |
| "docs/security-status.md" | |
| "docs/production-operations.md" | |
| "docs/production-readiness-checklist.md" | |
| "docs/slos.md" | |
| "docs/release-policy.md" | |
| "docs/support-lifecycle.md" | |
| "docs/test-counts.json" | |
| "docs/install/bare-metal.md" | |
| "SECURITY.md" | |
| "CONTRIBUTING.md" | |
| "LICENSE" | |
| ) | |
| ERRORS=0 | |
| for doc in "${REQUIRED_DOCS[@]}"; do | |
| if [ -f "$doc" ]; then | |
| echo "OK: $doc" | |
| else | |
| echo "MISSING: $doc" | |
| ERRORS=$((ERRORS + 1)) | |
| fi | |
| done | |
| if [ "$ERRORS" -gt 0 ]; then | |
| echo "FAIL: ${ERRORS} required document(s) missing" | |
| exit 1 | |
| fi | |
| echo "All required documents present" | |
| - name: Validate test-counts.json format | |
| run: | | |
| python3 -c " | |
| import json, sys | |
| with open('docs/test-counts.json') as f: | |
| data = json.load(f) | |
| required = ['generated', 'go', 'go_total', 'python_total', 'grand_total'] | |
| for key in required: | |
| if key not in data: | |
| print(f'FAIL: test-counts.json missing key: {key}') | |
| sys.exit(1) | |
| if not isinstance(data['go'], dict): | |
| print('FAIL: go field must be a dict of service -> count') | |
| sys.exit(1) | |
| print(f'OK: test-counts.json valid (total: {data[\"grand_total\"]} tests)') | |
| " |