- infra/scripts/simulate-two-host/: real two-Docker-network simulation (Gateway genuinely isolated from any Lab bridge network) proving the Lab Host Router fix - 13/13 real assertions (HTTP+WebSocket through the Router, Alice/Bob isolation, garbage/destroyed tokens, mTLS-only Router access). - infra/scripts/drills/: Lab Host loss/rebuild (9/9), deployment rollback, CI-fails-closed (6 required failure modes), fake cohort dress rehearsal (14/14, one continuous narrative through the real app). - infra/ci/gitea-workflows/build-and-test.yml: new security-scan job (npm audit, gitleaks, hadolint, shellcheck, trivy - all free/open-source), severity policy in docs/SECURITY_SCANNING.md (CRITICAL/HIGH blocks, MEDIUM/LOW reported only). Gates build-images alongside regression. Found and fixed two real pre-existing bugs via the CI-fails-closed drill: staging-smoke-test.sh and production-smoke-test.sh both had a classic set -e gotcha (curl ... && echo "OK" as one statement - a failing command that isn't last in an && chain doesn't trigger -e) that let an unreachable/broken endpoint fall through silently instead of failing the smoke test. platform/test/e2e-phase3a.js updated for the new paginated /api/admin/audit-events response shape (Phase 4.1 pagination work).
78 lines
3.3 KiB
Bash
Executable File
78 lines
3.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Phase 4.1 §23 — proves the CI pipeline (infra/ci/gitea-workflows/build-and-test.yml) fails
|
|
# CLOSED on every listed failure mode, i.e. the underlying command genuinely exits nonzero on
|
|
# broken input, which is what makes a Gitea/GitHub Actions step (and therefore the job, and
|
|
# therefore `needs: regression` gating build-images/deploy-staging) fail. Doesn't need a real
|
|
# CI run — checks the actual commands the workflow calls, directly, the same way Actions would
|
|
# invoke them.
|
|
set -uo pipefail # NOT -e — this script's whole point is deliberately triggering failures and checking their exit codes
|
|
cd "$(dirname "${BASH_SOURCE[0]}")/../../.." # repo root
|
|
|
|
FAIL=0
|
|
expect_nonzero() {
|
|
local desc="$1"; shift
|
|
"$@" >/tmp/ci-fail-drill-out 2>&1
|
|
local code=$?
|
|
if [ "$code" -eq 0 ]; then
|
|
echo " FAIL — $desc (command succeeded, but it should have failed)"; cat /tmp/ci-fail-drill-out; FAIL=1
|
|
else
|
|
echo " ok — $desc correctly failed (exit $code)"
|
|
fi
|
|
}
|
|
|
|
echo "Phase 4.1 CI-fails-closed drill"
|
|
echo
|
|
|
|
echo "== 1. Test/syntax-check failure =="
|
|
BAD_JS="$(mktemp).js"
|
|
echo 'function broken( {' > "$BAD_JS"
|
|
expect_nonzero "node --check on syntactically broken JS" node --check "$BAD_JS"
|
|
rm -f "$BAD_JS"
|
|
|
|
echo "== 2. Frontend build failure =="
|
|
cp frontend/src/App.jsx /tmp/App.jsx.bak
|
|
echo 'this is not valid jsx <<<' >> frontend/src/App.jsx
|
|
(cd frontend && expect_nonzero "npm run build with a broken component" npm run build)
|
|
cp /tmp/App.jsx.bak frontend/src/App.jsx; rm -f /tmp/App.jsx.bak
|
|
|
|
echo "== 3. Image build failure =="
|
|
TMPDOCKERFILE="$(mktemp -d)/Dockerfile"
|
|
cat > "$TMPDOCKERFILE" <<'EOF'
|
|
FROM node:20-slim
|
|
COPY this-file-does-not-exist.js /app/
|
|
EOF
|
|
expect_nonzero "docker build with a COPY of a nonexistent file" docker build -q -f "$TMPDOCKERFILE" "$(dirname "$TMPDOCKERFILE")"
|
|
|
|
echo "== 4. Config validation failure =="
|
|
BAD_COMPOSE="$(mktemp -d)/docker-compose.yml"
|
|
cat > "$BAD_COMPOSE" <<'EOF'
|
|
services:
|
|
a:
|
|
image: alpine
|
|
a:
|
|
image: alpine
|
|
EOF
|
|
expect_nonzero "docker compose config on a file with a duplicate service key" docker compose -f "$BAD_COMPOSE" config
|
|
|
|
echo "== 5. Migration preflight failure =="
|
|
( cd platform/api && expect_nonzero "runMigrations() against an unreachable Postgres" \
|
|
env PLATFORM_DB_URL="postgres://nobody:[email protected]:1/nope" \
|
|
node -e "require('./src/db').runMigrations().then(()=>process.exit(0)).catch((e)=>{console.error(e.message);process.exit(1)})" )
|
|
|
|
echo "== 6. Staging smoke test failure =="
|
|
expect_nonzero "staging-smoke-test.sh with no STAGING_GATEWAY_URL set" \
|
|
env -u STAGING_GATEWAY_URL bash infra/scripts/staging-smoke-test.sh
|
|
expect_nonzero "staging-smoke-test.sh against an unreachable staging URL" \
|
|
env STAGING_GATEWAY_URL="http://127.0.0.1:1" bash infra/scripts/staging-smoke-test.sh
|
|
|
|
echo
|
|
if [ "$FAIL" = "1" ]; then
|
|
echo "DRILL FAILED — at least one failure mode did NOT fail closed. See above."
|
|
exit 1
|
|
fi
|
|
echo "OK: every listed failure mode genuinely exits nonzero — CI fails closed on all of them."
|
|
echo "NOT TESTED HERE: an actual Gitea Actions run (needs Gitea Actions enabled on the real"
|
|
echo "instance and this workflow file activated at .gitea/workflows/ — see build-and-test.yml's"
|
|
echo "own header comment). This proves the underlying commands, which is what actually"
|
|
echo "determines pass/fail once a real run happens."
|