# IPZ E2E Job template. Render every placeholder before applying this file.
#
# Placeholder contract for the Wave-2 dispatcher:
#   __RUN_ID__        A unique DNS-1123-safe run identifier. Replace every
#                     occurrence; it is the Job name suffix and ownership label.
#   __NODE__          One of debian1, debian2, or debian3. Pin mode renders the
#                     selected node here. Spread mode selects a different value
#                     for successive runs (round-robin or least-loaded), while
#                     the topology spread/anti-affinity rules remain in force.
#   __MIRROR_SLUG__   One path-safe directory name present below
#                     /home/user/builds on the selected node. It is mounted
#                     read-only and copied into the writable run workspace.
#   __IMAGE__         The locally imported, immutable runner image reference
#                     (prefer a digest-pinned reference). The dispatcher must
#                     verify it is present on the selected node and must not
#                     leave an unresolved token in the submitted object.
#   __SNAPSHOT_DIR__  The absolute node-local snapshot directory for the
#                     current e2e-snapshot-contract.sh fingerprint. The
#                     dispatcher derives it from the invoking checkout's
#                     contract script (or IPZ_E2E_SNAPSHOT_DIR) so the Job can
#                     never mount a snapshot from a different contract.
#   __EXPECTED_COMMIT__ The 40-character git HEAD of the invoking checkout.
#                     The staged mirror must resolve to this exact revision.
#
# The source mirror is never modified. The init container stages it into an
# emptyDir so npm installs and test artifacts stay inside the run-owned pod.
#
# Stack lifecycle (merged from the T1.2 wp-stack template): MariaDB and
# WordPress are native Kubernetes sidecars (initContainers with
# restartPolicy: Always). wp-provision is a finite init container and exits 0
# only after every provisioning check succeeds. After the Playwright runner
# exits, Kubernetes terminates the sidecars so the Job reaches a terminal
# result for TTL cleanup. All stack containers share the pod network
# namespace: MariaDB serves 127.0.0.1:3306 and Apache serves
# http://127.0.0.1:8080 — the same origin baked into the snapshot's
# WP_HOME/WP_SITEURL, so the runner needs no Host-header rewriting.
apiVersion: batch/v1
kind: Job
metadata:
  name: "ipz-e2e-__RUN_ID__"
  namespace: ipz-e2e
  labels:
    app: ipz-e2e
    run: "__RUN_ID__"
spec:
  # The dispatcher owns retries and maps each Job outcome to the e2e-remote
  # receipt contract. Kubernetes must not retry a failed pod itself.
  backoffLimit: 0
  activeDeadlineSeconds: __ACTIVE_DEADLINE_SECONDS__
  ttlSecondsAfterFinished: __TTL_SECONDS_AFTER_FINISHED__
  template:
    metadata:
      labels:
        app: ipz-e2e
        run: "__RUN_ID__"
    spec:
      restartPolicy: Never
      automountServiceAccountToken: false
      terminationGracePeriodSeconds: 30
      nodeSelector:
        kubernetes.io/os: linux
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: kubernetes.io/hostname
                    operator: In
                    values:
                      - "__NODE__"
          # If the dispatcher deliberately omits pinning in a future template
          # variant, this preference and the spread constraint below keep
          # app=ipz-e2e jobs distributed over the three buildbox nodes.
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              podAffinityTerm:
                topologyKey: kubernetes.io/hostname
                labelSelector:
                  matchLabels:
                    app: ipz-e2e
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: kubernetes.io/hostname
          whenUnsatisfiable: ScheduleAnyway
          labelSelector:
            matchLabels:
              app: ipz-e2e
      # debian3 is the control-plane node but is an eligible buildbox. These
      # tolerations keep an explicit __NODE__ selection schedulable there.
      tolerations:
        - key: node-role.kubernetes.io/control-plane
          operator: Exists
          effect: NoSchedule
        - key: node-role.kubernetes.io/master
          operator: Exists
          effect: NoSchedule
      initContainers:
        - name: stage-mirror
          image: "__IMAGE__"
          imagePullPolicy: IfNotPresent
          command: ["/bin/sh", "-ec"]
          args:
            - >-
              cp -a /source/. /workspace/ &&
              chown -R pwuser:pwuser /workspace
          securityContext:
            runAsUser: 0
            runAsGroup: 0
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop: ["ALL"]
              # cp -a preserves source ownership and chown re-owns the staged
              # tree for pwuser; both are chown(2) calls, and root without
              # CAP_CHOWN cannot make them. DAC_OVERRIDE covers mirror files
              # that are not world-readable, FOWNER the permission-bit copies.
              add: ["CHOWN", "DAC_OVERRIDE", "FOWNER"]
          volumeMounts:
            - name: source-mirror
              mountPath: /source
              readOnly: true
            - name: run-workspace
              mountPath: /workspace
        # Exit 42 is the dispatcher's reserved, machine-recognisable freshness
        # rejection. Never push, rsync, scp, or repair the mirror here; mirror
        # refresh is a separate operator action.
        - name: verify-mirror-revision
          image: "__IMAGE__"
          imagePullPolicy: IfNotPresent
          command: ["/bin/sh", "-ec"]
          args:
            - |
              set -eu
              expected="${IPZ_E2E_EXPECTED_COMMIT:-}"
              node="${IPZ_E2E_NODE:-unknown}"
              # The pinned Playwright runner intentionally has no git binary.
              # Resolve HEAD from Git's on-disk metadata instead: mirrors may
              # use either a detached HEAD, a loose symbolic ref, or packed-refs.
              actual=""
              if [ -f /workspace/.git/HEAD ]; then
                head_value="$(cat /workspace/.git/HEAD 2>/dev/null || true)"
                case "${head_value}" in
                  'ref: '*)
                    head_ref="${head_value#ref: }"
                    case "${head_ref}" in
                      refs/*)
                        if [ -f "/workspace/.git/${head_ref}" ]; then
                          actual="$(cat "/workspace/.git/${head_ref}" 2>/dev/null || true)"
                        elif [ -f /workspace/.git/packed-refs ]; then
                          actual="$(awk -v ref="${head_ref}" '$2 == ref { print $1; exit }' /workspace/.git/packed-refs 2>/dev/null || true)"
                        fi
                        ;;
                    esac
                    ;;
                  *) actual="${head_value}" ;;
                esac
              fi
              printf '%s\n' "${expected}" | grep -Eq '^[0-9a-f]{40}$' || expected=empty
              printf '%s\n' "${actual}" | grep -Eq '^[0-9a-f]{40}$' || actual=unreadable
              if [ "${actual}" != "${expected}" ]; then
                printf 'IPZ_E2E_MIRROR_FRESHNESS expected=%s actual=%s node=%s\n' "${expected}" "${actual}" "${node}" >&2
                exit 42
              fi
              printf 'mirror revision confirmed: %s\n' "${actual}"
          env:
            - name: IPZ_E2E_EXPECTED_COMMIT
              value: "__EXPECTED_COMMIT__"
            - name: IPZ_E2E_NODE
              value: "__NODE__"
          volumeMounts:
            - name: run-workspace
              mountPath: /workspace
              readOnly: true
        - name: validate-connect-credentials
          image: docker.io/library/alpine:3.20
          imagePullPolicy: IfNotPresent
          command: ["sh", "-ec"]
          args:
            - |
              api_key_present=false
              site_id_present=false
              [ -n "${IPZ_E2E_SITE_API_KEY:-}" ] && api_key_present=true
              [ -n "${IPZ_E2E_SITE_ID:-}" ] && site_id_present=true
              printf 'IPZ connect credential presence: api_key=%s site_id=%s\n' "$api_key_present" "$site_id_present"
              [ "$api_key_present" = true ] && [ "$site_id_present" = true ]
          env:
            - name: IPZ_E2E_SITE_API_KEY
              valueFrom:
                secretKeyRef:
                  name: ipz-e2e-connect
                  key: IPZ_E2E_SITE_API_KEY
            - name: IPZ_E2E_SITE_ID
              valueFrom:
                secretKeyRef:
                  name: ipz-e2e-connect
                  key: IPZ_E2E_SITE_ID
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop: ["ALL"]
        - name: validate-snapshot
          image: docker.io/library/alpine:3.20
          imagePullPolicy: IfNotPresent
          command: ["sh", "-ec"]
          args:
            - |
              set -eu
              test -s /snapshot/database.tar
              test -s /snapshot/wordpress.tar
              test -f /snapshot/fingerprint
              test -f /snapshot/contract
              expected_contract="$(printf '%s\n' \
                'contract=1' \
                'wordpress=docker.io/library/wordpress:6.6-php8.2-apache' \
                'mariadb=docker.io/library/mariadb:11' \
                'database_schema=wordpress-core-6.6' \
                'php=>=8.2' \
                'acf=6.3.5' \
                'fixtures=acf-fields-v1')"
              expected_fingerprint="$(printf '%s\n' \
                "$expected_contract" | sha256sum | cut -d ' ' -f 1)"
              test "$(cat /snapshot/contract)" = "$expected_contract"
              test "$(cat /snapshot/fingerprint)" = "$expected_fingerprint"
              tar -tf /snapshot/database.tar >/dev/null
              tar -tf /snapshot/wordpress.tar >/dev/null
              if tar -tf /snapshot/wordpress.tar | grep -Eq '(^|/)wp-content/plugins/international-press-zone(/|$)'; then
                printf '%s\n' 'Snapshot contains the current plugin source; refusing to provision.' >&2
                exit 1
              fi
          volumeMounts:
            - name: e2e-snapshot
              mountPath: /snapshot
              readOnly: true
        - name: validate-plugin-inputs
          image: docker.io/library/alpine:3.20
          imagePullPolicy: IfNotPresent
          command: ["sh", "-ec"]
          args:
            - |
              set -eu
              # Fail before any WordPress process starts if the run did not
              # deliver the exact source inputs that remote-stack.sh requires.
              test -f /plugin/international-press-zone.php
              test -f /plugin/tests/e2e/fixtures/acf-field-table-fixture.php
          volumeMounts:
            - name: ipz-plugin
              mountPath: /plugin
              readOnly: true
        - name: restore-database
          image: docker.io/library/alpine:3.20
          imagePullPolicy: IfNotPresent
          command: ["sh", "-ec"]
          args:
            - |
              set -eu
              test -s /snapshot/database.tar
              tar -C /restore -xf /snapshot/database.tar
          volumeMounts:
            - name: mariadb-data
              mountPath: /restore
            - name: e2e-snapshot
              mountPath: /snapshot
              readOnly: true
        - name: restore-wordpress
          image: docker.io/library/alpine:3.20
          imagePullPolicy: IfNotPresent
          command: ["sh", "-ec"]
          args:
            - |
              set -eu
              test -s /snapshot/wordpress.tar
              tar -C /restore -xf /snapshot/wordpress.tar
          volumeMounts:
            - name: wordpress-data
              mountPath: /restore
            - name: e2e-snapshot
              mountPath: /snapshot
              readOnly: true
        - name: configure-wordpress
          image: docker.io/library/wordpress:cli-php8.2
          imagePullPolicy: IfNotPresent
          # The official CLI image runs as www-data; provisioning writes and
          # chowns files under /var/www/html, which needs root (wp is invoked
          # --allow-root).
          securityContext:
            runAsUser: 0
          command: ["sh", "-ec"]
          args:
            - |
              set -eu
              test -f /var/www/html/wp-includes/version.php
              test -f /var/www/html/wp-config.php
              wp() {
                /usr/local/bin/wp --allow-root --path=/var/www/html "$@"
              }
              # The snapshot was built with a temporary Podman DB hostname. Set
              # every connection/site constant before the Apache container
              # starts.
              wp config set DB_HOST "${WORDPRESS_DB_HOST}" --type=constant --quiet
              wp config set DB_NAME "${WORDPRESS_DB_NAME}" --type=constant --quiet
              wp config set DB_USER "${WORDPRESS_DB_USER}" --type=constant --quiet
              wp config set DB_PASSWORD "${WORDPRESS_DB_PASSWORD}" --type=constant --quiet
              wp config set WP_HOME "${IPZ_SITE_URL}" --type=constant --quiet
              wp config set WP_SITEURL "${IPZ_SITE_URL}" --type=constant --quiet
          env:
            - name: WORDPRESS_DB_HOST
              value: 127.0.0.1:3306
            - name: WORDPRESS_DB_NAME
              value: wordpress
            - name: WORDPRESS_DB_USER
              value: wordpress
            - name: WORDPRESS_DB_PASSWORD
              value: wordpress
            - name: IPZ_SITE_URL
              value: http://127.0.0.1:8080
          volumeMounts:
            - name: wordpress-data
              mountPath: /var/www/html
        # Native sidecars start before wp-provision and are terminated by
        # Kubernetes when the Playwright runner exits.
        - name: mariadb
          restartPolicy: Always
          image: docker.io/library/mariadb:11
          imagePullPolicy: IfNotPresent
          env:
            - name: MARIADB_DATABASE
              value: wordpress
            - name: MARIADB_USER
              value: wordpress
            - name: MARIADB_PASSWORD
              value: wordpress
            - name: MARIADB_ROOT_PASSWORD
              value: wordpress
          ports:
            - name: mysql
              containerPort: 3306
              protocol: TCP
          readinessProbe:
            exec:
              command:
                - sh
                - -ec
                - mariadb-admin ping --host=127.0.0.1 --user=wordpress --password=wordpress --silent
            periodSeconds: 2
            timeoutSeconds: 3
            failureThreshold: 3
          startupProbe:
            exec:
              command:
                - sh
                - -ec
                - mariadb-admin ping --host=127.0.0.1 --user=wordpress --password=wordpress --silent
            periodSeconds: 1
            timeoutSeconds: 3
            failureThreshold: 60
          volumeMounts:
            - name: mariadb-data
              mountPath: /var/lib/mysql
        - name: wordpress
          restartPolicy: Always
          image: docker.io/library/wordpress:6.6-php8.2-apache
          imagePullPolicy: IfNotPresent
          env:
            - name: WORDPRESS_DB_HOST
              value: 127.0.0.1:3306
            - name: WORDPRESS_DB_NAME
              value: wordpress
            - name: WORDPRESS_DB_USER
              value: wordpress
            - name: WORDPRESS_DB_PASSWORD
              value: wordpress
          # WP_HOME/WP_SITEURL are owned by configure-wordpress (wp config
          # set); a WORDPRESS_CONFIG_EXTRA copy here would define the same
          # constants a second time at runtime and spam PHP warnings.
          ports:
            - name: http
              containerPort: 8080
              protocol: TCP
          command: ["sh", "-ec"]
          args:
            - |
              set -eu
              # The snapshot bakes WP_HOME/WP_SITEURL as http://127.0.0.1:8080
              # and the Playwright runner targets that exact origin inside the
              # pod, so Apache itself must listen on 8080 — a Host-header
              # rewrite would leave the runner with nothing to connect to.
              sed -ri 's/^Listen 80$/Listen 8080/' /etc/apache2/ports.conf
              sed -ri 's/<VirtualHost \*:80>/<VirtualHost *:8080>/' /etc/apache2/sites-available/000-default.conf
              # ipz-run-state is a new emptyDir for every Pod; never clear a
              # failure marker that a faster provisioning sidecar already
              # wrote.
              /usr/local/bin/docker-entrypoint.sh apache2-foreground &
              server_pid=$!
              while kill -0 "${server_pid}" 2>/dev/null; do
                if test -f /run/ipz-e2e/provision.failed; then
                  kill "${server_pid}" 2>/dev/null || true
                  wait "${server_pid}" 2>/dev/null || true
                  exit 1
                fi
                if test -f /run/ipz-e2e/provision.ready; then
                  wait "${server_pid}"
                  exit $?
                fi
                sleep 1
              done
              # Apache died before provisioning completed. Preserve a failed
              # Pod phase instead of allowing a successful-looking sidecar to
              # linger.
              wait "${server_pid}" 2>/dev/null || exit $?
              exit 1
          readinessProbe:
            exec:
              command:
                - sh
                - -ec
                - |
                  set -eu
                  test -f /run/ipz-e2e/provision.ready
                  php -r '
                  $url = "http://127.0.0.1:8080/wp-json/";
                  $context = stream_context_create(array("http" => array(
                      "ignore_errors" => true,
                      "timeout" => 5,
                  )));
                  $body = @file_get_contents($url, false, $context);
                  $headers = $http_response_header ?? array();
                  $content_type = "";
                  foreach ($headers as $header) {
                      if (stripos($header, "Content-Type:") === 0) {
                          $content_type = trim(substr($header, strlen("Content-Type:")));
                          break;
                      }
                  }
                  if (!is_string($body) || stripos($content_type, "application/json") !== 0 || json_decode($body, true) === null) {
                      fwrite(STDERR, sprintf("REST readiness requires JSON; content-type=%s body-prefix=%s\n", $content_type ?: "missing", is_string($body) ? substr($body, 0, 80) : "unavailable"));
                      exit(1);
                  }
                  '
            periodSeconds: 2
            timeoutSeconds: 6
            failureThreshold: 3
          startupProbe:
            exec:
              command:
                - sh
                - -ec
                - test -f /var/www/html/wp-includes/version.php
            periodSeconds: 2
            timeoutSeconds: 3
            failureThreshold: 60
          volumeMounts:
            - name: wordpress-data
              mountPath: /var/www/html
            - name: ipz-plugin
              mountPath: /var/www/html/wp-content/plugins/international-press-zone
              readOnly: true
            - name: ipz-run-state
              mountPath: /run/ipz-e2e
        - name: wp-provision
          image: docker.io/library/wordpress:cli-php8.2
          imagePullPolicy: IfNotPresent
          # Runs chown on .htaccess and writes under /var/www/html — must be
          # root (the image defaults to www-data, which EPERMs and blocks
          # provision.ready).
          securityContext:
            runAsUser: 0
          command: ["sh", "-ec"]
          args:
            - |
              set -eu
              state_dir=/run/ipz-e2e
              provision_exit() {
                status=$?
                if [ "${status}" -ne 0 ]; then
                  : >"${state_dir}/provision.failed"
                fi
                exit "${status}"
              }
              trap provision_exit EXIT
              wp() {
                /usr/local/bin/wp --allow-root --path=/var/www/html "$@"
              }
              attempt=0
              while ! test -f /var/www/html/wp-includes/version.php || ! test -f /var/www/html/wp-config.php; do
                attempt=$((attempt + 1))
                test "${attempt}" -lt 60 || exit 1
                sleep 1
              done
              # Wait for a successful SQL query, not merely an open TCP port.
              # This is the WordPress-side equivalent of the remote-stack
              # mariadb-admin polling gate and prevents a race with DB
              # bootstrap.
              attempt=0
              until wp db query 'SELECT 1' >/dev/null 2>&1; do
                attempt=$((attempt + 1))
                test "${attempt}" -lt 60 || exit 1
                sleep 1
              done
              wp core is-installed
              wp user set-role admin administrator
              wp cache flush
              wp eval '
              $user = get_user_by("login", "admin");
              if (!$user || !$user->has_cap("manage_options")) {
                  fwrite(STDERR, "E2E administrator lacks manage_options.\n");
                  exit(1);
              }
              '
              wp plugin activate international-press-zone
              wp plugin is-active advanced-custom-fields
              wp plugin is-active international-press-zone
              # Enable the flag-gated E2E surfaces. remote-stack.sh enables
              # ipz_team_workflow for the deferred Team/Workflow/Assignments
              # fixtures; the migration data-region contract additionally
              # needs the ipz_wpml_migration route registered, since the spec
              # requires /mpz/v1/migration/preflight to reach data-state=ready
              # against the real endpoint.
              wp eval '
              update_option("ipz_feature_flags", array(
                  "ipz_team_workflow" => true,
                  "ipz_wpml_migration" => true,
              ));
              if (!\InternationalPressZone\Core\FeatureFlags::isEnabled("ipz_team_workflow")
                  || !\InternationalPressZone\Core\FeatureFlags::isEnabled("ipz_wpml_migration")) {
                  fwrite(STDERR, "E2E feature flags were not enabled.\n");
                  exit(1);
              }
              '
              # Match remote-stack.sh's fixture-graph gate before publishing
              # readiness.
              wp eval '
              $required_fields = array(
                  "field_acf_table_e2e_intro" => "text",
                  "field_acf_table_e2e_repeater" => "repeater",
                  "field_acf_table_e2e_repeater_title" => "text",
                  "field_acf_table_e2e_repeater_copy" => "textarea",
                  "field_acf_table_e2e_flexible" => "flexible_content",
                  "field_acf_table_e2e_hero_heading" => "text",
                  "field_acf_table_e2e_hero_items" => "repeater",
                  "field_acf_table_e2e_hero_item_label" => "text",
                  "field_acf_table_e2e_callout_message" => "textarea",
                  "field_acf_table_e2e_secondary_note" => "textarea",
              );
              foreach ($required_fields as $key => $type) {
                  $field = acf_get_field($key);
                  if (!is_array($field) || ($field["key"] ?? "") !== $key || ($field["type"] ?? "") !== $type) {
                      fwrite(STDERR, sprintf("ACF fixture field assertion failed: %s\n", $key));
                      exit(1);
                  }
              }
              $field_keys = static function ($fields): array {
                  $keys = array();
                  foreach ((array) $fields as $field) {
                      if (is_array($field) && isset($field["key"])) {
                          $keys[] = $field["key"];
                      }
                  }
                  return $keys;
              };
              $assert_exact_keys = static function ($fields, array $expected, string $parent) use ($field_keys): void {
                  $actual = $field_keys($fields);
                  if ($actual !== $expected) {
                      fwrite(STDERR, sprintf(
                          "ACF fixture ordered graph assertion failed: %s (expected %s, got %s)\n",
                          $parent,
                          implode(",", $expected),
                          implode(",", $actual)
                      ));
                      exit(1);
                  }
              };
              $repeater = acf_get_field("field_acf_table_e2e_repeater");
              if (!is_array($repeater)) {
                  fwrite(STDERR, "ACF fixture repeater is missing.\n");
                  exit(1);
              }
              $assert_exact_keys(
                  $repeater["sub_fields"] ?? array(),
                  array("field_acf_table_e2e_repeater_title", "field_acf_table_e2e_repeater_copy"),
                  $repeater["key"]
              );
              $hero_items = acf_get_field("field_acf_table_e2e_hero_items");
              if (!is_array($hero_items)) {
                  fwrite(STDERR, "ACF fixture hero repeater is missing.\n");
                  exit(1);
              }
              $assert_exact_keys(
                  $hero_items["sub_fields"] ?? array(),
                  array("field_acf_table_e2e_hero_item_label"),
                  $hero_items["key"]
              );
              $flexible = acf_get_field("field_acf_table_e2e_flexible");
              if (!is_array($flexible)) {
                  fwrite(STDERR, "ACF fixture flexible field is missing.\n");
                  exit(1);
              }
              $expected_layout_keys = array("layout_acf_table_e2e_hero", "layout_acf_table_e2e_callout");
              $assert_exact_keys($flexible["layouts"] ?? array(), $expected_layout_keys, $flexible["key"]);
              $layouts = array();
              foreach ((array) ($flexible["layouts"] ?? array()) as $layout) {
                  if (is_array($layout) && isset($layout["key"])) {
                      $layouts[$layout["key"]] = $layout;
                  }
              }
              foreach ($expected_layout_keys as $key) {
                  if (!isset($layouts[$key]) || ($layouts[$key]["key"] ?? "") !== $key) {
                      fwrite(STDERR, sprintf("ACF fixture layout assertion failed: %s\n", $key));
                      exit(1);
                  }
              }
              $assert_exact_keys(
                  $layouts["layout_acf_table_e2e_hero"]["sub_fields"] ?? array(),
                  array("field_acf_table_e2e_hero_heading", "field_acf_table_e2e_hero_items"),
                  "layout_acf_table_e2e_hero"
              );
              $assert_exact_keys(
                  $layouts["layout_acf_table_e2e_callout"]["sub_fields"] ?? array(),
                  array("field_acf_table_e2e_callout_message"),
                  "layout_acf_table_e2e_callout"
              );
              '
              # Configure optional translation API credentials without placing
              # secrets in argv. HTTP is accepted only for loopback, as in IPZ.
              api_url="${IPZ_E2E_TRANSLATION_API_URL:-}"
              if [ -n "${api_url}" ]; then
                case "${api_url}" in
                  https://*|http://127.0.0.1:*|http://localhost:*|"http://[::1]:"*) ;;
                  *) printf '%s\n' 'Unsafe translation API URL.' >&2; exit 1 ;;
                esac
                api_url="${api_url%/}"
                wp config set IPZ_API_BASE_URL "${api_url}" --type=constant --quiet
                actual="$(wp eval 'echo defined("IPZ_API_BASE_URL") ? rtrim((string) IPZ_API_BASE_URL, "/") : "";')"
                test "${actual}" = "${api_url}"
              fi
              # Optional Connect fixtures are passed by environment name only
              # so credentials never appear in the WP-CLI argv or provisioning
              # logs.
              site_api_key="${IPZ_E2E_SITE_API_KEY:-}"
              site_id="${IPZ_E2E_SITE_ID:-}"
              if [ -n "${site_api_key}" ] || [ -n "${site_id}" ]; then
                test -n "${site_api_key}" && test -n "${site_id}"
                wp eval '
                $api_key = getenv("IPZ_E2E_SITE_API_KEY");
                $site_id = getenv("IPZ_E2E_SITE_ID");
                $email = getenv("IPZ_E2E_ACCOUNT_EMAIL");
                $display_name = getenv("IPZ_E2E_ACCOUNT_DISPLAY_NAME");
                if (!is_string($api_key) || $api_key === "" || !is_string($site_id) || $site_id === "") {
                    fwrite(STDERR, "E2E Connect credential environment is incomplete.\n");
                    exit(1);
                }
                $store = new \InternationalPressZone\Connect\CredentialStore();
                if (!$store->save($api_key, $site_id, array(
                    "email" => is_string($email) ? $email : "",
                    "display_name" => is_string($display_name) ? $display_name : "",
                ))) {
                    fwrite(STDERR, "E2E Connect credential could not be stored.\n");
                    exit(1);
                }
                $stored = $store->get_api_key();
                if (!is_string($stored) || !hash_equals($api_key, $stored)) {
                    fwrite(STDERR, "E2E Connect credential verification failed.\n");
                    exit(1);
                }
                $metadata = $store->metadata();
                if (($metadata["site_id"] ?? "") !== strtolower($site_id)) {
                    fwrite(STDERR, "E2E Connect site metadata verification failed.\n");
                    exit(1);
                }
                $service = new \InternationalPressZone\Connect\ConnectService($store);
                $status = $service->status(true);
                if (is_wp_error($status) || empty($status["connected"])) {
                    $code = is_wp_error($status) ? $status->get_error_code() : "disconnected";
                    fwrite(STDERR, "E2E Connect credential failed backend status verification: " . $code . "\n");
                    exit(1);
                }
                '
              fi
              # The snapshot contract requires pretty permalinks and a writable
              # Apache rules file before REST readiness is allowed.
              touch /var/www/html/.htaccess
              chown www-data:www-data /var/www/html/.htaccess
              chmod 664 /var/www/html/.htaccess
              wp rewrite structure '/%postname%/'
              wp rewrite flush
              wp eval '
              global $wp_rewrite;
              $rules = $wp_rewrite->mod_rewrite_rules();
              if (!is_string($rules) || $rules === "") {
                  fwrite(STDERR, "Apache rewrite rule generation failed.\n");
                  exit(1);
              }
              if (file_put_contents(ABSPATH . ".htaccess", $rules) === false) {
                  fwrite(STDERR, "Apache .htaccess write failed.\n");
                  exit(1);
              }
              '
              chown www-data:www-data /var/www/html/.htaccess
              chmod 664 /var/www/html/.htaccess
              test "$(wp option get permalink_structure)" = '/%postname%/'
              # Verify unauthenticated rewrite routing before publishing
              # readiness.
              php -r '
              $url = "http://127.0.0.1:8080/wp-json/";
              $context = stream_context_create(array("http" => array(
                  "ignore_errors" => true,
                  "timeout" => 5,
              )));
              $body = @file_get_contents($url, false, $context);
              $headers = $http_response_header ?? array();
              $content_type = "";
              foreach ($headers as $header) {
                  if (stripos($header, "Content-Type:") === 0) {
                      $content_type = trim(substr($header, strlen("Content-Type:")));
                      break;
                  }
              }
              if (!is_string($body) || stripos($content_type, "application/json") !== 0 || json_decode($body, true) === null) {
                  fwrite(STDERR, sprintf("REST rewrite smoke failed: content-type=%s body-prefix=%s\n", $content_type ?: "missing", is_string($body) ? substr($body, 0, 80) : "unavailable"));
                  exit(1);
              }
              '
              : >"${state_dir}/provision.ready"
              trap - EXIT
              # This is a finite init container. Once the runner is started,
              # Kubernetes owns sidecar shutdown; do not keep a regular
              # container alive here because Jobs wait for every regular
              # container to exit.
              exit 0
          env:
            - name: WORDPRESS_DB_HOST
              value: 127.0.0.1:3306
            - name: WORDPRESS_DB_NAME
              value: wordpress
            - name: WORDPRESS_DB_USER
              value: wordpress
            - name: WORDPRESS_DB_PASSWORD
              value: wordpress
            - name: IPZ_E2E_TRANSLATION_API_URL
              value: ""
            - name: IPZ_E2E_SITE_API_KEY
              valueFrom:
                secretKeyRef:
                  name: ipz-e2e-connect
                  key: IPZ_E2E_SITE_API_KEY
            - name: IPZ_E2E_SITE_ID
              valueFrom:
                secretKeyRef:
                  name: ipz-e2e-connect
                  key: IPZ_E2E_SITE_ID
            - name: IPZ_E2E_ACCOUNT_EMAIL
              value: ""
            - name: IPZ_E2E_ACCOUNT_DISPLAY_NAME
              value: ""
          volumeMounts:
            - name: wordpress-data
              mountPath: /var/www/html
            - name: ipz-plugin
              mountPath: /var/www/html/wp-content/plugins/international-press-zone
              readOnly: true
            - name: ipz-run-state
              mountPath: /run/ipz-e2e
      containers:
        - name: playwright
          image: "__IMAGE__"
          imagePullPolicy: IfNotPresent
          workingDir: /workspace/plugins/international-press-zone/tests/e2e
          env:
            - name: IPZ_E2E_RUN_ID
              value: "__RUN_ID__"
            - name: IPZ_E2E_MIRROR_SLUG
              value: "__MIRROR_SLUG__"
            - name: WP_BASE_URL
              value: http://127.0.0.1:8080
            - name: IPZ_E2E_SITE_API_KEY
              valueFrom:
                secretKeyRef:
                  name: ipz-e2e-connect
                  key: IPZ_E2E_SITE_API_KEY
            - name: IPZ_E2E_SITE_ID
              valueFrom:
                secretKeyRef:
                  name: ipz-e2e-connect
                  key: IPZ_E2E_SITE_ID
            - name: NODE_PATH
              value: /opt/ipz-e2e/node_modules
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: false
            capabilities:
              drop: ["ALL"]
          volumeMounts:
            - name: run-workspace
              mountPath: /workspace
        # This must remain a regular container: kubelet tears native sidecars
        # down when Playwright exits, which would race artifact extraction.
        # The dispatcher renders a finite retention period; there is deliberately
        # no unbounded mode, so every run terminates without operator cleanup.
        - name: artifact-exporter
          image: docker.io/library/alpine:3.20
          imagePullPolicy: IfNotPresent
          command: ["sh", "-ec"]
          args:
            - |
              set -eu
              elapsed=0
              wait_seconds=__ARTIFACT_EXPORTER_WAIT_SECONDS__
              while ! test -f /workspace/.ipz-e2e-client-finished && test "${elapsed}" -lt "${wait_seconds}"; do
                sleep 1
                elapsed=$((elapsed + 1))
              done
              if ! test -f /workspace/.ipz-e2e-client-finished; then
                printf 'artifact-exporter: the client-finished marker never appeared within %ss; exiting so the pod can be reclaimed\n' "${wait_seconds}" >&2
                exit 0
              fi
              sleep __ARTIFACT_EXPORTER_SECONDS__
          volumeMounts:
            - name: run-workspace
              mountPath: /workspace
      volumes:
        - name: source-mirror
          hostPath:
            path: "/home/user/builds/__MIRROR_SLUG__"
            type: Directory
        - name: run-workspace
          emptyDir: {}
        - name: mariadb-data
          emptyDir: {}
        - name: wordpress-data
          emptyDir: {}
        - name: ipz-run-state
          emptyDir:
            medium: Memory
        - name: e2e-snapshot
          hostPath:
            path: "__SNAPSHOT_DIR__"
            type: Directory
        - name: ipz-plugin
          hostPath:
            path: "/home/user/builds/__MIRROR_SLUG__/plugins/international-press-zone"
            type: Directory
