#!/usr/bin/env bash # ci/l1.sh — Stage 4: L1 rendering checks. Runs in CI WITHOUT a cluster # (docs/14: "L1 rendering assertions run in CI without a cluster"). # # L1 asserts ServiceDefinition -> rendered resources match golden expectations # (e.g. `egress: vpn` renders the kill-switched egress path). The actual render # + assert is Chainsaw's L1 layer, owned by the conformance harness (T-0.4). # CI just invokes the runner filtered to the L1 layer and the touched subset. set -euo pipefail CI_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # shellcheck disable=SC1091 source "${CI_DIR}/lib.sh" log "Stage: L1 rendering checks (no cluster)" mkartifacts if ! entrypoint_ready "$CONFORMANCE_CMD"; then skip "conformance runner not present at '${CONFORMANCE_CMD}' (T-0.4 / conformance/ not delivered yet) — UNWIRED, not green" summary "- l1-render: **SKIPPED** (conformance runner missing — T-0.4)" exit 3 fi # Touched-requirement subset (R-TEST-03). An empty OR undeterminable subset => run all # L1 as a superset sanity net, and SAY SO — see load_touched_requirements in ci/lib.sh. load_touched_requirements req_args=(${REQ_ARGS[@]+"${REQ_ARGS[@]}"}) # RECONCILED (PM 2026-08-16): conformance/run.sh's real interface is `--layer ` # [--requirements R-A,R-B]; it ALWAYS writes artifacts to a FIXED location # (conformance/_artifacts/) and does NOT accept --junit/--coverage. Invoke by layer + # touched subset, then COLLECT its artifacts into the CI artifact dirs. # --include-scope: union the touched subset with every L1 test DECLARING # `conformance.home.k3s/scope: whole-repo`. No L1 test declares it today, so this is a no-op # now — it is wired HERE, once, so the NEXT whole-repo invariant (at any layer) is picked up by # declaring the annotation, instead of needing someone to remember to hand-write a stage. That # "someone must remember" is the failure mode this whole change removes; see # conformance/lib/common.sh. rc 3 = a scope-declared check SKIPPED = UNWIRED, never a pass. : "${WHOLE_REPO_SCOPE:=whole-repo}" l1_rc=0 "$CONFORMANCE_CMD" --layer l1 --include-scope "$WHOLE_REPO_SCOPE" \ ${req_args[@]+"${req_args[@]}"} || l1_rc=$? CONF_ART="$(dirname "$CONFORMANCE_CMD")/_artifacts" if [ -f "${CONF_ART}/report.junit.xml" ]; then cp "${CONF_ART}/report.junit.xml" "${JUNIT_DIR}/l1.junit.xml" fi if [ -d "${CONF_ART}/coverage" ]; then cp -r "${CONF_ART}/coverage/." "${COVERAGE_DIR}/" fi case "$l1_rc" in 0) ok "L1 rendering checks passed" summary "- l1-render: **passed**" emit_offline_summary ;; 3) skip "an L1 check declaring scope='${WHOLE_REPO_SCOPE}' SKIPPED — UNWIRED, not a pass" summary "- l1-render: **SKIPPED** (a whole-repo check is UNWIRED — not a pass)" emit_offline_summary exit 3 ;; *) err "L1 rendering checks FAILED (rc=${l1_rc})" summary "- l1-render: **FAILED**" emit_offline_summary exit "$l1_rc" ;; esac