# ============================================================================= # L0 STATIC — Flux postBuild substitution is complete and fail-closed-safe # Catalog: docs/14 L0 row ("Pre-apply, in CI") — runs WITHOUT a cluster. # # R-ENV-02 ("authored once, promoted dev -> prod") is implemented by ONE mechanism: # `postBuild.substituteFrom cluster-vars` injecting `${TOKEN}`s into the manifests each # Flux Kustomization renders. That mechanism FAILS OPEN — an undefined variable becomes # the empty string while the Kustomization still reports Ready — which emptied Cilium's # k8sServiceHost and broke the CNI on 2/3 dev nodes (DECISIONS-LOG 2026-09-01). # # GIVEN every Flux Kustomization under clusters/ that declares postBuild.substituteFrom, # WHEN each `spec.path` is resolved to the exact file set it renders (following # kustomization.yaml `resources:` transitively, not globbing the repo), # THEN every `${...}` in those files is either DEFINED in clusters/base/cluster-vars.yaml # or deliberately ESCAPED `$${...}`; no unescaped expansion carries a default # operator on an undefined name (the silent-empty class strict mode does NOT catch); # no unescaped expansion is unparseable by envsubst (which fails the WHOLE # Kustomization); every path rendering a knob actually substitutes cluster-vars; # every overlay-patched knob has a base default; and the check is wired into the # merge gate as a blocking, unconditionally-run stage. # # This is the prerequisite proof for enabling kustomize-controller's # StrictPostBuildSubstitutions feature gate, and the permanent guard that keeps it safe. # # STATUS: EXECUTABLE. `check.sh` is a pure offline assertion script (standard library # only); conformance/run.sh --layer l0 and ci/l0.sh run it directly, and this Chainsaw # Test wraps the SAME script. # # NOT proven (named in check.sh's header): this is a STATIC scan of the SOURCE manifests # a path includes — a deliberate superset of the post-`kustomize build` stream. It does # not render, does not prove the feature gate is enabled on the live kustomize-controller, # and does not validate knob VALUES. R-GITOPS-01 is tagged for the narrow static slice # only: what git says a Kustomization renders is fully determined, with no variable # silently emptied on the way in. The live-state half is L2, untested today. # ============================================================================= apiVersion: chainsaw.kyverno.io/v1alpha1 kind: Test metadata: name: flux-postbuild-substitution labels: conformance.home.k3s/R-ENV-02: "true" conformance.home.k3s/R-GITOPS-01: "true" annotations: conformance.home.k3s/requirements: "R-ENV-02, R-GITOPS-01" conformance.home.k3s/layer: "L0" conformance.home.k3s/safety-critical: "false" conformance.home.k3s/executable: "true" conformance.home.k3s/intent: >- Every variable a substituted Flux path renders is defined in cluster-vars or escaped, so post-build substitution cannot silently empty a value and enabling StrictPostBuildSubstitutions cannot fail a legitimate manifest. spec: steps: - name: audit-every-substituted-flux-path description: >- Resolve each postBuild.substituteFrom Kustomization to the files it renders and assert every ${...} is defined or escaped, then assert the gate wires it blocking. try: - script: content: bash ./check.sh