# .gitea/workflows/gate.yml — the ENFORCED PR merge gate (Phase 6, WS-GITOSS). # # This is the LIVE gate wired to the in-cluster Gitea Actions runner (ns gitea-actions, # Docker executor + DinD). It invokes the AGGREGATE merge gate — `make -C ci gate` — so CI # runs BYTE-FOR-BYTE the same command a human runs in the devcontainer (R-TEST-02): the # runner just installs the pinned tools from ci/versions.env first. `make -C ci gate` # (ci/gate.sh) runs every stage in order — spec-immutability, lint, Kyverno L0, # render==golden, L1 offline render — and applies the R-TEST-03 decision, printing # "MERGE GATE: PASS" / "MERGE GATE: BLOCKED". It is the single required status check on # `main` (see branch protection). A non-zero exit fails the job, so a BLOCKED gate blocks # the merge. # # The cluster-gated L2/L3 conformance stage SKIPS honestly here (exit 3, surfaced not faked # — HARD RULE 4) because this runner declares no cluster: `make -C ci gate` does not set # CLUSTER_AVAILABLE, so gate.sh reports the touched requirements UNVERIFIED without blocking # Phase-0/6 static work. L2/L3 run as an IN-CLUSTER Chainsaw Job (its own scoped SA; CI holds # no broad kubeconfig) driven by the separate `conformance` job below. # # NOTE (R-TEST-06 / gate-semantics): spec-immutability is ALSO surfaced as its own explicit # early step (below) so its result is a distinct, tolerant status in the Gitea UI; gate.sh # re-runs it as its first stage, which is the authoritative one. `make -C ci gate` here is # the byte-identical laptop gate; the extra pre-step is a read-only git-diff check and is # purely additive. The full pipeline blueprint (spec-checkout, per-layer jobs, decision job) # is retained dormant in merge-gate.yaml. --- name: gate on: pull_request: branches: [main] push: branches: - main - 'feature/**' - 'fix/**' - 'docs/**' - 'chore/**' concurrency: group: gate-${{ github.ref }} cancel-in-progress: true jobs: # Single required status check: the static merge gate. gate: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 # full history so ci/touched-requirements.sh can diff vs main - name: Install pinned CI toolchain (ci/versions.env) run: | ./.gitea/workflows/scripts/install-tools.sh l0 ./.gitea/workflows/scripts/install-tools.sh l1 # R-TEST-06: the spec is read-only to agents. An EXPLICIT early surfacing of the # spec-immutability check, so its result is a distinct, tolerant status line in the # Gitea UI before the (longer) aggregate gate log. The spec repo is NOT checked out # into CI here (separate repo; SPEC_REPO_SLUG not wired into this runner), so the guard # honestly SKIPS (exit 3) rather than faking green; a real drift/edit of a co-located # spec exits 1 and fails. Tolerated: exit 3 only. Call the script DIRECTLY, not via # `make spec-immutability`: GNU make masks a recipe's exit code as its own 2, so # `make spec-immutability || [ $? -eq 3 ]` would see 2 and fail. The AUTHORITATIVE # spec-immutability run is the FIRST stage of `make -C ci gate` below (gate.sh calls # the script directly, so it sees exit 3 → SKIP, not a mask); this pre-step is a cheap, # read-only git-diff duplicate that only adds early UI signal. - name: Spec immutability (R-TEST-06, skips if spec repo absent) run: ./ci/spec-immutability.sh || [ "$?" -eq 3 ] # Aggregate merge gate: byte-for-byte the command a human runs in the devcontainer # (`make -C ci gate` -> ci/gate.sh). Runs spec-immutability, lint, Kyverno L0, # render==golden and L1 render, then the R-TEST-03 decision. Exits non-zero on a # BLOCKED gate, which fails this job. The cluster-gated L2/L3 stage SKIPS here (no # CLUSTER_AVAILABLE) — surfaced as UNVERIFIED, never faked green (see header). - name: Merge gate — make -C ci gate run: make -C ci gate # NOTE: no actions/upload-artifact step. Gitea Actions rejects upload-artifact@v4 # ("not supported on GHES" — Gitea does not implement the artifact-v2 backend), which # would fail the job even on a green gate. JUnit + coverage are written under # conformance/_artifacts/ in the job workspace; wire artifact upload later via the # Gitea-compatible @v3 action or Gitea's own artifact backend if retention is wanted. # ─────────────────────────────────────────────────────────────────────────── # PLACEHOLDER — NOT WIRED (deferred; concrete plan written, awaiting operator decision). # L2/L3 conformance (Chainsaw behavioural + fault-injection) needs a LIVE cluster and is # gated behind vars.CLUSTER_AVAILABLE + a narrow secrets.CONFORMANCE_TRIGGER_KUBECONFIG. # The intended model: an in-cluster Chainsaw Job (conformance/incluster/) under its OWN # scoped ServiceAccount — CI holds NO broad kubeconfig, only a trigger credential. The # blocker is a real privilege-escalation design question (a `create jobs` credential can # create a Job running as the privileged runner SA), plus a runner image + repo-clone + # results-collection that don't exist yet. Full analysis + step sequence: # docs/plans/l2-l3-conformance-ci-wiring.md # The blueprint job shape is retained dormant in merge-gate.yaml. # Kept `if: false` so it never runs (shows skipped, never fakes green — HARD RULE 4). # ─────────────────────────────────────────────────────────────────────────── conformance: runs-on: ubuntu-latest needs: [gate] if: false steps: - name: Placeholder run: | echo "L2/L3 conformance not wired — see gate.yml header + WS-GITOSS report." >&2 exit 1