diff --git a/.gitea/workflows/publish.yml b/.gitea/workflows/publish.yml index c7a9282..2bd244f 100644 --- a/.gitea/workflows/publish.yml +++ b/.gitea/workflows/publish.yml @@ -85,6 +85,60 @@ jobs: -t ${{ steps.tags.outputs.sha_tag }} \ . + # ── D-10 image hygiene assertions — run AFTER build, BEFORE push ─────────── + # A failure here stops the job before any push, so a regressed image can + # never be published (T-16-18 / T-16-19 / T-16-20). + - name: Image hygiene — static assertions + run: | + set -euo pipefail + # Assert .dockerignore exists + if [ ! -f ".dockerignore" ]; then + echo "FAIL: .dockerignore does not exist" + exit 1 + fi + # Assert every forbidden pattern is covered by .dockerignore + for pattern in ".env" "node_modules" "apps/api/scripts" ".git" ".planning" "apps/api/tests" "apps/pwa/e2e"; do + if ! grep -q "$pattern" .dockerignore; then + echo "FAIL: .dockerignore missing pattern: $pattern" + exit 1 + fi + done + # Assert this workflow still pins --target production (D-10 / T-16-19) + if ! grep -q "\-\-target production" .gitea/workflows/publish.yml; then + echo "FAIL: publish.yml does not build --target production" + exit 1 + fi + echo "Static image hygiene assertions PASSED." + + # Boot-smoke: run the freshly-built production image with the forbidden + # NODE_ENV=production + DEV_AUTH_BYPASS=true combo and assert it refuses to + # boot — proving the D-08 guard (assertNotDevBypassInProduction) fires in + # the ACTUAL shipped image (T-16-18 / T-16-21). + # EXIT==0 → image started → guard NOT working → FAIL + # EXIT==124 → timeout (15s) → guard not firing → FAIL + # Any other non-zero exit → image refused boot → PASS + - name: Image hygiene — boot-smoke (must refuse dev-bypass in production) + run: | + set -euo pipefail + IMAGE="${{ steps.tags.outputs.sha_tag }}" + set +e + timeout 15 docker run --rm \ + --env NODE_ENV=production \ + --env DEV_AUTH_BYPASS=true \ + "$IMAGE" \ + 2>&1 | head -20 + EXIT=$? + set -e + if [ "$EXIT" -eq 0 ]; then + echo "FAIL: Production image started successfully with DEV_AUTH_BYPASS=true — guard not working" + exit 1 + fi + if [ "$EXIT" -eq 124 ]; then + echo "FAIL: Production image did not exit within 15s — guard not firing" + exit 1 + fi + echo "PASS: Production image refused to start with DEV_AUTH_BYPASS=true (exit $EXIT)" + # Push the IMMUTABLE :- tag FIRST. set -euo pipefail stops on # the first failed push, so :latest is only moved after the immutable, # rollback-traceable tag has landed — a failed second push can never leave