#!/usr/bin/env bash # ci/gate.sh — the merge-gate decision. Runs every stage in order and applies the # R-TEST-03 rule: a merge is allowed only when the touched-requirement subset is # GREEN. Honestly distinguishes three outcomes per stage: # pass (0) green # fail (1) BLOCKS the merge # skip (3) not run (cluster-gated stage w/ no cluster, or unwired seam) — # NOT counted as green (HARD RULE 4: don't fake green) # # Phase 0 policy (no cluster): L0/L1 stages (lint, schema, kyverno, L1 render) # MUST be green. L2/L3 SKIP. If the touched subset needs L2/L3 and no cluster is # available, the gate reports those requirements UNVERIFIED and — unless # REQUIRE_CLUSTER_TESTS=true — does not hard-block Phase-0 bootstrap work, while # making the gap loud. Set REQUIRE_CLUSTER_TESTS=true (Phase 1+) to make any # skipped, cluster-needing touched requirement a hard block. set -uo pipefail # NOTE: no -e; we want to run all stages and aggregate. CI_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # shellcheck disable=SC1091 source "${CI_DIR}/lib.sh" set +e # lib.sh enables -e; the gate must run ALL stages and aggregate, not abort. : "${REQUIRE_CLUSTER_TESTS:=false}" declare -A OUTCOME run_stage() { local name="$1"; shift log "==== stage: ${name} ====" "$@" local rc=$? case "$rc" in 0) OUTCOME[$name]="pass"; ok "stage ${name}: pass" ;; 3) OUTCOME[$name]="skip"; skip "stage ${name}: SKIPPED (not run)" ;; *) OUTCOME[$name]="fail"; err "stage ${name}: FAIL" ;; esac } summary "## Merge gate (R-TEST-03)" summary "" run_stage spec-immutability "${CI_DIR}/spec-immutability.sh" run_stage lint "${CI_DIR}/lint.sh" run_stage l0 "${CI_DIR}/l0.sh" run_stage l1 "${CI_DIR}/l1.sh" run_stage conformance "${CI_DIR}/conformance.sh" echo >&2 log "==== gate decision ====" BLOCK=0 # spec-immutability (R-TEST-06): a FAIL always blocks (the spec was modified/drifted). # A skip means the spec repo wasn't available to verify — surfaced, not green. case "${OUTCOME[spec-immutability]}" in fail) err "spec-immutability: FAILED -> blocks merge (R-TEST-06)"; BLOCK=1 ;; skip) warn "spec-immutability: skipped (spec repo unavailable) — not verified" ;; pass) ok "spec-immutability: green" ;; esac # L0/L1 stages: any failure blocks. A SKIP here means a seam is unwired — in # Phase 0 that is tolerated (placeholders), but surfaced. for s in lint l0 l1; do case "${OUTCOME[$s]}" in fail) err "${s}: FAILED -> blocks merge"; BLOCK=1 ;; skip) warn "${s}: skipped (unwired seam / missing tool) — not green" ;; pass) ok "${s}: green" ;; esac done # L2/L3 conformance: cluster-gated. case "${OUTCOME[conformance]}" in fail) err "conformance (L2/L3): FAILED -> blocks merge"; BLOCK=1 ;; pass) ok "conformance (L2/L3): green" ;; skip) if cluster_available; then err "conformance: skipped despite CLUSTER_AVAILABLE=true -> blocks merge"; BLOCK=1 else mapfile -t reqs < <("${CI_DIR}/touched-requirements.sh" || true) if [ "${#reqs[@]}" -gt 0 ]; then warn "conformance L2/L3 UNVERIFIED for touched requirements: ${reqs[*]}" warn " (no cluster — Phase 0). These are NOT certified green." summary "- **UNVERIFIED (no cluster):** ${reqs[*]}" if [ "$REQUIRE_CLUSTER_TESTS" = "true" ]; then err "REQUIRE_CLUSTER_TESTS=true and cluster-needing requirements are unverified -> blocks merge" BLOCK=1 fi else skip "conformance L2/L3 skipped; no cluster-needing requirements touched" fi fi ;; esac echo >&2 if [ "$BLOCK" -eq 0 ]; then ok "MERGE GATE: PASS (touched-requirement subset green for the layers that can run)" summary "" summary "**Result: PASS** — L0/L1 green. L2/L3 status per cluster availability above." exit 0 else err "MERGE GATE: BLOCKED" summary "" summary "**Result: BLOCKED** — see failures above." exit 1 fi