# .gitea/workflows/merge-gate.yaml # CI merge gate (T-0.2, R-GITOPS-01, R-TEST-02/03) for the home k3s + Longhorn # platform. Enforces docs/14 (Testing & Conformance) gating rules as Gitea Actions. # # Pipeline (docs/14 "Where each layer runs"): # lint -> schema -> kyverno-L0 -> render==golden -> L1-render [no cluster; live now] # | # v # conformance-L2-L3 (in-cluster Chainsaw Job) # | [cluster-gated; SKIPS in Phase 0] # v # merge-gate decision [R-TEST-03: touched subset green] # # STATUS (corrected 2026-09-29): Gitea IS hosted and Gitea Actions IS enabled; the runner # `incluster-dind` is online and gate.yml is the live PR gate. The text that used to sit here # ("Today there is no Gitea host, so this file is dormant") was stale by weeks -- it was written # as a plan and never revisited once the plan completed. # # THIS file is still dormant, but for the reason in the DORMANT block below -- its spec checkout # and L2/L3 seams are unwired -- NOT because there is no host. Do not read a stale plan as a # statement about the live platform; query the API instead (see infra/gitea/). # # CI holds NO broad kubeconfig. L2/L3 run as an in-cluster Chainsaw Job whose own # scoped ServiceAccount holds cluster access (docs/14, R-SEC-02); CI uses only a # narrow trigger credential referenced BY SECRET NAME below. # # ───────────────────────────────────────────────────────────────────────────── # DORMANT (Phase 6, WS-GITOSS): the LIVE enforced gate is now gate.yml. This file is # retained as the full-pipeline BLUEPRINT (spec-checkout via SPEC_REPO_SLUG + the L2/L3 # conformance job + the R-TEST-03 decision job) for when those seams are wired: # - SPEC_REPO_SLUG must be set as a Gitea repo/org variable AND the spec repo hosted on # this Gitea, or spec-immutability's second checkout fails the whole run. # - vars.CLUSTER_AVAILABLE + a scoped conformance trigger credential must exist. # Until then it must NOT auto-run (its spec checkout would fail with SPEC_REPO_SLUG unset), # so its triggers are reduced to manual `workflow_dispatch`. Re-enable by restoring the # pull_request/push triggers once the seams above are wired. # ───────────────────────────────────────────────────────────────────────────── --- name: merge-gate on: workflow_dispatch: {} # DORMANT blueprint — manual-only; gate.yml is the live gate concurrency: group: merge-gate-${{ github.ref }} cancel-in-progress: true # Repo/org variables & secrets (set in Gitea, referenced by NAME only — no plaintext): # vars.CLUSTER_AVAILABLE 'true' once a dev/test cluster exists (Phase 1+); else L2/L3 SKIP # secrets.CONFORMANCE_TRIGGER_KUBECONFIG narrow creds to CREATE/AWAIT the in-cluster Chainsaw Job only env: CLUSTER_AVAILABLE: ${{ vars.CLUSTER_AVAILABLE }} jobs: # R-TEST-06 hard gate: the specification is read-only to agents. Fail the whole run # if the spec repo is modified or has drifted from ci/SPEC_PIN. Runs first; everything # else depends on it. Requires the spec repo checked out beside the impl repo — wired # for when Gitea hosts both. If the spec repo is unavailable the guard SKIPS (exit 3), # tolerated here (surfaced, never a fake green); a real violation (exit 1) blocks. spec-immutability: runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 # the implementation repo - name: Check out the read-only spec (sibling path) uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: repository: ${{ vars.SPEC_REPO_SLUG }} # e.g. Home-Network/home-network-k3s (set in Gitea) path: ../home-network-k3s fetch-depth: 0 - name: Verify spec immutability (R-TEST-06) run: make -C ci spec-immutability || [ $? -eq 3 ] lint: runs-on: ubuntu-latest needs: [spec-immutability] steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: { fetch-depth: 0 } # full history so touched-requirement diff works - name: Install pinned tools run: ./.gitea/workflows/scripts/install-tools.sh lint - name: yaml/lint run: make -C ci lint l0: runs-on: ubuntu-latest needs: [lint] steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: { fetch-depth: 0 } - name: Install pinned tools run: ./.gitea/workflows/scripts/install-tools.sh l0 - name: Schema validation + Kyverno L0 run: make -C ci l0 render-golden: runs-on: ubuntu-latest needs: [l0] steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: { fetch-depth: 0 } - name: Install pinned tools run: ./.gitea/workflows/scripts/install-tools.sh l1 - name: render==golden reproducibility (canonical render vs committed apps/) run: make -C ci render-golden l1: runs-on: ubuntu-latest needs: [render-golden] steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: { fetch-depth: 0 } - name: Install pinned tools run: ./.gitea/workflows/scripts/install-tools.sh l1 - name: L1 rendering checks run: make -C ci l1 - name: Upload L1 artifacts if: always() uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 with: name: l1-junit-coverage path: | results/junit/*.xml reports/coverage/** if-no-files-found: ignore # L2/L3 behavioral + fault-injection. Runs ONLY when a cluster is declared. # In Phase 0 (CLUSTER_AVAILABLE unset) this job is SKIPPED by Gitea — shown as # skipped, never as passed. It never fakes green (HARD RULE 4). conformance: runs-on: ubuntu-latest needs: [l1] if: ${{ vars.CLUSTER_AVAILABLE == 'true' }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: { fetch-depth: 0 } - name: Install pinned tools run: ./.gitea/workflows/scripts/install-tools.sh conformance - name: Trigger in-cluster Chainsaw Job (L2/L3) and collect artifacts env: # Narrow trigger credential; the Job's scoped SA holds real cluster access. CONFORMANCE_TRIGGER_KUBECONFIG: ${{ secrets.CONFORMANCE_TRIGGER_KUBECONFIG }} run: make -C ci conformance - name: Upload L2/L3 artifacts if: always() uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 with: name: conformance-junit-coverage path: | results/junit/*.xml reports/coverage/** if-no-files-found: ignore # Final decision. Applies R-TEST-03 honestly over the upstream job results: # any L0/L1 failure -> BLOCK # conformance failed -> BLOCK # conformance skipped + cluster available -> BLOCK (should have run) # conformance skipped + no cluster -> report UNVERIFIED (Phase 0), do not fake green merge-gate: runs-on: ubuntu-latest needs: [lint, l0, render-golden, l1, conformance] if: always() steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - name: Decide env: R_LINT: ${{ needs.lint.result }} R_L0: ${{ needs.l0.result }} R_L1: ${{ needs.l1.result }} R_CONF: ${{ needs.conformance.result }} run: ./.gitea/workflows/scripts/decide.sh