The Containerfile said chainsaw "publishes no per-asset checksum file at a stable URL". Literally true, and misleading: it reads as "no integrity option exists". Checked the release assets via the GitHub API -- v0.2.12 ships per-asset cosign `.sig` + `.pem` and an SBOM alongside each tarball. So verification IS available, it just needs cosign in the builder stage and the right keyless identity for kyverno's release workflow. Comment now says what is actually true: kubectl is sha256-verified, chainsaw rests on tag + TLS, and the cosign path is named as a TODO rather than implied away. Deliberately not implemented here -- picking a trusted signer identity is a supply-chain decision that belongs in its own reviewed change, not a silent addition to a runner image. Noted that infra/devcontainer/Containerfile has the same gap for the same binary. Rebuilt after the edit; the in-image verification still passes. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
In-cluster conformance Job (L2/L3)
STATUS NOTE (2026-09-29) — read before trusting the "Phase 0" framing below. This document speaks as though no cluster exists and the platform is scaffolding. A three-node k3s cluster has existed for weeks and Flux has been reconciling against it. Gitea is live, the Actions runner
incluster-dindis online, andmainis a protected branch. Where the text says "Phase 0", "stub", or "no cluster exists", read it as describing a live gap rather than an expected stage — most importantly, L2/L3 conformance still does not run in CI (vars.CLUSTER_AVAILABLEis unset), which is a real coverage hole, not a phase.This mismatch is not cosmetic. A month of "the gate enforces this" claims rested on documentation rather than measurement, and
mainwas unprotected with zero runners registered the whole time. Query the live API before asserting platform state.
Per ADR-0011 and docs/14 "Where each layer runs": L0/L1 run in CI without a cluster, but L2 behavioral and L3 fault-injection run as an in-cluster Chainsaw Job on the dev/test cluster that CI triggers and waits for. CI holds no cluster credentials — the Job's own scoped ServiceAccount does (R-SEC-02).
Files
rbac.yaml— theconformancenamespace, theconformance-runnerServiceAccount, and a least-privilege ClusterRole skeleton. Rules start empty and are added — each commented with the test/requirement that needs it — only as the matching fault-injection test lands. This is a privileged test identity: never reused by workloads, its own audit item.chainsaw-job.yaml— the Job skeleton. Single entrypoint./run.sh; CI setsargsto--requirements <touched IDs>(merge gate) or--layer all --strict-coverage(phase exit). Itsimage:is STALE: it still namesghcr.io/kyverno/chainsaw:v0.2.12, which (a) cannot run./run.shat all and (b) is DENIED at admission. The trigger renders this template per run withCONFORMANCE_RUNNER_IMAGEinjected (plan §3 item 4, T-6.1b); until that lands, treat the literalimage:here as a placeholder, not a pin.Containerfile— theconformance-runnerimage:bash+python3+chainsaw+kubectl+git, non-root (numeric uid 65532) and working under a read-only root filesystem. This is what the Job actually needs; the stock chainsaw image carries none of it. Versions are--build-arg-injected frominfra/devcontainer/versions.env— the single source of truth — so the chainsaw/kubectl in the runner are the same pins the toolbox gate uses. Read its header before changing the Job'sargs:run.shruns the selected offline L0/L1 checks for any non-offline-only selection, and those need flux/promtool/kyverno/ kustomize, which this image deliberately omits. Use--layer l2/--layer l3.build-image.sh— builds the above, verifies the toolchain by running it inside the image as uid 65532 with--read-only, and prints the digest. It never pushes: it prints the exactpodman push --digestfile …command for the operator instead. Seeconformance/versions.envCONFORMANCE_RUNNER_IMAGEfor the post-push pin.
Flow
- CI (Gitea Actions, delivered in T-6.1b) creates a Job from
chainsaw-job.yaml(via a trigger with narrow rights: create Job + read its logs/status — not a broad kubeconfig). - The Job clones/loads the impl repo at the commit under test into
/workspaceand runs./run.shunderconformance-runner. run.shruns Chainsaw over the selected tests and writesreport.junit.xml+coverage/to the results volume (/workspace/conformance/_artifacts).- CI collects those artifacts (results volume or the terminating log stream, which echoes their paths) and enforces the merge / phase-exit gate on them.
Phase 0
No cluster exists, so nothing here is applied yet. These manifests are reconciled by Flux
from Phase 1+ and first exercised when L2/L3 can run (T-3.0 onward). In Phase 0 the same
run.sh runs in CI's no-cluster stage and emits a skipped JUnit + the coverage report.