security-gate job was downloading ~300MB of React/Vite npm packages unnecessarily. casan-step.mjs uses only node:child_process, node:fs, node:os (built-ins) — no npm packages needed. Removing npm ci -w frontend saves ~300MB peak RAM in CI. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
199 lines
7.7 KiB
YAML
199 lines
7.7 KiB
YAML
name: CASAN CI Gate
|
|
|
|
# Runs on every push/PR to catch regressions (H3) and validate security controls (H4/H5).
|
|
on:
|
|
push:
|
|
branches: [main, develop, "feature/**"]
|
|
pull_request:
|
|
branches: [main]
|
|
|
|
# Cancel in-flight runs of the same branch when a newer push arrives.
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
# All run steps execute inside AINative_OKR_CASAN5/ (the app directory).
|
|
# actions/* steps still reference $GITHUB_WORKSPACE root, so paths in `with:` blocks
|
|
# must include AINative_OKR_CASAN5/ prefix.
|
|
|
|
jobs:
|
|
# ──────────────────────────────────────────────────────────────────────────
|
|
# Job 1 — Frontend unit tests (fast gate, ~1 min)
|
|
# ──────────────────────────────────────────────────────────────────────────
|
|
frontend-tests:
|
|
name: "Frontend Tests (H3 gate)"
|
|
runs-on: ubuntu-latest
|
|
defaults:
|
|
run:
|
|
working-directory: AINative_OKR_CASAN5
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v3
|
|
|
|
- name: Setup Node.js 20
|
|
uses: actions/setup-node@v3
|
|
with:
|
|
node-version: "20"
|
|
cache: "npm"
|
|
cache-dependency-path: AINative_OKR_CASAN5/package-lock.json
|
|
|
|
- name: Install frontend dependencies
|
|
run: npm ci -w frontend
|
|
|
|
- name: Run Vitest (16 tests)
|
|
run: npm test -w frontend
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────
|
|
# Job 2 — CASAN Security Gate + Vault KMS signing (H4/H5/H2/H6/H7)
|
|
# ──────────────────────────────────────────────────────────────────────────
|
|
security-gate:
|
|
name: "CASAN Security Gate + Vault KMS (H4/H5)"
|
|
runs-on: ubuntu-latest
|
|
needs: [frontend-tests]
|
|
defaults:
|
|
run:
|
|
working-directory: AINative_OKR_CASAN5
|
|
env:
|
|
VAULT_ADDR: "http://vault:8200"
|
|
VAULT_TOKEN: ${{ secrets.VAULT_TOKEN }}
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v3
|
|
|
|
- name: Setup Node.js 20
|
|
uses: actions/setup-node@v3
|
|
with:
|
|
node-version: "20"
|
|
|
|
- name: Install test tools
|
|
run: |
|
|
apt-get update -qq 2>/dev/null && \
|
|
apt-get install -y -qq jq openssl python3 python-is-python3 uuid-runtime curl 2>/dev/null || true
|
|
command -v python >/dev/null 2>&1 || \
|
|
ln -sf "$(command -v python3)" /usr/local/bin/python
|
|
echo "python: $(python --version)"
|
|
echo "jq: $(jq --version)"
|
|
echo "openssl: $(openssl version)"
|
|
|
|
- name: Vault KMS — enable transit + pre-create keys
|
|
run: |
|
|
if curl -sf "$VAULT_ADDR/v1/sys/health" >/dev/null 2>&1; then
|
|
bash .specify/scripts/bash/vault-kms.sh enable-transit
|
|
bash .specify/scripts/bash/vault-kms.sh ensure-key casan-policy-key
|
|
bash .specify/scripts/bash/vault-kms.sh ensure-key casan-audit-key
|
|
echo "VAULT_KMS_READY"
|
|
else
|
|
echo "VAULT_KMS_SKIP (unreachable — will use local-file fallback)"
|
|
fi
|
|
|
|
- name: Run CASAN4 harness tests (35 tests)
|
|
run: bash .specify/tests/run-casan4-harness-tests.sh
|
|
|
|
- name: Run adversarial harness tests (44 tests)
|
|
run: bash .specify/tests/adversarial-harness-tests.sh
|
|
|
|
- name: Sign audit chain head via Vault KMS (H5)
|
|
run: bash .specify/scripts/bash/sign-audit-head.sh
|
|
|
|
- name: Sign policy bundle via Vault KMS (H5)
|
|
run: bash .specify/scripts/bash/sign-policy-bundle.sh sign
|
|
|
|
- name: Verify audit chain (anchor=signed expected in CI)
|
|
run: bash .specify/scripts/bash/verify-audit-chain.sh
|
|
|
|
- name: Security gate — aggregate verdict (PASS=11 FAIL=0 SKIP=0)
|
|
run: bash .specify/scripts/bash/security-gate.sh
|
|
|
|
- name: Upload test evidence
|
|
if: always()
|
|
uses: actions/upload-artifact@v3
|
|
with:
|
|
name: casan-evidence-${{ github.run_number }}
|
|
path: |
|
|
AINative_OKR_CASAN5/docs/output/casan/evidence/harness-test-report.md
|
|
AINative_OKR_CASAN5/docs/output/casan/evidence/
|
|
retention-days: 14
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────
|
|
# Job 3 — Deploy OKR web app (main branch only)
|
|
#
|
|
# okr-backend — NestJS + Prisma + SQLite, port 3001 (internal)
|
|
# okr-frontend — nginx + React SPA, port 80 (public)
|
|
# nginx proxies /api/v1/* → okr-backend:3001
|
|
# ──────────────────────────────────────────────────────────────────────────
|
|
deploy-okr:
|
|
name: "Deploy OKR → port 80 (H3 CI gate)"
|
|
runs-on: ubuntu-latest
|
|
needs: [frontend-tests, security-gate]
|
|
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
|
defaults:
|
|
run:
|
|
working-directory: AINative_OKR_CASAN5
|
|
env:
|
|
JWT_SECRET: ${{ secrets.JWT_SECRET }}
|
|
FRONTEND_ORIGIN: "http://161.33.139.73"
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v3
|
|
|
|
- name: Build backend image (node:22-slim — required for node:sqlite)
|
|
run: |
|
|
docker build \
|
|
-t okr-backend:latest \
|
|
-f Dockerfile.backend \
|
|
.
|
|
|
|
- name: Build frontend image (nginx + React SPA)
|
|
run: |
|
|
docker build \
|
|
--build-arg VITE_API_BASE_URL=/api/v1 \
|
|
-t okr-frontend:latest \
|
|
-f Dockerfile.frontend \
|
|
.
|
|
|
|
- name: Create network + persistent volume
|
|
run: |
|
|
docker network create okr-net 2>/dev/null || true
|
|
docker volume create okr-db 2>/dev/null || true
|
|
|
|
- name: Deploy backend (port 3001, internal only)
|
|
run: |
|
|
docker rm -f okr-backend 2>/dev/null || true
|
|
docker run -d \
|
|
--name okr-backend \
|
|
--network okr-net \
|
|
-e PORT=3001 \
|
|
-e DATABASE_URL=file:/data/okr.db \
|
|
-e JWT_SECRET="${JWT_SECRET}" \
|
|
-e FRONTEND_ORIGIN="${FRONTEND_ORIGIN}" \
|
|
-e NODE_ENV=production \
|
|
-v okr-db:/data \
|
|
--restart unless-stopped \
|
|
okr-backend:latest
|
|
|
|
- name: Deploy frontend (port 80, public)
|
|
run: |
|
|
docker rm -f okr-frontend 2>/dev/null || true
|
|
docker run -d \
|
|
--name okr-frontend \
|
|
--network okr-net \
|
|
-p 80:80 \
|
|
--restart unless-stopped \
|
|
okr-frontend:latest
|
|
|
|
- name: Health check
|
|
run: |
|
|
echo "Waiting 20 s for containers to initialise..."
|
|
sleep 20
|
|
echo "=== Running containers ==="
|
|
docker ps --filter "name=okr" --format "{{.Names}}\t{{.Status}}\t{{.Ports}}"
|
|
if docker ps --filter "name=okr-frontend" --filter "status=running" | grep -q okr-frontend; then
|
|
echo "DEPLOY_OK frontend=http://161.33.139.73"
|
|
else
|
|
echo "DEPLOY_WARN containers not fully running — dumping logs"
|
|
docker logs okr-frontend --tail 30 || true
|
|
docker logs okr-backend --tail 30 || true
|
|
fi
|