#!/usr/bin/env bash
# Apply, live-execute, verify, and publish the corrected Overdeck K3s Phase 3.
# This phase operates only on the existing debian1/debian2/debian3 cluster.
set -euo pipefail
umask 077
export PYTHONDONTWRITEBYTECODE=1

SCRIPT_VERSION=1
DEFAULT_PACKAGE_NAME=overdeck-k3s-phase3-existing-cluster.zip

usage() {
  cat <<'USAGE'
Usage:
  ./apply-overdeck-k3s-phase3-existing-cluster.sh REPO_ROOT [options]

Live existing-cluster verification and hardening:
  ./apply-overdeck-k3s-phase3-existing-cluster.sh /home/user/Projects/overdeck

Options:
  --package PATH              Package ZIP (default: beside this launcher)
  --kubeconfig PATH           Cluster-admin kubeconfig (default: ~/.kube/config-buildboxes)
  --result-root PATH          Parent for result and private Git transport; outside checkout
  --no-push                   Commit locally but do not push
  --no-pr                     Push branch but do not open a draft pull request
  --gate-timeout SECONDS      Per repository gate timeout (default: 1200)
  --live-timeout SECONDS      Complete cluster transaction timeout (default: 3600)
  --ssh-timeout SECONDS       Per-node SSH identity timeout (default: 15)
  --node-timeout SECONDS      Node verification timeout (default: 300)
  --canary-timeout SECONDS    Per-canary readiness timeout (default: 300)
  -h, --help                  Show this help

Safety:
  - Run as the normal repository owner, never root.
  - This launcher accepts no candidate hostname and performs no node enrollment.
  - It creates no K3s token, installs no K3s component, reboots no host, and changes no registry.
  - It verifies exactly debian1, debian2, and debian3 from tracked registries, Tailscale, SSH, and Kubernetes.
  - A clean private Git transport bypasses broken refs without mutating the shared checkout.
  - The only live mutation is a reversible restricted canary namespace and five protected labels per existing Node.
  - No Git staging, commit, push, or PR occurs until the independent live receipt validator passes.
  - The launcher never pushes directly to main and never merges a pull request.
USAGE
}

die() { printf 'phase3-existing-apply: %s\n' "$*" >&2; exit 2; }
log() { printf '[phase3-existing-apply] %s\n' "$*"; }
need() { command -v "$1" >/dev/null 2>&1 || die "required command not found: $1"; }

(($# >= 1)) || { usage >&2; exit 2; }
case "${1:-}" in -h|--help) usage; exit 0;; esac
REPO_INPUT=$1
shift

SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
PACKAGE="$SCRIPT_DIR/$DEFAULT_PACKAGE_NAME"
KUBECONFIG_PATH=${HOME}/.kube/config-buildboxes
RESULT_ROOT=""
PUSH=1
CREATE_PR=1
GATE_TIMEOUT_SEC=${OD_PHASE_GATE_TIMEOUT_SEC:-1200}
LIVE_TIMEOUT_SEC=${OD_PHASE3_EXISTING_LIVE_TIMEOUT_SEC:-3600}
SSH_TIMEOUT_SEC=${OD_PHASE3_EXISTING_SSH_TIMEOUT_SEC:-15}
NODE_TIMEOUT_SEC=${OD_PHASE3_EXISTING_NODE_TIMEOUT_SEC:-300}
CANARY_TIMEOUT_SEC=${OD_PHASE3_EXISTING_CANARY_TIMEOUT_SEC:-300}
TEST_FIXTURE=0

while (($#)); do
  case "$1" in
    --package) (($# >= 2)) || die "--package requires a path"; PACKAGE=$2; shift 2 ;;
    --kubeconfig) (($# >= 2)) || die "--kubeconfig requires a path"; KUBECONFIG_PATH=$2; shift 2 ;;
    --result-root) (($# >= 2)) || die "--result-root requires a path"; RESULT_ROOT=$2; shift 2 ;;
    --no-push) PUSH=0; CREATE_PR=0; shift ;;
    --no-pr) CREATE_PR=0; shift ;;
    --gate-timeout) (($# >= 2)) || die "--gate-timeout requires seconds"; GATE_TIMEOUT_SEC=$2; shift 2 ;;
    --live-timeout) (($# >= 2)) || die "--live-timeout requires seconds"; LIVE_TIMEOUT_SEC=$2; shift 2 ;;
    --ssh-timeout) (($# >= 2)) || die "--ssh-timeout requires seconds"; SSH_TIMEOUT_SEC=$2; shift 2 ;;
    --node-timeout) (($# >= 2)) || die "--node-timeout requires seconds"; NODE_TIMEOUT_SEC=$2; shift 2 ;;
    --canary-timeout) (($# >= 2)) || die "--canary-timeout requires seconds"; CANARY_TIMEOUT_SEC=$2; shift 2 ;;
    --test-fixture)
      [[ ${OD_PHASE3_EXISTING_ALLOW_TEST_FIXTURE:-0} == 1 ]] || die "--test-fixture is reserved for isolated package validation"
      TEST_FIXTURE=1; CREATE_PR=0; shift ;;
    -h|--help) usage; exit 0 ;;
    *) die "unknown option: $1" ;;
  esac
done

for value in "$GATE_TIMEOUT_SEC" "$LIVE_TIMEOUT_SEC" "$SSH_TIMEOUT_SEC" "$NODE_TIMEOUT_SEC" "$CANARY_TIMEOUT_SEC"; do
  [[ "$value" =~ ^[1-9][0-9]*$ ]] || die "timeouts must be positive integers"
done
[[ ${EUID:-$(id -u)} -ne 0 ]] || die "run as the normal repository owner, not root"
for command in bash python3 node sha256sum tar timeout; do need "$command"; done
[[ -x /usr/bin/git ]] || die "/usr/bin/git is required"
SELF_PATH=$(python3 -c 'import os,sys; print(os.path.realpath(sys.argv[1]))' "${BASH_SOURCE[0]}")
[[ -r "$PACKAGE" ]] || die "package not found: $PACKAGE"
PACKAGE=$(CDPATH= cd -- "$(dirname -- "$PACKAGE")" && printf '%s/%s\n' "$PWD" "$(basename -- "$PACKAGE")")
REPO=$(CDPATH= cd -- "$REPO_INPUT" 2>/dev/null && pwd) || die "repository path does not exist: $REPO_INPUT"
[[ -d "$REPO/.git" || -f "$REPO/.git" ]] || die "not a Git checkout/worktree: $REPO"
TOP=$(/usr/bin/git -C "$REPO" rev-parse --path-format=absolute --show-toplevel 2>/dev/null) || die "cannot resolve repository root"
[[ "$TOP" == "$REPO" ]] || die "pass the checkout root, not a subdirectory: $TOP"
ORIGIN_URL=$(/usr/bin/git -C "$REPO" remote get-url origin 2>/dev/null) || die "origin remote is missing"

expand_path() { python3 -c 'import os,sys; print(os.path.abspath(os.path.expandvars(os.path.expanduser(sys.argv[1]))))' "$1"; }
KUBECONFIG_PATH=$(expand_path "$KUBECONFIG_PATH")
if ((TEST_FIXTURE == 0)); then
  for command in ssh tailscale kubectl; do need "$command"; done
  [[ -f "$KUBECONFIG_PATH" && ! -L "$KUBECONFIG_PATH" ]] || die "live mode requires a regular kubeconfig: $KUBECONFIG_PATH"
else
  if [[ ${OD_PHASE3_EXISTING_TEST_ALLOW_PUSH:-0} != 1 ]]; then PUSH=0; fi
  if ((PUSH)); then
    case "$ORIGIN_URL" in /*|file://*|./*|../*) ;; *) die "fixture validation may push only to a local file origin";; esac
  fi
fi

STAMP=$(date -u +%Y%m%dT%H%M%SZ)
SLUG="k3s-phase3-existing-cluster-${STAMP,,}"
BRANCH="wt/$SLUG"
if [[ -z "$RESULT_ROOT" ]]; then RESULT_ROOT=$(dirname -- "$REPO"); fi
RESULT_ROOT=$(python3 -c 'import os,sys; print(os.path.realpath(os.path.abspath(os.path.expanduser(sys.argv[1]))))' "$RESULT_ROOT")
python3 - "$REPO" "$RESULT_ROOT" <<'PYRESULTROOT'
from pathlib import Path
import sys
repo,result=map(lambda value: Path(value).resolve(),sys.argv[1:])
try: result.relative_to(repo)
except ValueError: pass
else: raise SystemExit('result root must be outside the shared checkout')
PYRESULTROOT
mkdir -p "$RESULT_ROOT"
RESULT_ROOT=$(CDPATH= cd -- "$RESULT_ROOT" && pwd)
ISOLATION_ROOT="$RESULT_ROOT/overdeck-k3s-phase3-existing-cluster-isolation-$STAMP"
TRANSPORT_GIT="$ISOLATION_ROOT/transport.git"
WORKTREE="$ISOLATION_ROOT/worktree"
RESULT_DIR="$RESULT_ROOT/overdeck-k3s-phase3-existing-cluster-result-$STAMP"
GIT_TRANSPORT_JSON="$RESULT_DIR/git-transport.json"
[[ ! -e "$RESULT_DIR" ]] || die "result path already exists: $RESULT_DIR"
[[ ! -e "$ISOLATION_ROOT" ]] || die "isolation path already exists: $ISOLATION_ROOT"
mkdir -p "$RESULT_DIR/logs" "$RESULT_DIR/package"
chmod 700 "$RESULT_DIR" "$RESULT_DIR/logs" "$RESULT_DIR/package"

PAYLOAD_DIR=$(mktemp -d)
GATE_TMP_DIR="$RESULT_DIR/gate-tmp"
mkdir -p "$GATE_TMP_DIR"
GATES_TSV="$RESULT_DIR/gates.tsv"
: >"$GATES_TSV"
FINISHED=0
PUSH_STATUS=not-attempted
PR_STATUS=not-attempted
PR_URL=""
CLUSTER_STATUS=not-attempted
RECEIPT_VALIDATION_STATUS=not-attempted
COMMIT=""
BASE_COMMIT=""
ORIGIN_MAIN=""
PACKAGE_SHA=$(sha256sum "$PACKAGE" | awk '{print $1}')
PHASE3_RECEIPT="$RESULT_DIR/phase3-cluster-receipt"
REGISTRY_BEFORE="$RESULT_DIR/registry-before.json"
REGISTRY_AFTER="$RESULT_DIR/registry-after.json"

export OD3E_SCRIPT_VERSION=$SCRIPT_VERSION OD3E_REPO="$REPO" OD3E_WORKTREE="$WORKTREE" OD3E_BRANCH="$BRANCH" \
  OD3E_ISOLATION_ROOT="$ISOLATION_ROOT" OD3E_TRANSPORT_GIT="$TRANSPORT_GIT" OD3E_GIT_TRANSPORT_JSON="$GIT_TRANSPORT_JSON" \
  OD3E_PACKAGE="$PACKAGE" OD3E_PACKAGE_SHA="$PACKAGE_SHA" OD3E_GATES="$GATES_TSV" \
  OD3E_RESULT_JSON="$RESULT_DIR/phase3-outer-result.json" OD3E_TEST_FIXTURE="$TEST_FIXTURE" \
  OD3E_KUBECONFIG="$KUBECONFIG_PATH" OD3E_GATE_TIMEOUT="$GATE_TIMEOUT_SEC" OD3E_LIVE_TIMEOUT="$LIVE_TIMEOUT_SEC"

write_result() {
  local state=$1 rc=$2
  export OD3E_STATUS="$state" OD3E_EXIT_CODE="$rc" OD3E_GENERATED_UTC="$(date -u +%FT%TZ)" \
    OD3E_COMMIT="$COMMIT" OD3E_BASE="$BASE_COMMIT" OD3E_ORIGIN_MAIN="$ORIGIN_MAIN" \
    OD3E_PUSH_STATUS="$PUSH_STATUS" OD3E_PR_STATUS="$PR_STATUS" OD3E_PR_URL="$PR_URL" \
    OD3E_CLUSTER_STATUS="$CLUSTER_STATUS" OD3E_VALIDATION_STATUS="$RECEIPT_VALIDATION_STATUS" \
    OD3E_RECEIPT="$PHASE3_RECEIPT"
  python3 - <<'PYRESULT'
import json, os
from pathlib import Path

def optional(value): return value or None
result={
  'schema_version':1,'phase':3,'variant':'existing-three-node-cluster',
  'script_version':int(os.environ['OD3E_SCRIPT_VERSION']),'status':os.environ['OD3E_STATUS'],
  'exit_code':int(os.environ['OD3E_EXIT_CODE']),'generated_utc':os.environ['OD3E_GENERATED_UTC'],
  'repository':os.environ['OD3E_REPO'],'isolation_root':os.environ['OD3E_ISOLATION_ROOT'],
  'transport_repository':os.environ['OD3E_TRANSPORT_GIT'],'worktree':os.environ['OD3E_WORKTREE'],
  'branch':os.environ['OD3E_BRANCH'],'commit':optional(os.environ.get('OD3E_COMMIT','')),
  'origin_main_at_start':optional(os.environ.get('OD3E_ORIGIN_MAIN','')),
  'package':os.environ['OD3E_PACKAGE'],'package_sha256':os.environ['OD3E_PACKAGE_SHA'],
  'package_base_commit':optional(os.environ.get('OD3E_BASE','')),
  'execution_mode':'fixture-validation' if os.environ['OD3E_TEST_FIXTURE']=='1' else 'live-existing-cluster',
  'kubeconfig_path':os.environ['OD3E_KUBECONFIG'],'cluster_status':os.environ.get('OD3E_CLUSTER_STATUS','unknown'),
  'receipt_validation_status':os.environ.get('OD3E_VALIDATION_STATUS','unknown'),
  'phase3_receipt':os.environ.get('OD3E_RECEIPT'),'push_status':os.environ.get('OD3E_PUSH_STATUS','unknown'),
  'pr_status':os.environ.get('OD3E_PR_STATUS','unknown'),'pr_url':optional(os.environ.get('OD3E_PR_URL','')),
  'gate_timeout_seconds':int(os.environ['OD3E_GATE_TIMEOUT']),'live_timeout_seconds':int(os.environ['OD3E_LIVE_TIMEOUT']),
  'expected_nodes':['debian1','debian2','debian3'],'candidate':None,'enrollment_performed':False,
  'registry_mutation_allowed':False,'secret_values_recorded':False,
}
gates=[]
with open(os.environ['OD3E_GATES'],encoding='utf-8') as handle:
  for line in handle:
    line=line.rstrip('\n')
    if not line: continue
    name, required, status, code, log_path=line.split('\t',4)
    gates.append({'name':name,'required':required=='required','status':status,'exit_code':int(code),'log':log_path})
result['gates']=gates
transport=Path(os.environ.get('OD3E_GIT_TRANSPORT_JSON',''))
if transport.is_file():
  try: result['git_transport']=json.load(transport.open(encoding='utf-8'))
  except Exception as exc: result['git_transport_parse_error']=f'{type(exc).__name__}: {exc}'
receipt=Path(os.environ.get('OD3E_RECEIPT',''))/'phase3-cluster-result.json'
if receipt.is_file():
  try: inner=json.load(receipt.open(encoding='utf-8'))
  except Exception as exc: result['phase3_parse_error']=f'{type(exc).__name__}: {exc}'
  else:
    result['phase3_summary']={k:inner.get(k) for k in (
      'status','mode','topology_contract','control_plane','contract_sha256','step_count',
      'enrollment_performed','bootstrap_token_created','k3s_installed_or_reconfigured','node_added','node_removed',
      'node_rebooted','registry_mutation_performed','workload_routing_changed','existing_workload_deleted',
      'cluster_policy_mutation_performed','secret_scan_passed','git_publication_allowed','rollback','error')}
with open(os.environ['OD3E_RESULT_JSON'],'w',encoding='utf-8') as handle:
  json.dump(result,handle,indent=2,sort_keys=True); handle.write('\n')
PYRESULT
  cat >"$RESULT_DIR/PHASE3_RESULT.txt" <<TXT
Overdeck K3s Phase 3 existing-cluster result
Status: $state
Exit code: $rc
Generated UTC: $(date -u +%FT%TZ)
Repository: $REPO
Isolation root: $ISOLATION_ROOT
Transport repository: $TRANSPORT_GIT
Worktree: $WORKTREE
Branch: $BRANCH
Commit: ${COMMIT:-not-created}
Origin main at start: ${ORIGIN_MAIN:-unknown}
Package: $PACKAGE
Package SHA-256: $PACKAGE_SHA
Package base: ${BASE_COMMIT:-unknown}
Mode: $([[ $TEST_FIXTURE -eq 1 ]] && echo fixture-validation || echo live-existing-cluster)
Expected Nodes: debian1, debian2, debian3
Candidate enrollment: none
Cluster transaction: $CLUSTER_STATUS
Receipt validation: $RECEIPT_VALIDATION_STATUS
Registry mutation allowed: no
Push: $PUSH_STATUS
Pull request: $PR_STATUS${PR_URL:+ ($PR_URL)}
Secret values recorded: no

Return the sibling .tar.gz archive for review. Keep any draft pull request unmerged.
TXT
}

scan_result() {
  python3 - "$RESULT_DIR" <<'PYSCAN'
from pathlib import Path
import re, stat, sys
root=Path(sys.argv[1]).resolve()
patterns=(
 re.compile(rb'AGE-SECRET-KEY-[A-Z0-9-]+'),
 re.compile(rb'-----BEGIN (?:OPENSSH |RSA |EC |)PRIVATE KEY-----'),
 re.compile(rb'\bK10[0-9a-fA-F]{64}::[^\s]+'),
 re.compile(rb'(?i)\b[a-z0-9]{6}\.[a-z0-9]{16}\b'),
)
for path in root.rglob('*'):
  if path.is_symlink(): raise SystemExit(f'result contains symlink: {path.relative_to(root)}')
  info=path.stat()
  if path.is_dir(): continue
  if not stat.S_ISREG(info.st_mode): raise SystemExit(f'result contains special file: {path.relative_to(root)}')
  if info.st_size>32*1024*1024: raise SystemExit(f'result file exceeds 32 MiB: {path.relative_to(root)}')
  data=path.read_bytes()
  for pattern in patterns:
    if pattern.search(data): raise SystemExit(f'secret-like material found in result: {path.relative_to(root)}')
PYSCAN
}

archive_result() {
  local archive="${RESULT_DIR}.tar.gz"
  rm -rf -- "$GATE_TMP_DIR" 2>/dev/null || true
  scan_result || return 1
  printf '%s\n' "$archive" >"$RESULT_DIR/RESULT_ARCHIVE_PATH.txt"
  rm -f -- "$archive"
  tar -C "$(dirname -- "$RESULT_DIR")" -czf "$archive" "$(basename -- "$RESULT_DIR")" || return 1
  printf '%s\n' "$archive"
}

on_exit() {
  local rc=$?
  trap - EXIT
  rm -rf -- "$PAYLOAD_DIR" 2>/dev/null || true
  if ((FINISHED == 0)); then
    write_result failed "$rc" || true
    local archive
    archive=$(archive_result 2>/dev/null || true)
    printf '\nPhase 3 existing-cluster execution failed. The private transport/worktree and logs were left intact.\n' >&2
    [[ -z "$archive" ]] || printf 'Return this failure receipt: %s\n' "$archive" >&2
  fi
  exit "$rc"
}
trap on_exit EXIT
exec > >(tee -a "$RESULT_DIR/apply.log") 2>&1

run_gate() {
  local required=$1 name=$2; shift 2
  local safe=${name//[^A-Za-z0-9._-]/_}
  local logfile="$RESULT_DIR/logs/$safe.log" rc status start end
  log "gate: $name"
  start=$(date -u +%FT%TZ)
  set +e
  (
    printf 'started_utc: %s\nexecution_scope: local-isolated-phase-environment\ncommand:' "$start"
    printf ' env LOCAL_GATE_ACTIVE=1 CPU_GUARD_ACTIVE=1 OD_PHASE_GATE_LOCAL=1 TMPDIR=%q' "$GATE_TMP_DIR"
    printf ' timeout --signal=TERM --kill-after=30s %q' "${GATE_TIMEOUT_SEC}s"
    printf ' %q' "$@"; printf '\n\n'
    env LOCAL_GATE_ACTIVE=1 CPU_GUARD_ACTIVE=1 OD_PHASE_GATE_LOCAL=1 \
      TMPDIR="$GATE_TMP_DIR" PYTHONPYCACHEPREFIX="$GATE_TMP_DIR/pycache" PYTHONDONTWRITEBYTECODE=1 \
      timeout --signal=TERM --kill-after=30s "${GATE_TIMEOUT_SEC}s" "$@"
  ) >"$logfile" 2>&1
  rc=$?
  set -e
  end=$(date -u +%FT%TZ)
  printf '\nfinished_utc: %s\nexit_code: %s\n' "$end" "$rc" >>"$logfile"
  [[ $rc -eq 0 ]] && status=passed || status=failed
  printf '%s\t%s\t%s\t%s\t%s\n' "$name" "$required" "$status" "$rc" "logs/$safe.log" >>"$GATES_TSV"
  if [[ "$required" == required && $rc -ne 0 ]]; then
    tail -n 200 "$logfile" >&2 || true
    die "required gate failed: $name (rc=$rc)"
  fi
  ((rc == 0)) || log "optional gate failed: $name (recorded)"
}

record_skip() {
  local required=$1 name=$2 reason=$3
  local safe=${name//[^A-Za-z0-9._-]/_}
  printf '%s\n' "$reason" >"$RESULT_DIR/logs/$safe.log"
  printf '%s\t%s\tskipped\t0\t%s\n' "$name" "$required" "logs/$safe.log" >>"$GATES_TSV"
  log "gate skipped: $name — $reason"
}

log "verifying package $PACKAGE"
python3 - "$PACKAGE" "$PAYLOAD_DIR" <<'PYEXTRACT'
from pathlib import Path, PurePosixPath
from zipfile import ZipFile
import os, stat, sys
source=Path(sys.argv[1]); destination=Path(sys.argv[2]).resolve()
seen=set(); total=0
with ZipFile(source) as archive:
  infos=archive.infolist()
  if not infos or len(infos)>512: raise SystemExit('empty or oversized package member count')
  for info in infos:
    name=info.filename; pure=PurePosixPath(name); normalized=pure.as_posix().rstrip('/')
    if not name or pure.is_absolute() or '..' in pure.parts or '\\' in name: raise SystemExit(f'unsafe package path: {name!r}')
    if normalized in seen: raise SystemExit(f'duplicate package path: {name!r}')
    seen.add(normalized)
    mode=(info.external_attr>>16)&0o177777; kind=stat.S_IFMT(mode)
    if kind==stat.S_IFLNK: raise SystemExit(f'package symlink refused: {name!r}')
    if not info.is_dir() and kind not in {0,stat.S_IFREG}: raise SystemExit(f'package special file refused: {name!r}')
    if info.file_size>32*1024*1024: raise SystemExit(f'package member exceeds 32 MiB: {name!r}')
    total+=info.file_size
    if total>128*1024*1024: raise SystemExit('package uncompressed size exceeds 128 MiB')
    target=(destination/pure).resolve()
    if target!=destination and destination not in target.parents: raise SystemExit(f'package path escapes extraction root: {name!r}')
  for info in infos:
    target=(destination/PurePosixPath(info.filename)).resolve()
    if info.is_dir(): target.mkdir(parents=True,exist_ok=True); continue
    target.parent.mkdir(parents=True,exist_ok=True)
    descriptor=os.open(target,os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600)
    try:
      with os.fdopen(descriptor,'wb') as handle:
        handle.write(archive.read(info)); handle.flush(); os.fsync(handle.fileno())
    except BaseException:
      target.unlink(missing_ok=True); raise
    archived_mode=(info.external_attr>>16)&0o777
    os.chmod(target,0o755 if archived_mode&0o111 else 0o644)
PYEXTRACT
for file in \
  SHA256SUMS PACKAGE.json CHANGED_PATHS.txt IMPLEMENTATION_PATHS.txt STABLE_PATHS.txt DELETED_PATHS.txt \
  MANIFEST.md PR_BODY.md AGENT_INSTRUCTIONS.md VALIDATION.md TOPOLOGY_CONTRACT.json BASELINE_REGISTRY_SHA256.json \
  PLAN_DOCUMENT_UPDATE.json PLAN_INDEX_UPDATE.json phase3-core.patch phase3-full.patch \
  apply-overdeck-k3s-phase3-existing-cluster.sh \
  package-tools/prepare-isolated-git.py package-tools/test_prepare_isolated_git.py \
  package-tools/merge-plan-document.py package-tools/test_merge_plan_document.py \
  package-tools/merge-plan-index.py package-tools/test_merge_plan_index.py package-tools/test_runner_contract.py; do
  [[ -f "$PAYLOAD_DIR/$file" ]] || die "package missing $file"
done
python3 - "$SELF_PATH" "$PAYLOAD_DIR/apply-overdeck-k3s-phase3-existing-cluster.sh" <<'PYSELF'
from pathlib import Path
import hashlib,sys
external,packaged=map(Path,sys.argv[1:])
def digest(path): return hashlib.sha256(path.read_bytes()).hexdigest()
if digest(external)!=digest(packaged): raise SystemExit('launcher/package mismatch: use apply-overdeck-k3s-phase3-existing-cluster.sh distributed with this ZIP')
PYSELF
(
  cd "$PAYLOAD_DIR"
  sha256sum -c SHA256SUMS
)
python3 - "$PAYLOAD_DIR" <<'PYPACKAGE'
from pathlib import Path, PurePosixPath
import json,re,sys
root=Path(sys.argv[1]); doc=json.load((root/'PACKAGE.json').open(encoding='utf-8'))
if doc.get('schema_version')!=1 or doc.get('phase')!=3 or doc.get('runner_version')!=1: raise SystemExit('unsupported package schema/phase/runner')
if doc.get('name')!='overdeck-k3s-phase3-existing-cluster': raise SystemExit('unexpected package name')
base=doc.get('base_commit','')
if not re.fullmatch(r'[0-9a-f]{40}',base): raise SystemExit('invalid package base commit')
fields=('changed_paths','implementation_paths','stable_paths','conditionally_changed_paths','deleted_paths','archive_paths')
for field in fields:
  value=doc.get(field)
  if not isinstance(value,list) or len(value)!=len(set(value)): raise SystemExit(f'{field} must be a unique list')
  for item in value:
    pure=PurePosixPath(item)
    if not item or pure.is_absolute() or '..' in pure.parts: raise SystemExit(f'unsafe {field} path: {item!r}')
stable=set(doc['stable_paths']); semantic=set(doc['conditionally_changed_paths']); deleted=set(doc['deleted_paths']); implementation=set(doc['implementation_paths']); changed=set(doc['changed_paths'])
if stable&semantic or stable&deleted or semantic&deleted: raise SystemExit('path partitions overlap')
if implementation!=stable|semantic|deleted: raise SystemExit('implementation path partition is invalid')
if changed!=stable|semantic: raise SystemExit('changed_paths must name resulting paths only')
if semantic!={'docs/plans/2026-08-10-k3s-migration-execution.md','docs/plans/INDEX.md'}: raise SystemExit('unexpected semantic paths')
if doc.get('runtime_generated_paths')!=[]: raise SystemExit('existing-cluster phase must not generate repository paths at runtime')
actual=sorted(str(path.relative_to(root)) for path in root.rglob('*') if path.is_file())
if sorted(doc['archive_paths'])!=actual: raise SystemExit(f'archive path mismatch: missing={sorted(set(doc["archive_paths"])-set(actual))!r} unexpected={sorted(set(actual)-set(doc["archive_paths"]))!r}')
for filename,values in [('CHANGED_PATHS.txt',doc['changed_paths']),('IMPLEMENTATION_PATHS.txt',doc['implementation_paths']),('STABLE_PATHS.txt',doc['stable_paths']),('DELETED_PATHS.txt',doc['deleted_paths'])]:
  if (root/filename).read_text().splitlines()!=values: raise SystemExit(f'{filename} differs from PACKAGE.json')
for path in stable|semantic:
  if not (root/path).is_file(): raise SystemExit(f'implementation content missing: {path}')
for path in deleted:
  if (root/path).exists(): raise SystemExit(f'deleted path must not be shipped: {path}')
application=doc.get('patch_application') or {}
if application.get('core_patch')!='phase3-core.patch' or application.get('full_patch')!='phase3-full.patch': raise SystemExit('unsupported patch filenames')
if application.get('semantic_updates')!=['PLAN_DOCUMENT_UPDATE.json','PLAN_INDEX_UPDATE.json']: raise SystemExit('unsupported semantic update order')
execution=doc.get('execution') or {}
for key in ('candidate_enrollment','bootstrap_token','k3s_install_or_reconfigure','host_reboot','registry_mutation','workload_routing_change'):
  if execution.get(key) is not False: raise SystemExit(f'forbidden execution capability enabled: {key}')
if execution.get('expected_nodes')!=['debian1','debian2','debian3']: raise SystemExit('unexpected node topology')
transport=doc.get('git_transport') or {}
for key,value in {'source_checkout_fetch_allowed':False,'source_checkout_ref_mutation_allowed':False,'private_bare_repository':True,'worktree_outside_shared_checkout':True,'remote_main_connectivity_required':True,'base_ancestry_required':True,'invalid_ref_inventory_recorded':True}.items():
  if transport.get(key) is not value: raise SystemExit(f'unsupported Git transport contract: {key}')
ui=doc.get('ui') or {}
if ui.get('new_primitives') is not False or ui.get('astryx_required') is not False: raise SystemExit('Phase 3 unexpectedly declares a new UI primitive')
PYPACKAGE

cp "$PAYLOAD_DIR/MANIFEST.md" "$PAYLOAD_DIR/PACKAGE.json" "$PAYLOAD_DIR/SHA256SUMS" \
  "$PAYLOAD_DIR/AGENT_INSTRUCTIONS.md" "$PAYLOAD_DIR/VALIDATION.md" "$PAYLOAD_DIR/TOPOLOGY_CONTRACT.json" \
  "$PAYLOAD_DIR/BASELINE_REGISTRY_SHA256.json" "$PAYLOAD_DIR/PLAN_DOCUMENT_UPDATE.json" \
  "$PAYLOAD_DIR/PLAN_INDEX_UPDATE.json" "$RESULT_DIR/package/"
BASE_COMMIT=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["base_commit"])' "$PAYLOAD_DIR/PACKAGE.json")
mapfile -t EXPECTED_PATHS < <(python3 -c 'import json,sys; print("\n".join(json.load(open(sys.argv[1]))["changed_paths"]))' "$PAYLOAD_DIR/PACKAGE.json")
mapfile -t STABLE_PATHS < <(python3 -c 'import json,sys; print("\n".join(json.load(open(sys.argv[1]))["stable_paths"]))' "$PAYLOAD_DIR/PACKAGE.json")
mapfile -t SEMANTIC_PATHS < <(python3 -c 'import json,sys; print("\n".join(json.load(open(sys.argv[1]))["conditionally_changed_paths"]))' "$PAYLOAD_DIR/PACKAGE.json")
mapfile -t DELETED_PATHS < <(python3 -c 'import json,sys; print("\n".join(json.load(open(sys.argv[1]))["deleted_paths"]))' "$PAYLOAD_DIR/PACKAGE.json")

run_gate required semantic-plan-document-tests python3 "$PAYLOAD_DIR/package-tools/test_merge_plan_document.py"
run_gate required semantic-plan-index-tests python3 "$PAYLOAD_DIR/package-tools/test_merge_plan_index.py"
run_gate required isolated-git-transport-tests python3 "$PAYLOAD_DIR/package-tools/test_prepare_isolated_git.py"
run_gate required runner-publication-contract env OD_RUNNER_PATH="$SELF_PATH" python3 "$PAYLOAD_DIR/package-tools/test_runner_contract.py"

log "fetching current origin/main through a clean private Git transport"
run_gate required isolated-git-transport-live python3 "$PAYLOAD_DIR/package-tools/prepare-isolated-git.py" \
  --source-repo "$REPO" --isolation-root "$ISOLATION_ROOT" --worktree "$WORKTREE" \
  --branch "$BRANCH" --base-commit "$BASE_COMMIT" --result "$GIT_TRANSPORT_JSON"
ORIGIN_MAIN=$(python3 -c 'import json,sys; d=json.load(open(sys.argv[1],encoding="utf-8")); assert d["status"]=="success"; print(d["origin_main"])' "$GIT_TRANSPORT_JSON")
TRANSPORT_REPORTED=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1],encoding="utf-8"))["transport_repository"])' "$GIT_TRANSPORT_JSON")
WORKTREE_REPORTED=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1],encoding="utf-8"))["worktree"])' "$GIT_TRANSPORT_JSON")
[[ "$TRANSPORT_REPORTED" == "$TRANSPORT_GIT" ]] || die "isolated transport path differs from launcher contract"
[[ "$WORKTREE_REPORTED" == "$WORKTREE" && -d "$WORKTREE" ]] || die "isolated worktree path differs from launcher contract"
INVALID_REF_COUNT=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1],encoding="utf-8")).get("source_invalid_ref_count",0))' "$GIT_TRANSPORT_JSON")
if ((INVALID_REF_COUNT > 0)); then log "shared Git metadata contains $INVALID_REF_COUNT invalid ref(s); preserved unchanged and bypassed by private transport"; fi

log "applying corrected Phase 3 core patch with three-way conflict detection"
/usr/bin/git -C "$WORKTREE" apply --3way "$PAYLOAD_DIR/phase3-core.patch"
log "semantically merging the corrected Phase 2 and Phase 3 sections"
python3 "$PAYLOAD_DIR/package-tools/merge-plan-document.py" \
  --path "$WORKTREE/docs/plans/2026-08-10-k3s-migration-execution.md" \
  --spec "$PAYLOAD_DIR/PLAN_DOCUMENT_UPDATE.json" --apply >"$RESULT_DIR/logs/semantic-plan-document-merge.json"
log "semantically merging the K3s migration row in the plan index"
python3 "$PAYLOAD_DIR/package-tools/merge-plan-index.py" \
  --path "$WORKTREE/docs/plans/INDEX.md" --spec "$PAYLOAD_DIR/PLAN_INDEX_UPDATE.json" --apply \
  >"$RESULT_DIR/logs/semantic-plan-index-merge.json"
/usr/bin/git -C "$WORKTREE" reset --quiet
[[ -z "$(/usr/bin/git -C "$WORKTREE" diff --cached --name-only)" ]] || die "candidate index is not clean before verification"

python3 - "$WORKTREE" "$PAYLOAD_DIR/PACKAGE.json" "$PAYLOAD_DIR" <<'PYCHECK'
import json,subprocess,sys
from pathlib import Path
root=Path(sys.argv[1]); package=Path(sys.argv[2]); payload=Path(sys.argv[3]); doc=json.load(package.open())
expected=set(doc['changed_paths']); stable=set(doc['stable_paths']); semantic=set(doc['conditionally_changed_paths']); deleted=set(doc['deleted_paths'])
tracked=set(subprocess.check_output(['/usr/bin/git','-C',str(root),'diff','--name-only','HEAD'],text=True).splitlines())
untracked=set(subprocess.check_output(['/usr/bin/git','-C',str(root),'ls-files','--others','--exclude-standard'],text=True).splitlines())
actual=tracked|untracked
unexpected=actual-(expected|deleted)
missing=stable-actual
if missing or unexpected: raise SystemExit(f'pre-live path mismatch: missing_stable={sorted(missing)!r} unexpected={sorted(unexpected)!r} actual={sorted(actual)!r}')
for path in stable:
  if (root/path).read_bytes()!=(payload/path).read_bytes(): raise SystemExit(f'candidate content differs from package: {path}')
for path in deleted:
  if (root/path).exists(): raise SystemExit(f'deleted path remains in candidate: {path}')
for path in semantic:
  if path in actual and not (root/path).is_file(): raise SystemExit(f'semantic path is not a regular file: {path}')
PYCHECK

run_gate required launcher-bash-syntax bash -n "$SELF_PATH"
run_gate required phase3-bash-syntax bash -n \
  "$WORKTREE/tools/k3s/phase3-existing-cluster.sh" \
  "$WORKTREE/tools/k3s/test/phase3-existing-cluster.test.sh" \
  "$WORKTREE/tools/k3s/test/k3s-phase3-existing-cluster-html.test.sh" \
  "$WORKTREE/tools/k3s/test/no-withdrawn-node-name.test.sh"
run_gate required phase3-python-compile python3 -c '
import pathlib,sys
for value in sys.argv[1:]:
  path=pathlib.Path(value); compile(path.read_text(encoding="utf-8"),str(path),"exec")
' "$WORKTREE/tools/k3s/phase3-existing-cluster.py" "$WORKTREE/tools/k3s/lib/phase3_cluster_common.py" \
  "$WORKTREE/tools/k3s/validate-phase3-cluster-receipt.py" "$PAYLOAD_DIR/package-tools/merge-plan-document.py" \
  "$PAYLOAD_DIR/package-tools/merge-plan-index.py" "$PAYLOAD_DIR/package-tools/prepare-isolated-git.py"
run_gate required k3s-python-unit-and-failure-tests bash -c "cd \"$WORKTREE\" && python3 -m unittest discover -s tools/k3s/test -p 'test_*.py' -v"
run_gate required phase3-deterministic-transaction bash "$WORKTREE/tools/k3s/test/phase3-existing-cluster.test.sh"
run_gate required phase3-presentation-static bash "$WORKTREE/tools/k3s/test/k3s-phase3-existing-cluster-html.test.sh"
run_gate required no-withdrawn-node-name bash "$WORKTREE/tools/k3s/test/no-withdrawn-node-name.test.sh"
run_gate required phase2-deterministic-regression bash "$WORKTREE/tools/k3s/test/phase2-enrollment.test.sh"
run_gate required phase2-presentation-regression bash "$WORKTREE/tools/k3s/test/k3s-phase2-enrollment-html.test.sh"
run_gate required phase1-fake-cluster-regression bash "$WORKTREE/tools/k3s/test/phase1-control-plane.test.sh"
run_gate required phase0-collector-regression bash "$WORKTREE/tools/k3s/test/phase0-discover.test.sh"
run_gate required phase0-presentation-regression bash "$WORKTREE/tools/k3s/test/k3s-migration-audit-html.test.sh"
run_gate required execution-locality env OD_EXPECTED_WORKTREE="$WORKTREE" node -e '
const fs=require("node:fs"),path=require("node:path");const expected=process.env.OD_EXPECTED_WORKTREE;
if(!expected||!fs.existsSync(path.join(expected,".git"))){console.error(`candidate worktree is not locally visible: ${expected||"<unset>"}`);process.exit(81)}
console.log(`candidate_worktree=${fs.realpathSync(expected)}`);console.log(`node_exec=${process.execPath}`);console.log(`phase_gate_local=${process.env.OD_PHASE_GATE_LOCAL||""}`);
'
run_gate required legacy-k3s-unit node "$WORKTREE/modules/workstation/claude/tests/k3s-remote-build.test.mjs"
run_gate required fleet-core bash -c "cd \"$WORKTREE\" && node --test modules/fleet/test/loader.test.mjs modules/fleet/test/expand.test.mjs modules/fleet/test/engine.test.mjs modules/fleet/test/harden.test.mjs"
run_gate required fleet-cli bash -c "cd \"$WORKTREE\" && bash modules/fleet/test/cli.test.sh"
if [[ "$HOME" == /home/user ]]; then
  run_gate optional remote-seat-transaction env HOME="$HOME" node "$WORKTREE/modules/workstation/claude/tests/remote-seat-provision.test.mjs"
else
  record_skip optional remote-seat-transaction "baseline remote-seat test asserts canonical /home/user; current HOME is $HOME"
fi
run_gate optional legacy-k3s-integration bash "$WORKTREE/modules/workstation/claude/tests/k3s-remote-build-integration.sh"
if python3 -c 'import pytest' >/dev/null 2>&1; then
  run_gate optional factory-kubernetes-regression bash -c "cd \"$WORKTREE\" && PYTHONPATH=modules/harness/factory python3 -m pytest -q modules/harness/factory/tests/test_kubernetes_job.py"
else
  record_skip optional factory-kubernetes-regression "python pytest is not installed; Phase 3 does not modify Factory code"
fi
if command -v pnpm >/dev/null 2>&1 && [[ -d "$WORKTREE/node_modules" ]]; then
  run_gate required deck-ui-test bash -c "cd \"$WORKTREE\" && pnpm --filter @overdeck/deck-ui test"
  run_gate required deck-ui-typecheck bash -c "cd \"$WORKTREE\" && pnpm --filter @overdeck/deck-ui typecheck"
  run_gate required web-build bash -c "cd \"$WORKTREE\" && pnpm --filter web build"
  run_gate required web-typecheck bash -c "cd \"$WORKTREE\" && pnpm --filter web typecheck"
else
  record_skip optional deck-ui-and-web "pnpm workspace dependencies are not installed; only one test fixture string changed"
fi
run_gate required git-diff-check bash -c "/usr/bin/git -C \"$WORKTREE\" diff --check && /usr/bin/git -C \"$WORKTREE\" diff --cached --check"

python3 - "$WORKTREE" "$REGISTRY_BEFORE" <<'PYREG'
from pathlib import Path
import hashlib,json,sys
root=Path(sys.argv[1]); out=Path(sys.argv[2])
files=['modules/fleet/fleet.json','modules/workstation/claude/buildbox-hosts.json']
data={p:hashlib.sha256((root/p).read_bytes()).hexdigest() for p in files}
out.write_text(json.dumps({'schema_version':1,'files':data},indent=2,sort_keys=True)+'\n')
PYREG

CLUSTER_ARGS=(--repo-root "$WORKTREE" --receipt-dir "$PHASE3_RECEIPT" --lock-file "$RESULT_DIR/phase3.lock" \
  --ssh-timeout "$SSH_TIMEOUT_SEC" --node-timeout "$NODE_TIMEOUT_SEC" --canary-timeout "$CANARY_TIMEOUT_SEC")
if ((TEST_FIXTURE)); then
  CLUSTER_ARGS+=(--fixture "$WORKTREE/tools/k3s/test/fixtures/phase3-existing-cluster.json")
else
  CLUSTER_ARGS+=(--kubeconfig "$KUBECONFIG_PATH" --yes-existing-cluster)
fi

log "running complete Phase 3 existing-cluster transaction for debian1, debian2, and debian3"
CLUSTER_STATUS=running
set +e
env LOCAL_GATE_ACTIVE=1 CPU_GUARD_ACTIVE=1 OD_PHASE_GATE_LOCAL=1 \
  TMPDIR="$GATE_TMP_DIR" PYTHONPYCACHEPREFIX="$GATE_TMP_DIR/pycache" PYTHONDONTWRITEBYTECODE=1 \
  timeout --signal=INT --kill-after=60s "${LIVE_TIMEOUT_SEC}s" \
  "$WORKTREE/tools/k3s/phase3-existing-cluster.sh" "${CLUSTER_ARGS[@]}" \
  >"$RESULT_DIR/logs/phase3-cluster.stdout.log" 2>"$RESULT_DIR/logs/phase3-cluster.stderr.log"
cluster_rc=$?
set -e
printf 'phase3-existing-cluster\trequired\t%s\t%s\t%s\n' "$([[ $cluster_rc -eq 0 ]] && echo passed || echo failed)" "$cluster_rc" "logs/phase3-cluster.stderr.log" >>"$GATES_TSV"
if ((cluster_rc != 0)); then
  CLUSTER_STATUS=failed
  tail -n 240 "$RESULT_DIR/logs/phase3-cluster.stderr.log" >&2 || true
  die "Phase 3 existing-cluster transaction failed (rc=$cluster_rc)"
fi
CLUSTER_STATUS=success

log "independently validating the Phase 3 receipt before Git staging"
VALIDATE_ARGS=(--receipt "$PHASE3_RECEIPT")
((TEST_FIXTURE)) && VALIDATE_ARGS+=(--allow-fixture)
set +e
"$WORKTREE/tools/k3s/validate-phase3-cluster-receipt.py" "${VALIDATE_ARGS[@]}" \
  >"$RESULT_DIR/logs/phase3-receipt-validation.json" 2>"$RESULT_DIR/logs/phase3-receipt-validation.err"
validation_rc=$?
set -e
printf 'phase3-receipt-validation\trequired\t%s\t%s\t%s\n' "$([[ $validation_rc -eq 0 ]] && echo passed || echo failed)" "$validation_rc" "logs/phase3-receipt-validation.json" >>"$GATES_TSV"
if ((validation_rc != 0)); then
  RECEIPT_VALIDATION_STATUS=failed
  cat "$RESULT_DIR/logs/phase3-receipt-validation.json" >&2 || true
  cat "$RESULT_DIR/logs/phase3-receipt-validation.err" >&2 || true
  die "Phase 3 receipt validation failed"
fi
RECEIPT_VALIDATION_STATUS=passed

python3 - "$WORKTREE" "$REGISTRY_AFTER" "$REGISTRY_BEFORE" <<'PYREGAFTER'
from pathlib import Path
import hashlib,json,sys
root=Path(sys.argv[1]); out=Path(sys.argv[2]); before=json.load(open(sys.argv[3],encoding='utf-8'))
files=['modules/fleet/fleet.json','modules/workstation/claude/buildbox-hosts.json']
data={p:hashlib.sha256((root/p).read_bytes()).hexdigest() for p in files}
if data!=before['files']: raise SystemExit(f'registry bytes changed during Phase 3: before={before["files"]!r} after={data!r}')
out.write_text(json.dumps({'schema_version':1,'files':data,'unchanged':True},indent=2,sort_keys=True)+'\n')
PYREGAFTER
[[ -z "$(/usr/bin/git -C "$WORKTREE" diff --cached --name-only)" ]] || die "candidate index changed before receipt authorization"

log "staging only package-declared repository paths after receipt authorization"
/usr/bin/git -C "$WORKTREE" add -A -- "${EXPECTED_PATHS[@]}" "${DELETED_PATHS[@]}"
python3 - "$WORKTREE" "$PAYLOAD_DIR/PACKAGE.json" <<'PYSTAGE'
import json,subprocess,sys
root,package=sys.argv[1:]
doc=json.load(open(package,encoding='utf-8')); expected=set(doc['changed_paths']); stable=set(doc['stable_paths']); semantic=set(doc['conditionally_changed_paths']); deleted=set(doc['deleted_paths'])
actual=set(subprocess.check_output(['/usr/bin/git','-C',root,'diff','--cached','--name-only'],text=True).splitlines())
missing=stable-actual; unexpected=actual-(expected|deleted)
if missing or unexpected: raise SystemExit(f'staged path mismatch: missing_stable={sorted(missing)!r} unexpected={sorted(unexpected)!r} actual={sorted(actual)!r}')
if not actual: raise SystemExit('no repository delta was staged')
PYSTAGE
/usr/bin/git -C "$WORKTREE" diff --cached --check

log "committing the exact corrected Phase 3 candidate"
GIT_AUTHOR_NAME=${GIT_AUTHOR_NAME:-Overdeck Phase Automation}
GIT_AUTHOR_EMAIL=${GIT_AUTHOR_EMAIL:-overdeck-phase@local}
GIT_COMMITTER_NAME=${GIT_COMMITTER_NAME:-$GIT_AUTHOR_NAME}
GIT_COMMITTER_EMAIL=${GIT_COMMITTER_EMAIL:-$GIT_AUTHOR_EMAIL}
export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL
/usr/bin/git -C "$WORKTREE" commit -m "k3s: verify and harden existing three-node cluster"
COMMIT=$(/usr/bin/git -C "$WORKTREE" rev-parse HEAD)
/usr/bin/git -C "$WORKTREE" show --stat --oneline --decorate HEAD >"$RESULT_DIR/git-show.txt"
/usr/bin/git -C "$WORKTREE" diff --name-status "${COMMIT}^" "$COMMIT" >"$RESULT_DIR/committed-paths.txt"
/usr/bin/git -C "$WORKTREE" diff "${COMMIT}^" "$COMMIT" --binary | sha256sum | awk '{print $1}' >"$RESULT_DIR/committed-patch.sha256"

log "removing ephemeral gate scratch before pre-publication scanning"
rm -rf -- "$GATE_TMP_DIR"
log "scanning the complete pre-publication result for secret-like material"
scan_result || die "pre-publication result secret scan failed"

if ((PUSH)); then
  log "pushing candidate branch $BRANCH"
  set +e
  /usr/bin/git -C "$WORKTREE" push -u origin "HEAD:refs/heads/$BRANCH" >"$RESULT_DIR/logs/git-push.log" 2>&1
  push_rc=$?
  set -e
  if ((push_rc != 0)); then PUSH_STATUS=failed; tail -n 200 "$RESULT_DIR/logs/git-push.log" >&2 || true; die "candidate branch push failed"; fi
  PUSH_STATUS=pushed
else
  PUSH_STATUS=skipped
fi

if [[ "$PUSH_STATUS" == pushed && $CREATE_PR -eq 1 ]]; then
  if command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1; then
    log "opening a draft corrected Phase 3 pull request"
    set +e
    PR_URL=$(cd "$WORKTREE" && gh pr create --draft --base main --head "$BRANCH" \
      --title "k3s: verify and harden existing three-node cluster" --body-file "$PAYLOAD_DIR/PR_BODY.md" \
      2>"$RESULT_DIR/logs/gh-pr.err")
    pr_rc=$?
    set -e
    [[ $pr_rc -eq 0 ]] && PR_STATUS=created || PR_STATUS=failed
  else
    PR_STATUS=skipped-gh-unavailable
  fi
else
  PR_STATUS=skipped
fi

write_result success 0
archive=$(archive_result) || die "final result sanitization or archive creation failed"
FINISHED=1
trap - EXIT
rm -rf -- "$PAYLOAD_DIR" 2>/dev/null || true

printf '\nPhase 3 existing-cluster package execution completed.\n'
printf 'Isolation root: %s\nTransport repository: %s\nWorktree: %s\nBranch: %s\nCommit: %s\n' "$ISOLATION_ROOT" "$TRANSPORT_GIT" "$WORKTREE" "$BRANCH" "$COMMIT"
printf 'Expected Nodes: debian1, debian2, debian3\nMode: %s\nCluster transaction: %s\nReceipt validation: %s\n' "$([[ $TEST_FIXTURE -eq 1 ]] && echo fixture-validation || echo live-existing-cluster)" "$CLUSTER_STATUS" "$RECEIPT_VALIDATION_STATUS"
printf 'Push: %s\nPull request: %s%s\n' "$PUSH_STATUS" "$PR_STATUS" "${PR_URL:+ ($PR_URL)}"
printf 'Return this archive for Phase 3 review: %s\n' "$archive"
