{{- if .Values.trigger.auto }}
{{- /*
Step 10 — auto-trigger Job (issue #933, refined per issue #935 Bug 2).

Helm post-install / post-upgrade hook that fires the cutover automatically
after the chart's ConfigMaps + RBAC land. Founder rule: handover is not
"done" until cutover has run; the operator must NOT have to click a CTA
on console.<sov-fqdn>/console/dashboard.

Auth path (issue #935 Bug 2)
────────────────────────────
The auto-trigger Job has no human session cookie — it's a pod with a
projected ServiceAccount token. The 0.1.16 implementation POSTed
/api/v1/sovereign/cutover/start unauthenticated, which sat behind
RequireSession middleware and 401'd forever on otech113 2026-05-05.

0.1.17 routes through a SEPARATE catalyst-api endpoint:
  POST /api/v1/internal/cutover/trigger

…which lives OUTSIDE RequireSession and validates the bearer token via
the apiserver's TokenReview API. Same engine, same idempotency, same
state machine — just an in-cluster auth surface (SA token) instead of
the operator session-cookie surface. See
products/catalyst/bootstrap/api/internal/handler/cutover_internal.go.

The SA token mounts at the standard projected-volume path
/var/run/secrets/kubernetes.io/serviceaccount/token; the script reads
the bytes at request time so the token rotates correctly on long-
running deadlines.

Readiness probe (issue #957 — chart 0.1.18)
───────────────────────────────────────────
0.1.17's readiness loop polled /api/v1/sovereign/cutover/status to
decide "is catalyst-api up yet?" That endpoint lives INSIDE the
RequireSession group and returned 401 to every unauthenticated probe
from this Pod. The probe treated 401 as "API not ready" → loop never
broke → /internal/cutover/trigger was NEVER called → cutover never
fired on otech113 2026-05-05. Live evidence: catalyst-api access log
showed 30 consecutive 401 responses on /sovereign/cutover/status from
the auto-trigger Pod and zero hits on /internal/cutover/trigger.

0.1.18 polls /healthz instead — an unauthenticated, always-200
endpoint while the process is up (products/catalyst/bootstrap/api/
internal/handler/health.go:51). The endpoint exists for exactly this
purpose (kubelet liveness, in-cluster reachability tests). Once
/healthz returns 200 we proceed straight to /internal/cutover/trigger;
the trigger endpoint is itself idempotent — it returns 200 with the
existing snapshot when cutoverComplete=true, 409 when a run is
already in progress, 200 otherwise — so we no longer need a separate
"cutoverComplete already true" pre-flight read.

Idempotency comes from catalyst-api's own engine logic:
  - cutoverComplete=true → 200, no Jobs created (already done)
  - cutoverComplete=false + steps in flight → 409 (in-progress)
  - cutoverComplete=false + no run → 200, kicks off the engine

So this Job can run on every chart install AND every upgrade without
side effects: it asks catalyst-api to start, and catalyst-api decides
whether anything actually needs to happen.

Hook ordering (helm.sh/hook-weight=20):
  - The chart's ConfigMaps + RBAC + DaemonSet land at default weight 0
    via normal install (no helm.sh/hook on those — they're regular
    resources, not hooks).
  - Helm orders hooks AFTER regular resources at install time, then
    by hook-weight ascending. Weight 20 leaves room for other charts
    to insert pre-cutover hooks at lower weights if needed.

Why a Job (not an init container on catalyst-api):
  - catalyst-api is provisioned at Phase-1 (long before the cutover
    chart). We can't tell catalyst-api "auto-trigger when you install
    the cutover chart" because catalyst-api has no awareness of which
    sub-charts have landed when.
  - A chart-side Helm hook is the canonical seam: "this chart has
    finished installing, fire its trigger." That's exactly what
    helm.sh/hook=post-install was designed for.

Why it doesn't crash a still-tethered Sovereign:
  - The catalyst-api /trigger endpoint returns 200 if cutoverComplete=true,
    409 if a cutover is already running, and 424 (FailedDependency) if
    the step ConfigMaps haven't landed yet (impossible here — we run
    AFTER them by hook ordering).
  - Network failure or catalyst-api unreachable at hook time? We poll
    with a generous timeout (5 minutes) and exit 0 even on timeout
    so a slow Sovereign cold-start doesn't fail the chart install. The
    operator can re-trigger via the CTA if auto-trigger never lands.

Re-trigger semantics:
  - On chart upgrade, this hook fires again. catalyst-api treats the
    re-trigger idempotently (skips already-success steps via the
    durable status ConfigMap). Safe to fire repeatedly.

Operator override:
  - Set trigger.auto=false in the per-Sovereign overlay to disable the
    auto-trigger and require the manual CTA. Default: true (founder rule).
*/ -}}
apiVersion: batch/v1
kind: Job
metadata:
  name: {{ include "bp-self-sovereign-cutover.name" . }}-auto-trigger
  namespace: {{ .Release.Namespace }}
  labels:
    {{- include "bp-self-sovereign-cutover.labels" . | nindent 4 }}
    app.kubernetes.io/component: cutover-auto-trigger
  annotations:
    "helm.sh/hook": "post-install,post-upgrade"
    "helm.sh/hook-weight": "20"
    "helm.sh/hook-delete-policy": "before-hook-creation,hook-succeeded"
spec:
  backoffLimit: 3
  ttlSecondsAfterFinished: {{ .Values.trigger.autoJobTTLSeconds | default 86400 }}
  activeDeadlineSeconds: {{ .Values.trigger.autoTimeoutSeconds | default 600 }}
  template:
    metadata:
      labels:
        app.kubernetes.io/name: {{ include "bp-self-sovereign-cutover.name" . }}
        app.kubernetes.io/component: cutover-auto-trigger
        catalyst.openova.io/blueprint: bp-self-sovereign-cutover
    spec:
      serviceAccountName: {{ include "bp-self-sovereign-cutover.serviceAccountName" . }}
      restartPolicy: Never
      containers:
        - name: trigger
          image: {{ include "bp-self-sovereign-cutover.image" (dict "repository" .Values.images.curl.repository "tag" .Values.images.curl.tag "cutoverPhase" "pre" "Values" .Values) }}
          imagePullPolicy: IfNotPresent
          env:
            - name: CATALYST_API_URL
              value: {{ .Values.trigger.catalystAPIURL | quote }}
            - name: AUTO_DELAY_SECONDS
              value: {{ .Values.trigger.autoDelaySeconds | default 0 | quote }}
            - name: WAIT_TIMEOUT_SECONDS
              value: {{ .Values.trigger.autoWaitForAPISeconds | default 300 | quote }}
          command: ["/bin/sh", "-c"]
          args:
            - |
              set -eu

              # Optional pre-flight delay to let just-handed-over operators
              # confirm the dashboard renders before the cutover hides the
              # CTA. autoDelaySeconds=0 means "fire immediately".
              if [ "${AUTO_DELAY_SECONDS}" -gt 0 ]; then
                echo "[auto-trigger] sleeping ${AUTO_DELAY_SECONDS}s before firing cutover"
                sleep "${AUTO_DELAY_SECONDS}"
              fi

              api="${CATALYST_API_URL%/}"
              # Issue #957 — chart 0.1.18: poll the unauthenticated
              # /healthz endpoint to test reachability. /api/v1/sovereign/
              # cutover/status (used in 0.1.17) lives behind RequireSession
              # middleware and returned 401 to every probe from this Pod
              # because the in-cluster Job has no operator session cookie.
              # The probe treated 401 as "API not ready" → loop never
              # broke → trigger was never called → cutover never fired.
              # /healthz exists for exactly this purpose (kubelet liveness
              # + in-cluster reachability) and returns 200 unconditionally
              # while the process is up.
              healthz_url="${api}/healthz"
              # Issue #935 Bug 2: hit the in-cluster /internal/cutover/trigger
              # endpoint (lives outside RequireSession) with this Pod's
              # ServiceAccount token. The previous /sovereign/cutover/start
              # path required a human session cookie and 401'd forever.
              trigger_url="${api}/api/v1/internal/cutover/trigger"
              sa_token_file="/var/run/secrets/kubernetes.io/serviceaccount/token"

              # 1. Wait for catalyst-api to be reachable. In a fresh
              #    Sovereign, the cutover chart and catalyst-platform
              #    chart land on slightly different timelines via Flux.
              #    We poll /healthz (an unauthenticated liveness endpoint)
              #    up to WAIT_TIMEOUT_SECONDS so a transient unreachability
              #    doesn't fail the hook.
              echo "[auto-trigger] waiting for catalyst-api at ${api}"
              deadline=$(( $(date +%s) + WAIT_TIMEOUT_SECONDS ))
              api_ready="false"
              while [ "$(date +%s)" -lt "${deadline}" ]; do
                code=$(curl -s -o /dev/null -w "%{http_code}" \
                  --connect-timeout 5 --max-time 15 \
                  "${healthz_url}" 2>/dev/null || echo "000")
                if [ "${code}" = "200" ]; then
                  api_ready="true"
                  break
                fi
                echo "[auto-trigger] catalyst-api not ready (HTTP ${code}); retrying in 10s"
                sleep 10
              done
              if [ "${api_ready}" != "true" ]; then
                echo "[auto-trigger] catalyst-api never became reachable within ${WAIT_TIMEOUT_SECONDS}s — exiting 0; operator can fire manually via CTA"
                exit 0
              fi

              # 2. Skip the pre-flight cutoverComplete=true short-circuit
              #    that 0.1.17 had: /internal/cutover/trigger is itself
              #    idempotent — it returns 200 with the existing snapshot
              #    when cutoverComplete=true (cutover_internal.go line
              #    279). Removing the pre-read keeps us off the auth-gated
              #    /sovereign/cutover/status endpoint entirely, which is
              #    the whole point of the 0.1.18 change.

              # 3. Confirm the SA token is mountable. Should always be
              #    true when the Pod is running with our serviceAccount,
              #    but a friendlier failure message helps operators
              #    triage AutomountServiceAccountToken=false misconfig.
              if [ ! -r "${sa_token_file}" ]; then
                echo "[auto-trigger] ServiceAccount token not mounted at ${sa_token_file}; cannot authenticate"
                echo "[auto-trigger] check that the Pod's serviceAccountName matches the chart's RBAC subject"
                # Exit 0 so the operator can fire manually via CTA;
                # failing the hook would block the chart install.
                exit 0
              fi

              # 4. POST /api/v1/internal/cutover/trigger with the SA
              #    token. catalyst-api validates the token via
              #    TokenReview and runs the same engine path as
              #    /sovereign/cutover/start.
              #
              #    Idempotency contract:
              #    - cutoverComplete=true → 200 with the existing snapshot
              #    - cutover already running on this Pod → 409 (benign)
              #    - no steps found → 424 (chart not installed; impossible here)
              #    - success → 200 with the freshly-seeded snapshot
              #    - SA token rejected by apiserver → 502 (transient)
              #    - SA mismatch → 403 (chart RBAC misconfig)
              echo "[auto-trigger] POSTing ${trigger_url}"
              # Read the token at request time (not as an env var) so
              # rotation during a long deadline still authenticates.
              code=$(curl -s -o /tmp/trigger.json -w "%{http_code}" \
                -X POST --connect-timeout 10 --max-time 60 \
                -H "Authorization: Bearer $(cat ${sa_token_file})" \
                -H "Content-Type: application/json" \
                -d '{}' \
                "${trigger_url}" 2>/dev/null || echo "000")

              case "${code}" in
                200)
                  echo "[auto-trigger] cutover started successfully"
                  cat /tmp/trigger.json 2>/dev/null || true
                  ;;
                409)
                  echo "[auto-trigger] cutover already in progress on the catalyst-api Pod — no-op"
                  ;;
                424)
                  echo "[auto-trigger] catalyst-api reports no step ConfigMaps — chart install ordering bug"
                  cat /tmp/trigger.json 2>/dev/null || true
                  exit 1
                  ;;
                401|403)
                  echo "[auto-trigger] catalyst-api rejected our SA token (HTTP ${code}); chart RBAC may be misconfigured"
                  cat /tmp/trigger.json 2>/dev/null || true
                  # Exit 0 — operator-actionable, not a chart-install
                  # failure. CTA still works via the operator session.
                  exit 0
                  ;;
                *)
                  echo "[auto-trigger] unexpected status ${code}"
                  cat /tmp/trigger.json 2>/dev/null || true
                  # Do NOT fail the hook — the operator can still fire
                  # the cutover manually via the CTA. Exit 0 so the
                  # chart install completes and Flux marks the
                  # HelmRelease Ready.
                  exit 0
                  ;;
              esac

              echo "[auto-trigger] complete"
          resources:
            requests:
              cpu: 50m
              memory: 64Mi
            limits:
              memory: 256Mi
          securityContext:
            runAsNonRoot: true
            runAsUser: 1000
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop: ["ALL"]
          volumeMounts:
            - name: tmp
              mountPath: /tmp
      volumes:
        - name: tmp
          emptyDir: {}
{{- end }}
