#!/usr/bin/env sh
set -eu
[ "${WHEELIE_INSTALL_DEBUG:-}" != 1 ] || set -x

# Public Wheelie installer template.
#
# The generated https://get.wheelie.dev script resolves checksum-bound channel
# metadata. This checked template keeps the public bootstrap behavior auditable:
# install the global `wheelie` command and its same-platform helper executables
# from one immutable artifact, verify checksums and optional legacy signatures,
# and do not install project-local overlays.

die() {
  printf '%s\n' "wheelie installer: $*" >&2
  exit 1
}

need_command() {
  command -v "$1" >/dev/null 2>&1 || die "missing required command: $1"
}

cosign_available() {
  command -v cosign >/dev/null 2>&1
}

strict_verify_enabled() {
  [ "${WHEELIE_STRICT_VERIFY:-}" = "1" ]
}

print_cosign_repair_guidance() {
  printf '%s\n' \
    'wheelie installer: the selected verification policy requires cosign, but cosign is not on PATH.' \
    '' \
    'Channels declaring WHEELIE_SIGNATURE_MODE=cosign fail closed unless signed metadata, release manifest, and the selected artifact graph verify locally. Explicitly unsigned channels remain checksum-bound only when channel policy declares WHEELIE_SIGNATURE_MODE=none.' \
    'WHEELIE_STRICT_VERIFY=1 additionally rejects explicitly unsigned channels.' \
    '' \
    'Repair for strict mode:' \
    '  macOS/Linux Homebrew: brew install cosign' \
    '  Other Linux package managers: use Sigstore cosign installation guidance for your distro.' \
    '  Sigstore docs: https://docs.sigstore.dev/cosign/installation/' \
    '' \
    'Then rerun:' \
    '  WHEELIE_STRICT_VERIFY=1 curl -fsSL https://get.wheelie.dev | sh' \
    '' \
    'Machine-readable repair record:' \
    '{"schema_version":"wheelie_installer_repair/v1","missing_command":"cosign","required_for":"cosign_declared_channel_or_strict_verification","strict_verification_env":"WHEELIE_STRICT_VERIFY=1","cosign_declared_channel_blocks_without_cosign":true,"repair_commands":[{"platform":"macos_or_linux_homebrew","command":"brew install cosign"}],"docs_url":"https://docs.sigstore.dev/cosign/installation/"}' >&2
}

require_cosign_for_strict() {
  if [ "${WHEELIE_ALLOW_UNSIGNED:-}" = "1" ]; then
    return
  fi
  if strict_verify_enabled && ! cosign_available; then
    print_cosign_repair_guidance
    exit 1
  fi
}

normalize_os() {
  os_name=$(uname -s | tr '[:upper:]' '[:lower:]')
  case "$os_name" in
    darwin) printf '%s' darwin ;;
    linux) printf '%s' linux ;;
    *) die "unsupported OS: $os_name" ;;
  esac
}

normalize_arch() {
  arch_name=$(uname -m)
  # A shell running under Rosetta reports x86_64 even on Apple Silicon. Select
  # the hardware-native artifact so Wheelie does not remain translated after
  # installation.
  if [ "$(uname -s)" = "Darwin" ] && [ "$arch_name" = "x86_64" ] && command -v sysctl >/dev/null 2>&1; then
    translated=$(sysctl -in sysctl.proc_translated 2>/dev/null || printf '0')
    arm64_capable=$(sysctl -in hw.optional.arm64 2>/dev/null || printf '0')
    if [ "$translated" = "1" ] && [ "$arm64_capable" = "1" ]; then
      printf '%s' arm64
      return
    fi
  fi
  case "$arch_name" in
    arm64|aarch64) printf '%s' arm64 ;;
    x86_64|amd64) printf '%s' amd64 ;;
    *) die "unsupported architecture: $arch_name" ;;
  esac
}

sha256_file() {
  path=$1
  if command -v sha256sum >/dev/null 2>&1; then
    sha256sum "$path" | awk '{print $1}'
    return
  fi
  if command -v shasum >/dev/null 2>&1; then
    shasum -a 256 "$path" | awk '{print $1}'
    return
  fi
  die 'missing sha256sum or shasum for artifact verification'
}

normalize_sha256() {
  digest=$1
  case "$digest" in
    sha256:*) printf '%s' "${digest#sha256:}" ;;
    *) printf '%s' "$digest" ;;
  esac
}

json_escape() {
  printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'
}

telemetry_emit_install() {
  exit_code=${1:-0}
  case "$exit_code" in ''|*[!0-9]*) exit_code=0 ;; esac
  if [ "$exit_code" = "0" ]; then command_outcome=success; else command_outcome=failure; fi
  payload=$(cat <<EOF
{"eventType":"wheelie_cli_command","surface":"wheelie.installer","subSurface":"install","deviceType":"cli","commandName":"install","commandAction":"$channel","commandOutcome":"$command_outcome","commandExitCode":$exit_code,"clientPlatform":"cli","clientEnvironment":"prod","appVersion":"$(json_escape "${version:-unknown}")","releaseCommit":"unknown","networkClass":"unknown"}
EOF
)
  if [ "${WHEELIE_TELEMETRY_SYNC:-0}" = "1" ]; then
    curl -fsS --connect-timeout 0.2 --max-time 0.7 \
      -H 'Content-Type: application/json' \
      -H 'Accept: application/json' \
      -X POST \
      --data "$payload" \
      'https://wheelie.dev/saxophone_telemetry.track_event' >/dev/null 2>&1 || true
  else
    curl -fsS --connect-timeout 0.2 --max-time 0.7 \
      -H 'Content-Type: application/json' \
      -H 'Accept: application/json' \
      -X POST \
      --data "$payload" \
      'https://wheelie.dev/saxophone_telemetry.track_event' >/dev/null 2>&1 &
  fi
  return 0
}

verify_checksum() {
  artifact=$1
  checksum_file=$2
  expected=$(normalize_sha256 "$(awk '{print $1}' "$checksum_file")")
  actual=$(sha256_file "$artifact")
  if [ "$expected" != "$actual" ]; then
    die "checksum mismatch for $artifact"
  fi
}

verify_signature() {
  artifact=$1
  signature=$2
  certificate=$3
  cert_identity_regexp=${4:-${WHEELIE_COSIGN_CERT_IDENTITY_REGEXP:-'.*(cloud-build|wheelie-release-signer)@thoughter\.iam\.gserviceaccount\.com'}}
  oidc_issuer=${5:-${WHEELIE_COSIGN_CERT_OIDC_ISSUER:-https://accounts.google.com}}
  [ -s "$signature" ] || die "missing detached signature for $artifact"
  [ -s "$certificate" ] || die "missing signing certificate for $artifact"
  if ! cosign_available; then
    print_cosign_repair_guidance
    exit 1
  fi
  cert_identity=${WHEELIE_COSIGN_CERT_IDENTITY:-}
  if [ -n "$cert_identity" ]; then
    printf '%s\n' "$cert_identity" | grep -E "$cert_identity_regexp" >/dev/null 2>&1 ||
      die 'configured exact cosign identity is outside signed channel signer policy'
    cosign verify-blob \
      --certificate "$certificate" \
      --certificate-identity "$cert_identity" \
      --certificate-oidc-issuer "$oidc_issuer" \
      --signature "$signature" \
      "$artifact" >/dev/null
    signature_verification_mode=cosign_verified
    return
  fi
  [ -n "$cert_identity_regexp" ] || die 'cosign-declared Wheelie channel omitted signer identity policy'
  [ -n "$oidc_issuer" ] || die 'cosign-declared Wheelie channel omitted signer OIDC issuer policy'
  cosign verify-blob \
    --certificate "$certificate" \
    --certificate-identity-regexp "$cert_identity_regexp" \
    --certificate-oidc-issuer "$oidc_issuer" \
    --signature "$signature" \
    "$artifact" >/dev/null
  signature_verification_mode=cosign_verified
}

download() {
  url=$1
  destination=$2
  curl -fsSL -o "$destination" "$url"
}

metadata_value() {
  key=$1
  metadata_file=$2
  awk -F= -v wanted="$key" '$1 == wanted { sub(/^[^=]*=/, "", $0); print; exit }' "$metadata_file"
}

json_file_string_value() {
  key=$1
  json_file=$2
  awk -v key="$key" '
    {
      pattern = "\\\"" key "\\\"[[:space:]]*:[[:space:]]*\\\""
      if (match($0, pattern)) {
        value = substr($0, RSTART + RLENGTH)
        sub(/\".*/, "", value)
        print value
        exit
      }
    }
  ' "$json_file"
}

json_file_number_value() {
  key=$1
  json_file=$2
  awk -v key="$key" '
    {
      pattern = "\\\"" key "\\\"[[:space:]]*:[[:space:]]*[0-9]+"
      if (match($0, pattern)) {
        value = substr($0, RSTART, RLENGTH)
        sub(/^.*:[[:space:]]*/, "", value)
        print value
        exit
      }
    }
  ' "$json_file"
}

manifest_file_digest() {
  object_path=$1
  manifest_path=$2
  awk -v wanted="$object_path" '
    $0 ~ /"objectPath"[[:space:]]*:/ {
      in_file = index($0, "\"" wanted "\"") > 0
      next
    }
    in_file && $0 ~ /"sha256"[[:space:]]*:/ {
      value = $0
      sub(/^.*"sha256"[[:space:]]*:[[:space:]]*"/, "", value)
      sub(/".*/, "", value)
      print value
      exit
    }
  ' "$manifest_path"
}

require_unsigned_integer() {
  label=$1
  value=$2
  case "$value" in ''|*[!0-9]*) die "$label is missing or malformed" ;; esac
}

require_fresh_metadata() {
  expires_at=$1
  printf '%s\n' "$expires_at" | grep '^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9]\(\.[0-9][0-9]*\)\{0,1\}Z$' >/dev/null 2>&1 ||
    die 'signed channel metadata expiry is missing or malformed'
  normalized_expiry=$(printf '%s' "$expires_at" | sed 's/\.[0-9][0-9]*Z$/Z/')
  now_utc=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
  awk -v expiry="$normalized_expiry" -v now="$now_utc" 'BEGIN { exit(expiry > now ? 0 : 1) }' ||
    die 'signed channel metadata is stale'
}

version_less_than() {
  awk -v a="$1" -v b="$2" '
    function key(v, parts, count, out, i) {
      sub(/^v/, "", v)
      count = split(v, parts, /[._-]/)
      out = ""
      for (i = 1; i <= 6; i += 1) {
        if (i <= count && parts[i] ~ /^[0-9]+$/) out = out sprintf("%08d", parts[i])
        else out = out "00000000"
      }
      return out
    }
    BEGIN { exit(key(a) < key(b) ? 0 : 1) }
  '
}

platform_metadata_key() {
  key_os=$(printf '%s' "$os" | tr '[:lower:]' '[:upper:]')
  key_arch=$(printf '%s' "$arch" | tr '[:lower:]' '[:upper:]')
  printf 'WHEELIE_ARTIFACT_SHA256_%s_%s' "$key_os" "$key_arch"
}

platform_component_metadata_key() {
  prefix=$1
  key_os=$(printf '%s' "$os" | tr '[:lower:]' '[:upper:]')
  key_arch=$(printf '%s' "$arch" | tr '[:lower:]' '[:upper:]')
  printf '%s_%s_%s' "$prefix" "$key_os" "$key_arch"
}

trim_trailing_slash() {
  value=$1
  while [ "${value%/}" != "$value" ]; do
    value=${value%/}
  done
  printf '%s' "$value"
}

canonical_dir() {
  mkdir -p "$1"
  case "$1" in
    /*) printf '%s' "$1" ;;
    *) printf '%s/%s' "$(pwd)" "$1" ;;
  esac
}

assert_metadata_mentions_digest() {
  metadata_path=$1
  label=$2
  digest=$3
  if ! grep "$digest" "$metadata_path" >/dev/null 2>&1; then
    die "$label metadata does not reference artifact digest sha256:$digest"
  fi
}

verify_manifest_file_digest() {
  path=$1
  expected=$2
  label=$3
  [ -n "$expected" ] || die "signed release manifest omitted $label"
  [ "$(sha256_file "$path")" = "$(normalize_sha256 "$expected")" ] ||
    die "signed release manifest $label digest mismatch"
}

executable_metadata_value() {
  executable=$1
  key=$2
  awk -F= -v wanted="$key" '$1 == wanted { sub(/^[^=]*=/, "", $0); gsub(/^"|"$/, "", $0); print; exit }' "$executable"
}

json_metadata_value() {
  key=$1
  printf '%s\n' "$2" | awk -v key="$key" '
    {
      pattern = "\\\"" key "\\\"[[:space:]]*:[[:space:]]*\\\""
      if (match($0, pattern)) {
        value = substr($0, RSTART + RLENGTH)
        sub(/\".*/, "", value)
        print value
        exit
      }
    }
  '
}

validate_staged_package() {
  staged_dir=$1
  [ "$package_layout" = wheelie_atomic_runtime_v1 ] || die "unsupported signed package layout: ${package_layout:-<empty>}"
  [ "$native_helper_name" = wheelie-native ] || die "signed package selected unsupported native helper name: ${native_helper_name:-<empty>}; expected wheelie-native"
  [ -x "$staged_dir/wheelie" ] || die 'release archive did not contain an executable wheelie file at archive root'
  [ -x "$staged_dir/wheelie-daemon" ] || die 'release archive did not contain an executable wheelie-daemon file at archive root'
  [ -x "$staged_dir/wheelie-native" ] || die 'signed package requires an executable wheelie-native file at archive root'
  [ -x "$staged_dir/wheelie-recovery" ] || die 'signed package requires an executable independent wheelie-recovery kernel at archive root'
  if [ -e "$staged_dir/wheelie-native-v8" ]; then
    die 'release archive contains stale helper name wheelie-native-v8; expected only wheelie-native'
  fi

  staged_shell_sha256=$(sha256_file "$staged_dir/wheelie")
  staged_daemon_sha256=$(sha256_file "$staged_dir/wheelie-daemon")
  staged_native_sha256=$(sha256_file "$staged_dir/wheelie-native")
  staged_recovery_sha256=$(sha256_file "$staged_dir/wheelie-recovery")
  if [ -n "$expected_shell_sha256" ] && [ "$staged_shell_sha256" != "$expected_shell_sha256" ]; then
    die 'signed shell component digest does not match the extracted wheelie executable'
  fi
  if [ -n "$expected_daemon_sha256" ] && [ "$staged_daemon_sha256" != "$expected_daemon_sha256" ]; then
    die 'signed daemon component digest does not match the extracted wheelie-daemon executable'
  fi
  if [ -n "$expected_native_sha256" ] && [ "$staged_native_sha256" != "$expected_native_sha256" ]; then
    die 'signed native-helper component digest does not match the extracted wheelie-native executable'
  fi
  if [ -n "$expected_recovery_sha256" ] && [ "$staged_recovery_sha256" != "$expected_recovery_sha256" ]; then
    die 'signed recovery-kernel component digest does not match the extracted wheelie-recovery executable'
  fi
  assert_metadata_mentions_digest "$sbom_path" "SBOM shell component" "$staged_shell_sha256"
  assert_metadata_mentions_digest "$sbom_path" "SBOM daemon component" "$staged_daemon_sha256"
  assert_metadata_mentions_digest "$sbom_path" "SBOM native-helper component" "$staged_native_sha256"
  assert_metadata_mentions_digest "$sbom_path" "SBOM recovery-kernel component" "$staged_recovery_sha256"

  staged_shell_version=$(executable_metadata_value "$staged_dir/wheelie" WHEELIE_INSTALLED_VERSION)
  staged_shell_channel=$(executable_metadata_value "$staged_dir/wheelie" WHEELIE_INSTALLED_CHANNEL)
  staged_shell_source_commit=$(executable_metadata_value "$staged_dir/wheelie" WHEELIE_SOURCE_COMMIT)
  if [ -n "$staged_shell_version" ]; then
    [ "$staged_shell_version" = "$version" ] || die "shell package version mismatch: expected $version got ${staged_shell_version:-<empty>}"
    [ "$staged_shell_channel" = "$artifact_channel" ] || die "shell package artifact channel mismatch: expected $artifact_channel got ${staged_shell_channel:-<empty>}"
    if [ "$source_commit" != unknown ] && [ "$staged_shell_source_commit" != "$source_commit" ]; then
      die "shell package source commit mismatch: expected $source_commit got ${staged_shell_source_commit:-<empty>}"
    fi
  else
    if ! cli_metadata=$("$staged_dir/wheelie" version --json 2>/dev/null); then
      die 'wheelie failed its staged native CLI compatibility probe'
    fi
    staged_cli_version=$(json_metadata_value version "$cli_metadata")
    staged_cli_channel=$(json_metadata_value channel "$cli_metadata")
    staged_cli_commit=$(json_metadata_value commit "$cli_metadata")
    [ "$staged_cli_version" = "$version" ] || die "native CLI package version mismatch: expected $version got ${staged_cli_version:-<empty>}"
    [ "$staged_cli_channel" = "$artifact_channel" ] || die "native CLI package artifact channel mismatch: expected $artifact_channel got ${staged_cli_channel:-<empty>}"
    if [ "$source_commit" != unknown ] && [ "$staged_cli_commit" != "$source_commit" ]; then
      die "native CLI package source commit mismatch: expected $source_commit got ${staged_cli_commit:-<empty>}"
    fi
  fi

  if ! native_metadata=$("$staged_dir/wheelie-native" version --json 2>/dev/null); then
    die 'wheelie-native failed its staged version compatibility probe'
  fi
  staged_native_version=$(json_metadata_value version "$native_metadata")
  staged_native_channel=$(json_metadata_value channel "$native_metadata")
  staged_native_commit=$(json_metadata_value commit "$native_metadata")
  staged_native_os=$(json_metadata_value goos "$native_metadata")
  staged_native_arch=$(json_metadata_value goarch "$native_metadata")
  [ "$staged_native_version" = "$version" ] || die "native-helper package version mismatch: expected $version got ${staged_native_version:-<empty>}"
  [ "$staged_native_channel" = "$artifact_channel" ] || die "native-helper package artifact channel mismatch: expected $artifact_channel got ${staged_native_channel:-<empty>}"
  [ "$staged_native_os" = "$os" ] || die "native-helper architecture mismatch: expected $os/$arch got ${staged_native_os:-<empty>}/${staged_native_arch:-<empty>}"
  [ "$staged_native_arch" = "$arch" ] || die "native-helper architecture mismatch: expected $os/$arch got ${staged_native_os:-<empty>}/${staged_native_arch:-<empty>}"
  if [ "$source_commit" != unknown ] && [ "$staged_native_commit" != "$source_commit" ]; then
    die "native-helper package source commit mismatch: expected $source_commit got ${staged_native_commit:-<empty>}"
  fi

  if ! daemon_metadata=$("$staged_dir/wheelie-daemon" package-smoke --json 2>/dev/null); then
    die 'wheelie-daemon failed its bounded staged package smoke'
  fi
  staged_daemon_version=$(json_metadata_value version "$daemon_metadata")
  staged_daemon_channel=$(json_metadata_value channel "$daemon_metadata")
  staged_daemon_adoption_protocol=$(json_metadata_value session_lease_adoption_protocol "$daemon_metadata")
  [ "$staged_daemon_version" = "$version" ] || die "daemon package version mismatch: expected $version got ${staged_daemon_version:-<empty>}"
  [ "$staged_daemon_channel" = "$artifact_channel" ] || die "daemon package artifact channel mismatch: expected $artifact_channel got ${staged_daemon_channel:-<empty>}"
  [ "$staged_daemon_adoption_protocol" = wheelie_session_lease_adoption/v1 ] || die 'candidate daemon does not implement the required session/lease/fence adoption protocol'
  if ! recovery_metadata=$("$staged_dir/wheelie-recovery" package-smoke --json 2>/dev/null); then
    die 'independent recovery kernel failed its bounded staged package smoke'
  fi
  staged_recovery_protocol=$(json_metadata_value activation_protocol "$recovery_metadata")
  [ "$staged_recovery_protocol" = wheelie_activation_transaction/v1 ] || die 'candidate recovery kernel does not implement the required atomic activation protocol'
  if ! capability_metadata=$("$staged_dir/wheelie-native" capability list --json 2>/dev/null); then
    die 'wheelie-native failed its staged capability-list smoke'
  fi
  old_ifs=$IFS
  IFS=,
  for capability_id in $required_capability_ids; do
    [ -z "$capability_id" ] && continue
    case "$capability_id" in *[!A-Za-z0-9._:-]*) die "signed channel metadata contains invalid required capability id: $capability_id" ;; esac
    printf '%s\n' "$capability_metadata" | grep "\"$capability_id\"" >/dev/null 2>&1 || die "staged native helper omitted required capability: $capability_id"
  done
  IFS=$old_ifs
}

preactivate_pi_runtime_compatibility() {
  staged_dir=$1
  pi_runtime_root=${WHEELIE_PI_RUNTIME_ROOT:-"$HOME/.wheelie/runtime/pi"}
  pi_runtime_compatibility_state=no_active_bundle
  activation_path="$pi_runtime_root/activation.json"
  if [ ! -e "$activation_path" ]; then
    return 0
  fi
  [ -f "$activation_path" ] && [ ! -L "$activation_path" ] ||
    die 'active Pi runtime activation metadata is not a regular file; refusing CLI activation before any package mutation'
  compatibility_receipt="$tmp_dir/pi-runtime-compatibility.json"
  if ! "$staged_dir/wheelie-native" pi-runtime status --runtime-root "$pi_runtime_root" --json >"$compatibility_receipt" 2>/dev/null; then
    die 'candidate CLI rejected the active Pi runtime bundle or its profile registry; activate a compatible signed runtime bundle before retrying the CLI update'
  fi
  grep '"support_state"[[:space:]]*:[[:space:]]*"ready"' "$compatibility_receipt" >/dev/null 2>&1 ||
    die 'candidate CLI did not report the active Pi runtime bundle ready; refusing CLI activation'
  grep '"runtime_profile_registry_ready"[[:space:]]*:[[:space:]]*true' "$compatibility_receipt" >/dev/null 2>&1 ||
    die 'active Pi runtime bundle is missing the candidate CLI runtime-profile registry; refusing CLI activation'
  grep '"runtime_profile_compatibility_verified"[[:space:]]*:[[:space:]]*true' "$compatibility_receipt" >/dev/null 2>&1 ||
    die 'active Pi runtime profile registry is incompatible with the candidate CLI; refusing CLI activation'
  pi_runtime_compatibility_state=compatible_active_bundle_verified
}

preactivate_session_lease_adoption() {
  staged_dir=$1
  adoption_state=not_required_first_install
  active_session_count=0
  if [ ! -x "$install_dir/wheelie-daemon" ]; then
    return 0
  fi

  session_snapshot="$tmp_dir/current-session-lease-snapshot.json"
  if ! "$install_dir/wheelie-daemon" agent-sessions --json >"$session_snapshot" 2>/dev/null; then
    if ! "$staged_dir/wheelie-daemon" agent-sessions --json >"$session_snapshot" 2>/dev/null; then
      die 'cannot prove active session/lease state from either the current or staged daemon; refusing activation before any daemon or package mutation'
    fi
  fi
  active_session_count=$(awk '
    {
      remaining = $0
      while (match(remaining, /"cleanup_state"[[:space:]]*:[[:space:]]*"not_required"/)) {
        count += 1
        remaining = substr(remaining, RSTART + RLENGTH)
      }
    }
    END { print count + 0 }
  ' "$session_snapshot" 2>/dev/null || true)
  case "$active_session_count" in ''|*[!0-9]*) die 'current daemon returned an invalid active-session count during adoption preflight' ;; esac
  if [ "$active_session_count" = 0 ]; then
    adoption_state=no_active_sessions
    return 0
  fi

  adoption_receipt="$tmp_dir/candidate-adoption-preflight.json"
  if ! "$staged_dir/wheelie-daemon" update-adoption-preflight \
    --snapshot "$session_snapshot" \
    --active-session-count "$active_session_count" \
    --json >"$adoption_receipt" 2>/dev/null; then
    die "candidate daemon cannot adopt $active_session_count active sessions/leases/fences; refusing activation while the current daemon remains usable"
  fi
  grep '"support_state"[[:space:]]*:[[:space:]]*"ready"' "$adoption_receipt" >/dev/null 2>&1 ||
    die "candidate daemon did not prove compatible adoption for $active_session_count active sessions/leases/fences"
  grep '"fences_preserved"[[:space:]]*:[[:space:]]*true' "$adoption_receipt" >/dev/null 2>&1 ||
    die 'candidate daemon adoption preflight did not preserve fencing tokens'
  adoption_state=compatible_adoption_verified
}

replace_with_link() {
  link_path=$1
  link_target=$2
  link_tmp="$link_path.next.$$"
  rm -f "$link_tmp"
  ln -s "$link_target" "$link_tmp"
  if mv -f -h "$link_tmp" "$link_path" 2>/dev/null; then
    return 0
  fi
  mv -f -T "$link_tmp" "$link_path"
}

candidate_active() {
  [ "$(readlink "$install_dir/.wheelie-active" 2>/dev/null || true)" = ".wheelie-releases/$release_id" ]
}

rollback_active_package() {
  "$activation_recovery_kernel_path" rollback "$install_dir" "$activation_transaction" install_state_commit_failed >/dev/null 2>&1 || return 1
}

commit_activation() {
  if "$activation_recovery_kernel_path" commit "$install_dir" "$activation_transaction" >"$tmp_dir/activation-receipt.json" 2>"$tmp_dir/activation-commit.stderr"; then
    return 0
  fi
  printf 'wheelie installer: recovery kernel rejected activation commit (transaction_state=%s active_target=%s)\n' \
    "$(metadata_value state "$activation_transaction")" \
    "$(readlink "$install_dir/.wheelie-active" 2>/dev/null || true)" >&2
  awk 'NR <= 40 { print }' "$tmp_dir/activation-commit.stderr" >&2
  return 1
}

same_release_directory() {
  staged_dir=$1
  existing_dir=$2
  for executable_name in wheelie wheelie-daemon wheelie-native wheelie-recovery; do
    [ -f "$existing_dir/$executable_name" ] || return 1
    [ "$(sha256_file "$staged_dir/$executable_name")" = "$(sha256_file "$existing_dir/$executable_name")" ] || return 1
  done
}

activate_staged_package() {
  staged_dir=$1
  releases_dir="$install_dir/.wheelie-releases"
  recovery_kernels_dir="$install_dir/.wheelie-recovery-kernels"
  mkdir -p "$releases_dir" "$recovery_kernels_dir"
  release_id=$(printf '%s-%s' "$version" "$(printf '%s' "$actual_sha256" | cut -c 1-16)")
  release_dir="$releases_dir/$release_id"
  if [ -d "$release_dir" ]; then
    same_release_directory "$staged_dir" "$release_dir" || die 'existing immutable release directory does not match the signed candidate digest'
    rm -rf "$staged_dir"
  else
    mv "$staged_dir" "$release_dir"
  fi

  recovery_kernel_dir="$recovery_kernels_dir/$staged_recovery_sha256"
  mkdir -p "$recovery_kernel_dir"
  if [ ! -x "$recovery_kernel_dir/wheelie-recovery" ]; then
    cp "$release_dir/wheelie-recovery" "$recovery_kernel_dir/wheelie-recovery.next.$$"
    chmod 0755 "$recovery_kernel_dir/wheelie-recovery.next.$$"
    mv -f "$recovery_kernel_dir/wheelie-recovery.next.$$" "$recovery_kernel_dir/wheelie-recovery"
  fi
  candidate_recovery_kernel_path="$recovery_kernel_dir/wheelie-recovery"

  # Preserve the already-active recovery authority at a digest-addressed path.
  # It owns this activation and remains stable across the active-package swap;
  # candidate code cannot become its own sole rollback authority. First install
  # necessarily bootstraps from the separately signed and staged candidate.
  if [ -x "$install_dir/.wheelie-recovery" ]; then
    current_recovery_sha256=$(sha256_file "$install_dir/.wheelie-recovery")
    current_recovery_dir="$recovery_kernels_dir/$current_recovery_sha256"
    mkdir -p "$current_recovery_dir"
    if [ ! -x "$current_recovery_dir/wheelie-recovery" ]; then
      cp "$install_dir/.wheelie-recovery" "$current_recovery_dir/wheelie-recovery.next.$$"
      chmod 0755 "$current_recovery_dir/wheelie-recovery.next.$$"
      mv -f "$current_recovery_dir/wheelie-recovery.next.$$" "$current_recovery_dir/wheelie-recovery"
    fi
    replace_with_link "$install_dir/.wheelie-recovery" ".wheelie-recovery-kernels/$current_recovery_sha256/wheelie-recovery"
  else
    replace_with_link "$install_dir/.wheelie-recovery" ".wheelie-recovery-kernels/$staged_recovery_sha256/wheelie-recovery"
  fi
  activation_recovery_kernel_path="$install_dir/.wheelie-recovery"

  if [ "${WHEELIE_TEST_ACTIVATION_FAILPOINT:-}" = after_release_stage_crash ]; then
    exit 85
  fi

  active_link="$install_dir/.wheelie-active"
  previous_release_id=
  if [ -L "$active_link" ]; then
    previous_release_id=$(readlink "$active_link" 2>/dev/null || true)
  elif [ -x "$install_dir/wheelie" ] && [ ! -L "$install_dir/wheelie" ]; then
    legacy_id="legacy-$WHEELIE_INSTALL_STARTED_AT-$$"
    legacy_dir="$releases_dir/$legacy_id"
    mkdir -p "$legacy_dir"
    for legacy_name in wheelie wheelie-daemon wheelie-native wheelie-recovery; do
      if [ -x "$install_dir/$legacy_name" ]; then
        cp "$install_dir/$legacy_name" "$legacy_dir/$legacy_name"
        chmod 0755 "$legacy_dir/$legacy_name"
      fi
    done
    previous_release_id=".wheelie-releases/$legacy_id"
    replace_with_link "$active_link" "$previous_release_id"
  fi

  # These stable entry links all dereference the one active pointer. Preparing a
  # missing legacy link does not activate the candidate; the sole activation
  # mutation is the .wheelie-active swap owned by the recovery kernel.
  for executable_name in wheelie-native wheelie-daemon wheelie-recovery wheelie; do
    replace_with_link "$install_dir/$executable_name" ".wheelie-active/$executable_name"
  done

  activation_transaction="$install_dir/.wheelie-activation-transaction"
  activation_state_backup_dir="$install_dir/.wheelie-activation-state-backup"
  if ! "$activation_recovery_kernel_path" activate \
    "$install_dir" \
    ".wheelie-releases/$release_id" \
    "$previous_release_id" \
    "$activation_transaction" \
    "sha256:$actual_sha256" \
    "$receipt_dir/install-receipt.json" \
    "$install_dir/.wheelie-receipt-dir" \
    "$activation_state_backup_dir" >"$tmp_dir/activation-receipt.json"; then
    "$activation_recovery_kernel_path" recover "$install_dir" "$activation_transaction" >"$tmp_dir/automatic-rollback-receipt.json" 2>/dev/null ||
      die 'independent recovery kernel could not restore the prior release and install state after activation failure'
    die 'candidate activation failed; independent recovery kernel restored the exact prior release and install state'
  fi
  candidate_active || die 'recovery kernel returned from activation without leaving the candidate release active'
  if [ "${WHEELIE_TEST_INSTALL_STATE_FAILPOINT:-}" = after_activation_return_crash ]; then
    exit 88
  fi
}

rollback_failed_install_state() {
  if ! rollback_active_package; then
    die 'install-state commit failed and the independent recovery kernel could not restore the prior release and provenance state; do not retry until wheelie doctor and the activation transaction are inspected'
  fi
  if [ -n "$previous_release_id" ] && ! WHEELIE_NO_UPDATE_CHECK=1 "$install_dir/wheelie" version --no-update-check >/dev/null 2>&1; then
    die 'install-state commit failed and the previous Wheelie release could not be restored; do not retry until the active release link is repaired'
  fi
  die 'install-state commit failed; the independent recovery kernel restored the previous Wheelie release and provenance receipt'
}

write_verified_metadata_cache() {
  [ "$signature_mode" = cosign ] || return 0
  metadata_cache_key=$(printf '%s' "$(normalize_sha256 "$channel_metadata_digest")" | cut -c 1-16)
  metadata_cache_rel="supply-chain/$channel/$release_id-$metadata_cache_key"
  metadata_cache_dir="$receipt_dir/$metadata_cache_rel"
  mkdir -p "$metadata_cache_dir" || return 1
  for cache_entry in \
    "channel.env:$channel_metadata_path" \
    "channel.env.sig:$channel_metadata_sig_path" \
    "channel.env.crt:$channel_metadata_cert_path" \
    "release-manifest.json:$release_manifest_path" \
    "release-manifest.json.sig:$release_manifest_sig_path" \
    "release-manifest.json.crt:$release_manifest_cert_path"; do
    cache_name=${cache_entry%%:*}
    cache_source=${cache_entry#*:}
    cache_destination="$metadata_cache_dir/$cache_name"
    if [ -e "$cache_destination" ]; then
      [ -f "$cache_destination" ] && [ ! -L "$cache_destination" ] || return 1
      [ "$(sha256_file "$cache_destination")" = "$(sha256_file "$cache_source")" ] || return 1
      continue
    fi
    cp "$cache_source" "$cache_destination.tmp.$$" || return 1
    chmod 0600 "$cache_destination.tmp.$$" || return 1
    mv "$cache_destination.tmp.$$" "$cache_destination" || return 1
  done
}

write_install_receipt() {
  receipt_dir=$1
  receipt_path=$2
  mkdir -p "$receipt_dir" || return 1
  if cosign_available; then cosign_available_json=true; else cosign_available_json=false; fi
  if strict_verify_enabled; then strict_verify_json=true; else strict_verify_json=false; fi
  cat >"$receipt_path.tmp" <<EOF
{
  "schema_version": "wheelie_install_receipt/v2",
  "version": "$version",
  "channel": "$channel",
  "artifact_channel": "$artifact_channel",
  "os": "$os",
  "arch": "$arch",
  "artifact_url": "$artifact_url",
  "artifact_digest": "sha256:$actual_sha256",
  "binary_digest_sha256": "sha256:$staged_shell_sha256",
  "release_notes_url": "$release_notes_url",
  "source_commit": "$source_commit",
  "manifest_digest": "$release_manifest_digest",
  "release_manifest_ref": "$release_manifest_cache_ref",
  "release_manifest_signature_ref": "$release_manifest_signature_url",
  "channel_metadata_ref": "$channel_metadata_cache_ref",
  "channel_metadata_digest": "$channel_metadata_digest",
  "channel_metadata_signature_ref": "$channel_metadata_signature_url",
  "update_manifest_ref": "$channel_metadata_cache_ref",
  "signature_ref": "$signature_url",
  "provenance_ref": "$provenance_url",
  "sbom_ref": "$sbom_url",
  "manifest_serial": $manifest_serial,
  "rollback_epoch": $rollback_epoch,
  "metadata_expires_at": "$metadata_expires_at",
  "signer_identity_regexp": "$(json_escape "$signer_identity_regexp")",
  "signer_oidc_issuer": "$(json_escape "$signer_oidc_issuer")",
  "checksum_verified": true,
  "artifact_signature_verification_mode": "$artifact_signature_verification_mode",
  "signature_verification_mode": "$signature_verification_mode",
  "strict_mode_enabled": $strict_verify_json,
  "strict_verify": $strict_verify_json,
  "cosign_available": $cosign_available_json,
  "channel_metadata_signature_verified": $channel_metadata_signature_verified,
  "release_manifest_signature_verified": $release_manifest_signature_verified,
  "artifact_signature_verified": $artifact_signature_verified,
  "provenance_signature_verified": $provenance_signature_verified,
  "sbom_signature_verified": $sbom_signature_verified,
  "package_integrity_status": "verified",
  "package_layout": "$package_layout",
  "shell_executable_sha256": "sha256:$staged_shell_sha256",
  "daemon_executable_sha256": "sha256:$staged_daemon_sha256",
  "native_helper_name": "$native_helper_name",
  "native_helper_sha256": "sha256:$staged_native_sha256",
  "recovery_kernel_sha256": "sha256:$staged_recovery_sha256",
  "required_capability_ids": "$(json_escape "$required_capability_ids")",
  "pi_runtime_compatibility_state": "$pi_runtime_compatibility_state",
  "session_lease_adoption_state": "$adoption_state",
  "active_session_count_preserved": $active_session_count,
  "daemon_restarted": false,
  "activation_mode": "atomic_pointer_swap",
  "recovery_kernel": "$candidate_recovery_kernel_path",
  "activation_recovery_kernel": "$activation_recovery_kernel_path",
  "automatic_rollback_receipt": "$activation_transaction.receipt.json",
  "activation_release_id": "$release_id",
  "previous_release_id": "$previous_release_id",
  "install_dir": "$install_dir",
  "path_entry": "$install_dir"
}
EOF
  [ "${WHEELIE_TEST_INSTALL_STATE_FAILPOINT:-}" != receipt ] || return 1
  mv "$receipt_path.tmp" "$receipt_path" || return 1
}

write_receipt_locator() {
  locator_path="$install_dir/.wheelie-receipt-dir"
  printf '%s\n' "$receipt_dir" >"$locator_path.tmp" || return 1
  [ "${WHEELIE_TEST_INSTALL_STATE_FAILPOINT:-}" != locator ] || return 1
  mv "$locator_path.tmp" "$locator_path" || return 1
}

commit_install_state() {
  write_verified_metadata_cache || return 1
  if [ "$signature_mode" = cosign ]; then
    release_manifest_cache_ref="$metadata_cache_rel/release-manifest.json"
    channel_metadata_cache_ref="$metadata_cache_rel/channel.env"
  fi
  write_install_receipt "$receipt_dir" "$receipt_dir/install-receipt.json" || return 1
  candidate_active || { printf '%s\n' 'wheelie installer: candidate pointer changed while writing install receipt' >&2; return 1; }
  write_receipt_locator || return 1
  candidate_active || { printf '%s\n' 'wheelie installer: candidate pointer changed while writing receipt locator' >&2; return 1; }
  [ "${WHEELIE_TEST_INSTALL_STATE_FAILPOINT:-}" != health ] || return 1
  health_output="$tmp_dir/postactivation-doctor.json"
  if [ "$signature_mode" = cosign ]; then
    if ! WHEELIE_RECEIPT_DIR="$receipt_dir" WHEELIE_NO_UPDATE_CHECK=1 \
      "$install_dir/wheelie-native" doctor supply-chain --binary-path "$install_dir/wheelie" --json --no-update-check >"$health_output" 2>"$health_output.stderr"; then
      printf '%s\n' 'wheelie installer: post-activation supply-chain doctor rejected the candidate package:' >&2
      awk 'NR <= 80 { print }' "$health_output" >&2
      awk 'NR <= 40 { print }' "$health_output.stderr" >&2
      return 1
    fi
    grep '"support_state"[[:space:]]*:[[:space:]]*"ready"' "$health_output" >/dev/null 2>&1 || {
      printf '%s\n' 'wheelie installer: post-activation supply-chain doctor did not report ready:' >&2
      awk 'NR <= 80 { print }' "$health_output" >&2
      return 1
    }
  elif ! WHEELIE_RECEIPT_DIR="$receipt_dir" WHEELIE_NO_UPDATE_CHECK=1 \
    "$install_dir/wheelie" doctor --json --no-update-check >"$health_output" 2>"$health_output.stderr"; then
    printf '%s\n' 'wheelie installer: post-activation doctor rejected the candidate package:' >&2
    awk 'NR <= 80 { print }' "$health_output" >&2
    awk 'NR <= 40 { print }' "$health_output.stderr" >&2
    return 1
  fi
  candidate_active || { printf '%s\n' 'wheelie installer: candidate pointer changed during post-activation doctor' >&2; return 1; }
}

path_contains_install_dir() {
  case ":${PATH:-}:" in
    *:"$install_dir":*) return 0 ;;
    *) return 1 ;;
  esac
}

need_command curl
need_command tar
need_command awk
need_command grep
require_cosign_for_strict

channel=${WHEELIE_CHANNEL:-stable}
artifact_channel=${WHEELIE_ARTIFACT_CHANNEL:-$channel}
version=${WHEELIE_VERSION:-latest}
requested_version=${WHEELIE_REQUESTED_VERSION:-}
if [ -z "$requested_version" ] && [ -n "$version" ] && [ "$version" != latest ]; then
  requested_version=$version
fi
os=$(normalize_os)
arch=$(normalize_arch)
install_dir=$(canonical_dir "${WHEELIE_INSTALL_DIR:-"$HOME/.wheelie/bin"}")
receipt_dir=$(canonical_dir "${WHEELIE_RECEIPT_DIR:-"$HOME/.wheelie"}")
release_base_url=$(trim_trailing_slash "${WHEELIE_RELEASE_BASE_URL:-https://get.wheelie.dev/releases}")
metadata_sha256=${WHEELIE_ARTIFACT_SHA256:-}
artifact_base_url=${WHEELIE_ARTIFACT_BASE_URL:-}
release_notes_url=${WHEELIE_RELEASE_NOTES_URL:-}
source_commit=${WHEELIE_SOURCE_COMMIT:-unknown}
package_layout=wheelie_atomic_runtime_v1
native_helper_name=wheelie-native
expected_shell_sha256=
expected_daemon_sha256=
expected_native_sha256=
expected_recovery_sha256=
required_capability_ids=
pi_runtime_compatibility_state=not_checked
WHEELIE_INSTALL_STARTED_AT=$(date +%s 2>/dev/null || printf 0)
signature_mode=${WHEELIE_SIGNATURE_MODE:-none}
signature_verification_mode=not_attempted
artifact_signature_verification_mode=not_attempted
channel_metadata_signature_verified=false
release_manifest_signature_verified=false
artifact_signature_verified=false
provenance_signature_verified=false
sbom_signature_verified=false
release_manifest_digest=
release_manifest_cache_ref=
release_manifest_signature_url=
channel_metadata_digest=
channel_metadata_cache_ref=
channel_metadata_signature_url=
manifest_serial=0
rollback_epoch=0
metadata_expires_at=
signer_identity_regexp=
signer_oidc_issuer=
resolved_from_channel=0

case "$channel" in
  stable|canary) ;;
  *) die "unsupported channel: $channel" ;;
esac
case "$artifact_channel" in
  stable|canary) ;;
  *) die "unsupported artifact channel: $artifact_channel" ;;
esac

resolve_channel=1
if [ -n "$requested_version" ] && [ "${WHEELIE_SIGNATURE_MODE:-}" = none ] && [ -n "$metadata_sha256" ] && [ -n "$artifact_base_url" ]; then
  resolve_channel=0
fi
if [ "$resolve_channel" = 1 ]; then
    channel_metadata_url="${release_base_url}/channels/${channel}.env"
    channel_metadata_path=$(mktemp "${TMPDIR:-/tmp}/wheelie-channel.XXXXXX")
    channel_metadata_sig_path="$channel_metadata_path.sig"
    channel_metadata_cert_path="$channel_metadata_path.crt"
    trap 'rm -f "$channel_metadata_path" "$channel_metadata_sig_path" "$channel_metadata_cert_path" "${release_manifest_path:-}" "${release_manifest_sig_path:-}" "${release_manifest_cert_path:-}"' EXIT HUP INT TERM
    download "$channel_metadata_url" "$channel_metadata_path"
    metadata_signature_mode=$(metadata_value WHEELIE_SIGNATURE_MODE "$channel_metadata_path")
    [ -n "$metadata_signature_mode" ] || metadata_signature_mode=cosign
    case "$metadata_signature_mode" in
      none)
        if strict_verify_enabled; then
          die 'WHEELIE_STRICT_VERIFY=1 rejects this unsigned Wheelie CLI release; normal Wheelie CLI installation does not require signing'
        fi
        signature_verification_mode=not_provided_release_unsigned
        ;;
      cosign)
        download "${channel_metadata_url}.sig" "$channel_metadata_sig_path"
        download "${channel_metadata_url}.crt" "$channel_metadata_cert_path"
        verify_signature "$channel_metadata_path" "$channel_metadata_sig_path" "$channel_metadata_cert_path" \
          '.*(cloud-build|wheelie-release-signer)@thoughter\.iam\.gserviceaccount\.com' 'https://accounts.google.com'
        if [ "$signature_verification_mode" = "cosign_verified" ]; then
          channel_metadata_signature_verified=true
        fi
        ;;
      *) die "unsupported Wheelie channel signature mode: $metadata_signature_mode" ;;
    esac
    signature_mode=$metadata_signature_mode
    metadata_channel=$(metadata_value WHEELIE_CHANNEL "$channel_metadata_path")
    if [ "$metadata_channel" != "$channel" ]; then
      die "channel metadata mismatch: expected $channel got ${metadata_channel:-<empty>}"
    fi
    metadata_artifact_channel=$(metadata_value WHEELIE_ARTIFACT_CHANNEL "$channel_metadata_path")
    [ -n "$metadata_artifact_channel" ] || metadata_artifact_channel=$metadata_channel
    case "$metadata_artifact_channel" in
      stable|canary) artifact_channel=$metadata_artifact_channel ;;
      *) die "unsupported artifact channel: ${metadata_artifact_channel:-<empty>}" ;;
    esac
    version=$(metadata_value WHEELIE_VERSION "$channel_metadata_path")
    metadata_sha256=$(metadata_value "$(platform_metadata_key)" "$channel_metadata_path")
    if [ -z "$metadata_sha256" ]; then
      metadata_sha256=$(metadata_value WHEELIE_ARTIFACT_SHA256 "$channel_metadata_path")
    fi
    artifact_base_url=$(metadata_value WHEELIE_ARTIFACT_BASE_URL "$channel_metadata_path")
    metadata_release_notes_url=$(metadata_value WHEELIE_RELEASE_NOTES_URL "$channel_metadata_path")
    metadata_source_commit=$(metadata_value WHEELIE_SOURCE_COMMIT "$channel_metadata_path")
    manifest_serial=$(metadata_value WHEELIE_MANIFEST_SERIAL "$channel_metadata_path")
    rollback_epoch=$(metadata_value WHEELIE_ROLLBACK_EPOCH "$channel_metadata_path")
    metadata_expires_at=$(metadata_value WHEELIE_METADATA_EXPIRES_AT "$channel_metadata_path")
    signer_identity_regexp=$(metadata_value WHEELIE_SIGNER_IDENTITY_REGEXP "$channel_metadata_path")
    signer_oidc_issuer=$(metadata_value WHEELIE_SIGNER_OIDC_ISSUER "$channel_metadata_path")
    release_manifest_url=$(metadata_value WHEELIE_SIGNED_RELEASE_MANIFEST_URL "$channel_metadata_path")
    [ -n "$release_manifest_url" ] || release_manifest_url=$(metadata_value WHEELIE_RELEASE_MANIFEST_URL "$channel_metadata_path")
    metadata_package_layout=$(metadata_value WHEELIE_PACKAGE_LAYOUT "$channel_metadata_path")
    metadata_bundled_executables=$(metadata_value WHEELIE_BUNDLED_EXECUTABLES "$channel_metadata_path")
    metadata_native_helper_name=$(metadata_value WHEELIE_NATIVE_TRUST_ROOT_EXECUTABLE "$channel_metadata_path")
    expected_shell_sha256=$(metadata_value "$(platform_component_metadata_key WHEELIE_SHELL_SHA256)" "$channel_metadata_path")
    expected_daemon_sha256=$(metadata_value "$(platform_component_metadata_key WHEELIE_DAEMON_SHA256)" "$channel_metadata_path")
    expected_native_sha256=$(metadata_value "$(platform_component_metadata_key WHEELIE_NATIVE_TRUST_ROOT_SHA256)" "$channel_metadata_path")
    expected_recovery_sha256=$(metadata_value "$(platform_component_metadata_key WHEELIE_RECOVERY_KERNEL_SHA256)" "$channel_metadata_path")
    required_capability_ids=$(metadata_value WHEELIE_REQUIRED_CAPABILITY_IDS "$channel_metadata_path")
    [ -n "$metadata_release_notes_url" ] && release_notes_url=$metadata_release_notes_url
    [ -n "$metadata_source_commit" ] && source_commit=$metadata_source_commit
    [ -n "$metadata_package_layout" ] && package_layout=$metadata_package_layout
    [ -n "$metadata_native_helper_name" ] && native_helper_name=$metadata_native_helper_name
    case ",$metadata_bundled_executables," in
      *,wheelie,*wheelie-daemon,*wheelie-native,*wheelie-recovery,*) ;;
      *) die 'channel metadata does not declare the required CLI/daemon/native-helper/recovery package' ;;
    esac
    [ -n "$expected_shell_sha256" ] || die 'channel metadata omitted the shell component digest for this platform'
    [ -n "$expected_daemon_sha256" ] || die 'channel metadata omitted the daemon component digest for this platform'
    [ -n "$expected_native_sha256" ] || die 'channel metadata omitted the native-helper component digest for this platform'
    [ -n "$expected_recovery_sha256" ] || die 'channel metadata omitted the recovery-kernel component digest for this platform'
    expected_shell_sha256=$(normalize_sha256 "$expected_shell_sha256")
    expected_daemon_sha256=$(normalize_sha256 "$expected_daemon_sha256")
    expected_native_sha256=$(normalize_sha256 "$expected_native_sha256")
    expected_recovery_sha256=$(normalize_sha256 "$expected_recovery_sha256")

    if [ "$signature_mode" = cosign ]; then
      require_unsigned_integer 'signed channel manifest serial' "$manifest_serial"
      require_unsigned_integer 'signed channel rollback epoch' "$rollback_epoch"
      require_fresh_metadata "$metadata_expires_at"
      [ -n "$signer_identity_regexp" ] || die 'cosign-declared channel omitted WHEELIE_SIGNER_IDENTITY_REGEXP'
      [ -n "$signer_oidc_issuer" ] || die 'cosign-declared channel omitted WHEELIE_SIGNER_OIDC_ISSUER'
      [ -n "$release_manifest_url" ] || die 'cosign-declared channel omitted signed release manifest URL'
      channel_metadata_digest="sha256:$(sha256_file "$channel_metadata_path")"
      channel_metadata_signature_url="${channel_metadata_url}.sig"

      release_manifest_path=$(mktemp "${TMPDIR:-/tmp}/wheelie-release-manifest.XXXXXX")
      release_manifest_sig_path="$release_manifest_path.sig"
      release_manifest_cert_path="$release_manifest_path.crt"
      download "$release_manifest_url" "$release_manifest_path"
      download "${release_manifest_url}.sig" "$release_manifest_sig_path"
      download "${release_manifest_url}.crt" "$release_manifest_cert_path"
      verify_signature "$release_manifest_path" "$release_manifest_sig_path" "$release_manifest_cert_path" "$signer_identity_regexp" "$signer_oidc_issuer"
      release_manifest_signature_verified=true
      release_manifest_digest="sha256:$(sha256_file "$release_manifest_path")"
      release_manifest_signature_url="${release_manifest_url}.sig"

      manifest_schema=$(json_file_string_value schema_version "$release_manifest_path")
      manifest_version=$(json_file_string_value version "$release_manifest_path")
      manifest_channel=$(json_file_string_value channel "$release_manifest_path")
      manifest_source_commit=$(json_file_string_value source_commit "$release_manifest_path")
      manifest_signing_mode=$(json_file_string_value signing_mode "$release_manifest_path")
      manifest_policy_serial=$(json_file_number_value manifest_serial "$release_manifest_path")
      manifest_rollback_epoch=$(json_file_number_value rollback_epoch "$release_manifest_path")
      manifest_expires_at=$(json_file_string_value expires_at "$release_manifest_path")
      manifest_signer_identity=$(json_file_string_value signer_identity_regexp "$release_manifest_path" | sed 's/\\\\/\\/g')
      manifest_signer_issuer=$(json_file_string_value signer_oidc_issuer "$release_manifest_path")
      manifest_platform_key="${os}_${arch}"
      manifest_platform_digest=$(json_file_string_value "$manifest_platform_key" "$release_manifest_path")
      manifest_artifact_object="releases/artifacts/$version/wheelie-$version-$os-$arch.tar.gz"
      manifest_artifact_digest=$(manifest_file_digest "$manifest_artifact_object" "$release_manifest_path")
      manifest_checksum_digest=$(manifest_file_digest "$manifest_artifact_object.sha256" "$release_manifest_path")
      manifest_sbom_digest=$(manifest_file_digest "$manifest_artifact_object.sbom.spdx.json" "$release_manifest_path")
      manifest_provenance_digest=$(manifest_file_digest "$manifest_artifact_object.intoto.jsonl" "$release_manifest_path")
      manifest_artifact_signature_digest=$(manifest_file_digest "$manifest_artifact_object.sig" "$release_manifest_path")
      manifest_artifact_certificate_digest=$(manifest_file_digest "$manifest_artifact_object.crt" "$release_manifest_path")
      manifest_checksum_signature_digest=$(manifest_file_digest "$manifest_artifact_object.sha256.sig" "$release_manifest_path")
      manifest_checksum_certificate_digest=$(manifest_file_digest "$manifest_artifact_object.sha256.crt" "$release_manifest_path")
      manifest_sbom_signature_digest=$(manifest_file_digest "$manifest_artifact_object.sbom.spdx.json.sig" "$release_manifest_path")
      manifest_sbom_certificate_digest=$(manifest_file_digest "$manifest_artifact_object.sbom.spdx.json.crt" "$release_manifest_path")
      manifest_provenance_signature_digest=$(manifest_file_digest "$manifest_artifact_object.intoto.jsonl.sig" "$release_manifest_path")
      manifest_provenance_certificate_digest=$(manifest_file_digest "$manifest_artifact_object.intoto.jsonl.crt" "$release_manifest_path")
      [ "$manifest_schema" = wheelie_public_release_control_manifest/v1 ] || die 'signed release manifest schema is unsupported'
      [ "$manifest_version" = "$version" ] || die 'signed release manifest version does not match channel metadata'
      [ "$manifest_channel" = "$artifact_channel" ] || die 'signed release manifest artifact channel does not match channel metadata'
      [ "$manifest_source_commit" = "$source_commit" ] || die 'signed release manifest source commit does not match channel metadata'
      [ "$manifest_signing_mode" = cosign ] || die 'cosign-declared channel selected a non-cosign release manifest'
      [ "$manifest_policy_serial" = "$manifest_serial" ] || die 'signed release manifest serial does not match channel metadata'
      [ "$manifest_rollback_epoch" = "$rollback_epoch" ] || die 'signed release manifest rollback epoch does not match channel metadata'
      [ "$manifest_expires_at" = "$metadata_expires_at" ] || die 'signed release manifest expiry does not match channel metadata'
      [ "$manifest_signer_identity" = "$signer_identity_regexp" ] || die 'signed release manifest signer identity policy does not match channel metadata'
      [ "$manifest_signer_issuer" = "$signer_oidc_issuer" ] || die 'signed release manifest signer issuer policy does not match channel metadata'
      [ "$(normalize_sha256 "$manifest_platform_digest")" = "$(normalize_sha256 "$metadata_sha256")" ] || die 'signed release manifest platform digest does not match channel metadata'
      [ "$(normalize_sha256 "$manifest_artifact_digest")" = "$(normalize_sha256 "$metadata_sha256")" ] || die 'signed release manifest selected artifact row does not match channel metadata'
      [ -n "$manifest_checksum_digest" ] || die 'signed release manifest omitted selected checksum sidecar'
      [ -n "$manifest_sbom_digest" ] || die 'signed release manifest omitted selected SBOM sidecar'
      [ -n "$manifest_provenance_digest" ] || die 'signed release manifest omitted selected provenance sidecar'
      [ -n "$manifest_artifact_signature_digest" ] || die 'signed release manifest omitted selected artifact signature'
      [ -n "$manifest_artifact_certificate_digest" ] || die 'signed release manifest omitted selected artifact certificate'
      [ -n "$manifest_checksum_signature_digest" ] || die 'signed release manifest omitted selected checksum signature'
      [ -n "$manifest_checksum_certificate_digest" ] || die 'signed release manifest omitted selected checksum certificate'
      [ -n "$manifest_sbom_signature_digest" ] || die 'signed release manifest omitted selected SBOM signature'
      [ -n "$manifest_sbom_certificate_digest" ] || die 'signed release manifest omitted selected SBOM certificate'
      [ -n "$manifest_provenance_signature_digest" ] || die 'signed release manifest omitted selected provenance signature'
      [ -n "$manifest_provenance_certificate_digest" ] || die 'signed release manifest omitted selected provenance certificate'

      current_receipt="$receipt_dir/install-receipt.json"
      if [ -f "$current_receipt" ] && [ ! -L "$current_receipt" ]; then
        current_channel=$(json_file_string_value channel "$current_receipt")
        if [ "$current_channel" = "$channel" ]; then
          current_version=$(json_file_string_value version "$current_receipt")
          current_manifest_serial=$(json_file_number_value manifest_serial "$current_receipt")
          current_rollback_epoch=$(json_file_number_value rollback_epoch "$current_receipt")
          [ -z "$current_manifest_serial" ] || require_unsigned_integer 'installed manifest serial' "$current_manifest_serial"
          [ -z "$current_rollback_epoch" ] || require_unsigned_integer 'installed rollback epoch' "$current_rollback_epoch"
          [ -z "$current_rollback_epoch" ] || [ "$rollback_epoch" -ge "$current_rollback_epoch" ] || die 'signed channel rollback epoch regressed'
          if [ -n "$current_rollback_epoch" ] && [ "$rollback_epoch" -eq "$current_rollback_epoch" ]; then
            [ -z "$current_manifest_serial" ] || [ "$manifest_serial" -ge "$current_manifest_serial" ] || die 'signed channel manifest serial regressed'
            if [ -n "$current_version" ] && version_less_than "$version" "$current_version"; then
              die 'signed channel downgrade requires a higher rollback epoch'
            fi
          fi
        fi
      fi
    fi
    resolved_from_channel=1
fi

[ -n "$manifest_serial" ] || manifest_serial=0
[ -n "$rollback_epoch" ] || rollback_epoch=0
[ -z "$requested_version" ] || [ "$version" = "$requested_version" ] ||
  die "signed channel does not select requested version $requested_version"
[ -n "$version" ] || die 'release metadata did not provide WHEELIE_VERSION'
[ -n "$release_notes_url" ] || release_notes_url="https://wheelie.dev/releases/$version"
if [ "$resolved_from_channel" = "1" ] && [ -z "$metadata_sha256" ]; then
  die "release metadata did not provide $(platform_metadata_key) or WHEELIE_ARTIFACT_SHA256"
fi
if [ -z "$artifact_base_url" ]; then
  artifact_base_url="${release_base_url}/artifacts/${version}"
fi
artifact_base_url=$(trim_trailing_slash "$artifact_base_url")

artifact_name="wheelie-${version}-${os}-${arch}.tar.gz"
artifact_url="${artifact_base_url}/${artifact_name}"
checksum_url="${artifact_url}.sha256"
signature_url="${artifact_url}.sig"
certificate_url="${artifact_url}.crt"
sbom_url="${artifact_url}.sbom.spdx.json"
provenance_url="${artifact_url}.intoto.jsonl"

# Replace the channel-metadata cleanup trap with a full temp-dir cleanup once the
# artifact temp directory exists.
tmp_dir=$(mktemp -d "${TMPDIR:-/tmp}/wheelie-install.XXXXXX")
install_lock_acquired=0
cleanup_install_transaction() {
  rm -rf "$tmp_dir"
  if [ "$install_lock_acquired" = 1 ]; then
    rm -rf "$install_lock_dir"
  fi
  rm -f "${channel_metadata_path:-}" "${channel_metadata_sig_path:-}" "${channel_metadata_cert_path:-}" \
    "${release_manifest_path:-}" "${release_manifest_sig_path:-}" "${release_manifest_cert_path:-}"
}
trap cleanup_install_transaction EXIT HUP INT TERM

artifact_path="$tmp_dir/$artifact_name"
checksum_path="$artifact_path.sha256"
checksum_signature_path="$checksum_path.sig"
checksum_certificate_path="$checksum_path.crt"
signature_path="$artifact_path.sig"
certificate_path="$artifact_path.crt"
sbom_path="$artifact_path.sbom.spdx.json"
sbom_signature_path="$sbom_path.sig"
sbom_certificate_path="$sbom_path.crt"
provenance_path="$artifact_path.intoto.jsonl"
provenance_signature_path="$provenance_path.sig"
provenance_certificate_path="$provenance_path.crt"
extract_dir="$tmp_dir/extract"

mkdir -p "$extract_dir" "$install_dir"
install_lock_dir="$install_dir/.wheelie-install.lock"
if ! mkdir "$install_lock_dir" 2>/dev/null; then
  die 'another install/update transaction is already in progress; current release was not changed'
fi
install_lock_acquired=1
activation_transaction="$install_dir/.wheelie-activation-transaction"
if [ -x "$install_dir/.wheelie-recovery" ]; then
  "$install_dir/.wheelie-recovery" recover "$install_dir" "$activation_transaction" >"$tmp_dir/startup-recovery-receipt.json" ||
    die 'independent recovery kernel could not reconcile an incomplete prior activation'
fi

download "$artifact_url" "$artifact_path"
download "$checksum_url" "$checksum_path"
download "$sbom_url" "$sbom_path"
download "$provenance_url" "$provenance_path"

if [ "$signature_mode" = cosign ]; then
  [ "$(sha256_file "$checksum_path")" = "$(normalize_sha256 "$manifest_checksum_digest")" ] || die 'signed release manifest checksum sidecar digest mismatch'
  [ "$(sha256_file "$sbom_path")" = "$(normalize_sha256 "$manifest_sbom_digest")" ] || die 'signed release manifest SBOM sidecar digest mismatch'
  [ "$(sha256_file "$provenance_path")" = "$(normalize_sha256 "$manifest_provenance_digest")" ] || die 'signed release manifest provenance sidecar digest mismatch'
fi

case "$signature_mode" in
  none)
    if strict_verify_enabled; then
      die 'WHEELIE_STRICT_VERIFY=1 rejects this unsigned Wheelie CLI release; normal Wheelie CLI installation does not require signing'
    fi
    signature_verification_mode=not_provided_release_unsigned
    artifact_signature_verification_mode=$signature_verification_mode
    ;;
  cosign)
    download "${checksum_url}.sig" "$checksum_signature_path"
    download "${checksum_url}.crt" "$checksum_certificate_path"
    download "$signature_url" "$signature_path"
    download "$certificate_url" "$certificate_path"
    download "${sbom_url}.sig" "$sbom_signature_path"
    download "${sbom_url}.crt" "$sbom_certificate_path"
    download "${provenance_url}.sig" "$provenance_signature_path"
    download "${provenance_url}.crt" "$provenance_certificate_path"
    verify_manifest_file_digest "$signature_path" "$manifest_artifact_signature_digest" 'selected artifact signature'
    verify_manifest_file_digest "$certificate_path" "$manifest_artifact_certificate_digest" 'selected artifact certificate'
    verify_manifest_file_digest "$checksum_signature_path" "$manifest_checksum_signature_digest" 'selected checksum signature'
    verify_manifest_file_digest "$checksum_certificate_path" "$manifest_checksum_certificate_digest" 'selected checksum certificate'
    verify_manifest_file_digest "$sbom_signature_path" "$manifest_sbom_signature_digest" 'selected SBOM signature'
    verify_manifest_file_digest "$sbom_certificate_path" "$manifest_sbom_certificate_digest" 'selected SBOM certificate'
    verify_manifest_file_digest "$provenance_signature_path" "$manifest_provenance_signature_digest" 'selected provenance signature'
    verify_manifest_file_digest "$provenance_certificate_path" "$manifest_provenance_certificate_digest" 'selected provenance certificate'
    verify_signature "$checksum_path" "$checksum_signature_path" "$checksum_certificate_path" "$signer_identity_regexp" "$signer_oidc_issuer"
    verify_signature "$sbom_path" "$sbom_signature_path" "$sbom_certificate_path" "$signer_identity_regexp" "$signer_oidc_issuer"
    sbom_signature_verified=true
    verify_signature "$provenance_path" "$provenance_signature_path" "$provenance_certificate_path" "$signer_identity_regexp" "$signer_oidc_issuer"
    provenance_signature_verified=true
    verify_signature "$artifact_path" "$signature_path" "$certificate_path" "$signer_identity_regexp" "$signer_oidc_issuer"
    artifact_signature_verified=true
    artifact_signature_verification_mode=$signature_verification_mode
    ;;
  *) die "unsupported Wheelie artifact signature mode: $signature_mode" ;;
esac

sidecar_sha256=$(normalize_sha256 "$(awk '{print $1}' "$checksum_path")")
if [ -n "$metadata_sha256" ]; then
  metadata_sha256=$(normalize_sha256 "$metadata_sha256")
  if [ "$metadata_sha256" != "$sidecar_sha256" ]; then
    die "channel metadata digest does not match checksum sidecar for $artifact_name"
  fi
fi

verify_checksum "$artifact_path" "$checksum_path"
actual_sha256=$(sha256_file "$artifact_path")
assert_metadata_mentions_digest "$sbom_path" "SBOM" "$actual_sha256"
assert_metadata_mentions_digest "$provenance_path" "provenance" "$actual_sha256"

tar -xzf "$artifact_path" -C "$extract_dir"
validate_staged_package "$extract_dir"
preactivate_pi_runtime_compatibility "$extract_dir"
preactivate_session_lease_adoption "$extract_dir"
activate_staged_package "$extract_dir"
if ! commit_install_state; then
  rollback_failed_install_state
fi
if [ "${WHEELIE_TEST_INSTALL_STATE_FAILPOINT:-}" = after_install_state_crash ]; then
  exit 89
fi
if ! commit_activation; then
  rollback_failed_install_state
fi
replace_with_link "$install_dir/.wheelie-recovery" ".wheelie-recovery-kernels/$staged_recovery_sha256/wheelie-recovery"
telemetry_emit_install 0

printf '%s\n' "wheelie installed to $install_dir/wheelie"
printf '%s\n' "artifact digest: sha256:$actual_sha256"
if [ "$signature_verification_mode" = "cosign_verified" ]; then
  printf '%s\n' 'signature verification: cosign verified'
elif [ "$signature_verification_mode" = "skipped_cosign_not_installed_non_strict" ]; then
  printf '%s\n' 'signature verification: skipped (cosign not installed; set WHEELIE_STRICT_VERIFY=1 for fail-closed verification)'
elif [ "$signature_verification_mode" = "not_provided_release_unsigned" ]; then
  printf '%s\n' 'signature verification: not applicable (Wheelie CLI releases are checksum-bound and do not require signing)'
fi
if ! path_contains_install_dir; then
  printf '%s\n' "Add Wheelie to PATH: export PATH=\"$install_dir:\$PATH\""
fi
printf '%s\n' "Run 'wheelie doctor --json' to verify your environment."
