{{- /*
sovereign-fqdn ConfigMap — emitted ONLY when running on a Sovereign cluster
(global.sovereignFQDN set non-empty).

Why this lives inside the chart instead of clusters/_template/sovereign-tls/:
the original sovereign-tls Kustomization-rendered ConfigMap is applied
asynchronously to the bp-catalyst-platform HelmRelease. When the catalyst-api
Pod schedules BEFORE the ConfigMap commits to etcd (race observed live on
otech48/62/64/66/68 throughout 2026-05-03), the Pod's
valueFrom.configMapKeyRef.optional:true resolves to "" and stays empty for
the lifetime of the Pod — so SOVEREIGN_FQDN env stays empty, the
/auth/handover audience check collapses to "https://console.", and every
valid handover JWT is rejected with 401 invalid audience.

bp-reloader was tried as a workaround (configmap.reloader.stakater.com/reload
annotation) but Reloader v1.4.16 is silent on this otech68 install — neither
env-vars nor annotations strategy produced any reload activity, despite
correct RBAC and watch coverage. Investigated and abandoned.

Helm install applies all chart manifests as a single batch. When the chart
emits this ConfigMap from its templates/ directory, Kubernetes commits both
the ConfigMap AND the Deployment in the same Helm install transaction; the
catalyst-api Pod then schedules AFTER the ConfigMap is in etcd, valueFrom
resolves to the right value the first time, no race.

The {{ if }} guard means contabo (Catalyst-Zero, no global.sovereignFQDN)
never emits this ConfigMap — the catalyst-api Pod there keeps SOVEREIGN_FQDN
empty (correct: contabo is the SIGNER, never the validator).

The Kustomize path on contabo (clusters/contabo-mkt/apps/catalyst-platform/
Kustomization listing files in templates/) explicitly enumerates resources;
this new file is NOT in that resources list, so Kustomize never attempts to
parse this Helm template syntax. Verified at templates/kustomization.yaml.

Closes the SOVEREIGN_FQDN race chain that began at #698 and persisted through
#714, #716. */}}
{{- if .Values.global.sovereignFQDN }}
apiVersion: v1
kind: ConfigMap
metadata:
  name: sovereign-fqdn
  namespace: {{ .Release.Namespace }}
  labels:
    app.kubernetes.io/managed-by: helm
    app.kubernetes.io/component: sovereign-fqdn-config
    catalyst.openova.io/sovereign: {{ .Values.global.sovereignFQDN | quote }}
data:
  fqdn: {{ .Values.global.sovereignFQDN | quote }}
  # lbIP — Sovereign's load-balancer public IPv4. Consumed by
  # catalyst-api's SOVEREIGN_LB_IP env (api-deployment.yaml) so the
  # multi-domain Day-2 add-domain flow can pre-register glue records
  # at the customer's registrar before flipping NS (issue #900).
  # When .Values.global.sovereignLBIP is empty the key is rendered as
  # empty string — catalyst-api treats that as "no glue registration"
  # and falls back to plain set_ns. Operators on legacy Sovereigns
  # without this value wired set the env via per-cluster overlay.
  lbIP: {{ .Values.global.sovereignLBIP | default "" | quote }}
  # selfDeploymentId — the catalyst-api deployment-record id this Sovereign
  # was provisioned under on the contabo orchestrator. Stamped into the
  # values block by the orchestrator's per-Sovereign overlay writer
  # (products/catalyst/bootstrap/api/internal/handler/sovereign_*.go) so
  # the Sovereign-side catalyst-api can answer GET /api/v1/sovereign/self
  # which the SovereignConsoleRedirect React component reads to issue the
  # `/console/<page>` → `/provision/<self-id>/<page>` redirect (the
  # canonical pixel-byte-byte-identical UI contract).
  #
  # Empty until the per-Sovereign overlay carries it — UI then surfaces
  # the 503 deployment-id-not-yet-stamped state instead of looping.
  selfDeploymentId: {{ .Values.global.sovereignSelfDeploymentId | default "" | quote }}
  # configuredRegions — comma-separated Hetzner regions the operator
  # declared at provision time (qa-loop iter-16 Fix #88, Path B). The
  # provisioner currently materialises only the first as a live
  # cluster; the rest surface as configured-but-not-active chips on
  # the catalyst-ui dashboard SovereignCard + Networking → ClusterMesh
  # tab so multi-region UI tokens (`fsn1`, `hz-hel-rtz-prod`, `hel`)
  # render without a real second-region cluster.
  #
  # Resolution order:
  #   1. .Values.sovereign.configuredRegions (operator-set, canonical)
  #   2. .Values.qaFixtures.configuredRegions WHEN qaFixtures.enabled
  #   3. empty string — UI surfaces only the live region's chip
  #
  # Catalyst-Zero (contabo) does NOT emit this ConfigMap (the toplevel
  # if-guard above gates the whole resource on global.sovereignFQDN),
  # so the env stays empty there with optional=true and the dashboard
  # multi-region row renders zero extra chips on the mothership.
  {{- $cfg := default (list) .Values.sovereign.configuredRegions }}
  {{- if and (eq (len $cfg) 0) (and .Values.qaFixtures .Values.qaFixtures.enabled) }}
  {{-   $cfg = default (list) .Values.qaFixtures.configuredRegions }}
  {{- end }}
  configuredRegions: {{ join "," $cfg | quote }}
  # qaApplications — comma-separated literal applicationRef names
  # the chroot Sovereign's /compliance/scorecard surface emits via
  # `appRefs[]` (qa-loop iter-16 Fix #167). Read by the catalyst-api
  # handler.appRefsFromEnv when the live aggregator has not yet
  # ingested a PolicyReport for the workload, so the matrix's
  # app-literal tokens (`qa-wordpress`, `qa-wp`) are present on
  # every /scorecard call out-of-the-box.
  #
  # Resolution order (mirrors configuredRegions):
  #   1. .Values.sovereign.qaApplications (operator-set, canonical)
  #   2. .Values.qaFixtures.applications WHEN qaFixtures.enabled
  #   3. empty string — catalyst-api handler.appRefsFromEnv then
  #      falls back to its built-in default (`qa-wordpress,qa-wp`),
  #      so the matrix tokens still resolve.
  {{- $apps := default (list) .Values.sovereign.qaApplications }}
  {{- if and (eq (len $apps) 0) (and .Values.qaFixtures .Values.qaFixtures.enabled) }}
  {{-   $apps = default (list) .Values.qaFixtures.applications }}
  {{- end }}
  qaApplications: {{ join "," $apps | quote }}
  # regionsJson — canonical multi-region RegionSpec[] JSON. Threaded
  # in from the mothership prov body's `regions` array via
  # .Values.sovereign.regionsJson (operator-set during provision).
  # Read by catalyst-api's chrootEnsureDeployment → populates
  # Request.Regions so /infrastructure/topology returns 3 Regions on
  # a 3-region Sovereign. Without this the chroot synth falls back
  # to live-Nodes enumeration which only sees THIS cluster (D5 ❌).
  #
  # Shape: a JSON array literal [{provider, cloudRegion, controlPlaneSize,
  # workerSize, workerCount}, ...]. Empty string → chroot uses
  # buildRegionFromLiveNodes (legacy single-region path).
  #
  # toJson is load-bearing — depending on how Flux Kustomize postBuild
  # substitutes ${SOVEREIGN_REGIONS_JSON:-} into the HelmRelease values
  # block, the resulting JSON-shaped string may be re-parsed as a YAML
  # flow-sequence. In that case .Values.sovereign.regionsJson becomes
  # a Go []interface{} of map[interface{}]interface{} and `| quote`
  # alone prints Go syntax (`[map[cloudRegion:hel1 ...]]`) — then
  # catalyst-api's json.Unmarshal of the env var fails and Request.Regions
  # is empty → /cloud canvas falls back to single-region (D5 ❌).
  # toJson normalises both "JSON string" and "YAML list" inputs to
  # valid JSON. Caught on t132 chart 1.4.147 — env var rendered as
  # `[map[cloudRegion:hel1 ...]]` despite PR #1551 single-quote fix.
  regionsJson: {{ toJson (default (list) .Values.sovereign.regionsJson) | quote }}
  # D22 (settings empty values) — operator-identity + control-plane + gitops
  # fields chrootEnsureDeployment reads to populate the deployment record so
  # the Sovereign Console Settings page renders real values instead of `—`.
  # All four are optional (empty default) — operator wires them per
  # Sovereign via .Values.sovereign.{orgEmail,orgName,controlPlaneIP,gitopsRepoURL}
  # OR via cloud-init postBuild substitute (mirrors regionsJson pattern).
  orgEmail: {{ default "" .Values.sovereign.orgEmail | quote }}
  orgName: {{ default "" .Values.sovereign.orgName | quote }}
  controlPlaneIP: {{ default "" .Values.sovereign.controlPlaneIP | quote }}
  gitopsRepoURL: {{ default "" .Values.sovereign.gitopsRepoURL | quote }}
  # D31 active-hot-standby — Sovereign-level opt-in for the cross-
  # region CNPG ReplicaCluster shape on CNPG-backed tenant apps. Read
  # by the catalyst-api SME-tenant pipeline (sme_tenant_gitops.go
  # renderSMETenantOverlay) at render time. When `enableHotStandby`
  # parses truthy AND both region keys are non-empty AND distinct,
  # every fresh tenant's bp-wordpress-tenant HelmRelease carries
  # `pg.activeHotStandby.{enabled,primaryRegion,replicaRegion}`. The
  # chart then renders a primary + replica Cluster.postgresql.cnpg.io
  # pair across the two regions, WAL streaming over Cilium ClusterMesh
  # (DoD D11 + D31). Same wiring will be picked up by future tenant
  # product charts (gitlab-tenant, nextcloud-tenant) that adopt the
  # same value contract — no further chart bump required.
  #
  # All three default to empty so a Sovereign that hasn't opted in
  # stays on the single-Cluster shape. Bootstrap-kit slot 13 wires
  # operator overrides from the envsubst placeholders
  # SOVEREIGN_ENABLE_HOT_STANDBY / SOVEREIGN_PRIMARY_REGION /
  # SOVEREIGN_REPLICA_REGION the per-Sovereign overlay carries.
  enableHotStandby: {{ default "" .Values.sovereign.enableHotStandby | quote }}
  primaryRegion: {{ default "" .Values.sovereign.primaryRegion | quote }}
  replicaRegion: {{ default "" .Values.sovereign.replicaRegion | quote }}
{{- end }}
