--- # Flux Kustomization: PLATFORM services (cluster-wide). Reconciles ./platform. # Phase 0 STUB. Realizes R-GITOPS-01/03 (pull-based reconcile + drift correction) and # R-ENV-02 (env differences supplied ONLY via substituteFrom cluster-vars). apiVersion: kustomize.toolkit.fluxcd.io/v1 kind: Kustomization metadata: name: platform namespace: flux-system spec: interval: 10m retryInterval: 2m timeout: 5m # Cilium was moved OUT of this tier into its own Kustomization (clusters/base/cilium.yaml) # on 2026-09-01. `prune: true` is restored here now that the ownership transfer is VERIFIED # live: Namespace/kube-system, the Cilium HelmRelease and the HelmRepository all carry # `kustomize.toolkit.fluxcd.io/name=cilium`, this Kustomization's inventory no longer lists # any of the three, and the `cilium` Kustomization's inventory holds exactly those three. # # LESSON WORTH KEEPING: `platform/cilium/namespace.yaml` was the ONLY declaration of # Namespace/kube-system under ./platform. Moving that path out of a prune:true Kustomization # made the stale set `delete namespace kube-system` — coredns, metrics-server, local-path AND # Cilium. Before moving ANY platform/* component in future, check whether it is the sole # declarer of a shared Namespace; that turns a component-scoped prune into a cluster-scoped one. prune: true wait: true # block `apps` until platform is healthy # ORDERING FIX (2026-09-01 incident: a substitution race that failed OPEN broke Cilium). # # `cluster-vars` — the ConfigMap every substituting Kustomization reads — is APPLIED by the root # `flux-system` Kustomization (clusters//sync.yaml), not by this one. Without an edge here, # `platform` could render a manifest from revision N against the revision N-1 `cluster-vars`, so a # NEWLY ADDED knob was undefined and (non-strict substitution) silently became the EMPTY STRING. # That is how `k8sServiceHost: ""` reached the Cilium HelmRelease. # # `dependsOn` is REVISION-gated, not merely Ready-gated (verified against # kustomize-controller v1.4.0 `kustomization_controller.go :: checkDependencies`, our version): # when the dependency shares a GitRepository with the dependent, the dependent cannot reconcile # revision N until the dependency has APPLIED revision N. Applying revision N is exactly what # writes a newly-added `cluster-vars` knob — so this one edge closes the race. # # `platform` is the ONLY Kustomization in the graph with no `dependsOn`; every other substituting # consumer (platform-config, apps-wave-0/1/2, apps-vms, oauth2-proxy, headlamp, homepage, crafty, # velero) chains transitively through it. One edge therefore protects the whole graph. # # TRADE-OFF — READ THIS BEFORE YOU CURSE IT: the root is now a GATE. A malformed manifest # ANYWHERE under the root's path (clusters/, which includes clusters/base) fails the root # Kustomization and therefore stops `platform` and EVERYTHING downstream, where before this edge # the rest of the graph would have kept reconciling. That is deliberate and fail-closed: a broken # entrypoint should stop the line, not let consumers render against a half-applied variable set. # No deadlock risk — the root runs `wait: false`, so it reaches Ready on APPLY success and never # health-gates its own children (see clusters/dev/sync.yaml). dependsOn: - name: flux-system # the ROOT Kustomization (sync.yaml) that applies cluster-vars # THE CNI GATES THE TIER. Cilium was split out of ./platform into its own Kustomization # (clusters/base/cilium.yaml, wait:true) so its health has its own Ready condition and its own # suspend valve. This edge preserves the ordering that co-tenancy used to provide implicitly: # nothing in `platform` — and therefore nothing anywhere downstream — reconciles until the CNI # is healthy. It is also revision-gated, which is what makes the prune-safety argument above # deterministic rather than a race. - name: cilium sourceRef: kind: GitRepository name: flux-system path: ./platform # R-ENV-02: the ONLY place environment differences enter platform manifests. # dev/prod overlays patch the cluster-vars ConfigMap; the base manifests are never forked. postBuild: substituteFrom: - kind: ConfigMap name: cluster-vars # TODO(WS-SEC, Phase 1): enable SOPS decryption once the age key exists (ADR-0007). # decryption: # provider: sops # secretRef: # name: sops-age # TODO(PM): the top-level ./platform/kustomization.yaml must include platform/kyverno/ # (owned by the policy engineer, WS-TEST). See platform/README.md integration TODO.