ci: add root-level workflow for Gitea Actions

Gitea Actions requires the workflow file at repo root (.gitea/workflows/ci.yml)
not in the app subdirectory. Uses defaults.run.working-directory: AINative_OKR_CASAN5
so all run steps execute in the correct app context.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
thanhnv
2026-07-01 13:02:03 +09:00
co-authored by Claude Sonnet 4.6
parent 9892e82221
commit 72d3e56308
+198
View File
@@ -0,0 +1,198 @@
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
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: Install frontend dependencies
run: npm ci -w frontend
- 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
if curl -sf http://localhost/ -o /dev/null; then
echo "DEPLOY_OK frontend=http://161.33.139.73"
else
echo "DEPLOY_WARN frontend check failed — dumping logs"
docker logs okr-frontend --tail 30 || true
docker logs okr-backend --tail 30 || true
fi