#!/bin/sh
set -eu

log() {
  printf '%s\n' "$*" >&2
}

styled_output=0
if [ -z "${NO_COLOR+x}" ] && [ -t 2 ]; then
  styled_output=1
fi

receipt_item() {
  if [ "$styled_output" = "1" ]; then
    printf '\033[32m✓\033[0m %s\n' "$*" >&2
  else
    log "$*"
  fi
}

receipt_warning() {
  log "warning: $*"
}

pro_trial_failure_receipt() {
  receipt_warning "ctx pro trial setup failed; Core setup completed."
  log "Retry:"
  log "  ctx pro"
}

format_pro_trial_date() {
  trial_date="$1"
  trial_date_tail="${trial_date#*-}"
  trial_month="${trial_date_tail%%-*}"
  trial_day="${trial_date_tail#*-}"
  case "$trial_day" in 0*) trial_day="${trial_day#0}" ;; esac
  case "$trial_month" in
    01) trial_month_name=January ;;
    02) trial_month_name=February ;;
    03) trial_month_name=March ;;
    04) trial_month_name=April ;;
    05) trial_month_name=May ;;
    06) trial_month_name=June ;;
    07) trial_month_name=July ;;
    08) trial_month_name=August ;;
    09) trial_month_name=September ;;
    10) trial_month_name=October ;;
    11) trial_month_name=November ;;
    12) trial_month_name=December ;;
  esac
  printf '%s %s' "$trial_month_name" "$trial_day"
}

pro_trial_started_receipt() {
  receipt_item "ctx pro trial started"
  log ""
  log 'ctx pro is a US$20/mo paid add-on for “git blame, but for agent sessions.”'
  log 'Not a cloud service. Your agent history stays local.'
  log 'You get a 14-day free trial (no account or card required).'
  log ""
  if [ -n "$pro_trial_ends_on" ] && [ -n "$pro_action_url" ]; then
    pro_trial_display_date="$(format_pro_trial_date "$pro_trial_ends_on")"
    log "To keep it after $pro_trial_display_date, create an account and add a card:"
    log "$pro_action_url"
  elif [ -n "$pro_trial_ends_on" ]; then
    pro_trial_display_date="$(format_pro_trial_date "$pro_trial_ends_on")"
    log "To keep it after $pro_trial_display_date: run ctx pro"
  else
    log "To keep it after the trial: run ctx pro"
  fi
}

pro_browser_handoff_pending_receipt() {
  receipt_item "ctx pro browser setup pending"
  log ""
  log 'ctx pro is a US$20/mo paid add-on for “git blame, but for agent sessions.”'
  log 'Not a cloud service. Your agent history stays local.'
  log 'No free trial is active on this installation.'
  log ""
  log 'Sign in to connect existing access or start a US$20/mo subscription:'
  log "$pro_action_url"
}

usage() {
  cat <<'USAGE'
usage: curl -fsSL https://ctx.rs/install | sh
       curl -fsSL https://ctx.rs/install | sh -s -- --no-setup
       curl -fsSL https://ctx.rs/install | sh -s -- --no-daemon

Installs the ctx CLI from signed release metadata, installs the bundled
agent-history skill, then runs ctx setup to index discovered local agent
history.

Prerequisites:
  curl, OpenSSL, install, ln, mktemp, awk, date, id, sort, stat, uname

Options:
  --semantic           Explicitly enable signed Semantic runtime provisioning
                       and semantic setup for this install.
  --no-setup           Install only; do not install the skill or run ctx setup
                       unless a skill option is also passed.
  --no-daemon          Run installer setup with ctx setup --no-daemon.
  --no-pro-trial       Install Core without starting a ctx pro trial.
  --no-skill           Do not install the bundled ctx agent skill.
  --skill-agent AGENT  Install the skill into a specific agent skill dir.
                       Repeat for multiple agents.
  --all-skill-agents   Install the skill into all supported agent skill dirs.
  --no-modify-path     Do not update shell startup files when the install
                       directory is not on PATH.
  --no-man             Do not install generated man pages.
  --man-dir D          Man page directory. Defaults to $HOME/.local/share/man/man1.
  -h, --help           Show this help.

Environment:
  CTX_INSTALL_SEMANTIC=1              Enable signed Semantic runtime provisioning.
  CTX_SEARCH_SEMANTIC=true|false      Override persisted Semantic search for this install.
  CTX_INSTALL_NO_SETUP=1             Install only; do not install the skill or run ctx setup
                                     unless a skill option is also passed.
  CTX_INSTALL_NO_DAEMON=1            Run installer setup with ctx setup --no-daemon.
  CTX_INSTALL_NO_PRO_TRIAL=1         Install Core without starting a ctx pro trial.
  CTX_INSTALL_NO_SKILL=1             Do not install the bundled ctx agent skill.
  CTX_INSTALL_SKILL_AGENTS=codex,... Install the skill into specific agent dirs.
  CTX_INSTALL_ALL_SKILL_AGENTS=1     Install the skill into all supported agent dirs.
  CTX_INSTALL_NO_MODIFY_PATH=1       Do not update shell startup files.
  CTX_INSTALL_NO_MAN=1               Do not install generated man pages.
  CTX_MAN_DIR=$HOME/.local/share/man/man1
                                     Override man page install directory.
  CTX_SETUP_PROGRESS=auto            Setup progress mode: auto, plain, or none.
  CTX_ANALYTICS_ENABLED=false        Disable installer diagnostics and CLI analytics.
  CTX_DAEMON_ENABLED=false           Disable daemon maintenance. Effective Semantic
                                     installation requires daemon maintenance to remain enabled.
  CTX_INSTALL_ATTEMPT_ID=ia_...      Override installer attempt ID for tests.
  CTX_ALLOW_CUSTOM_RELEASE_BASE_URL=1
                                     Allow non-cli.ctx.rs artifact metadata for development.
  CTX_RELEASE_METADATA_SIGNATURE_URL Override detached metadata signature URL.
USAGE
}

fail() {
  if command -v stop_install_animation >/dev/null 2>&1; then
    stop_install_animation
  fi
  log "error: $*"
  exit 1
}

legacy_control_truthy() {
  legacy_value="$1"
  while :; do
    case "$legacy_value" in
      [[:space:]]*) legacy_value="${legacy_value#?}" ;;
      *) break ;;
    esac
  done
  while :; do
    case "$legacy_value" in
      *[[:space:]]) legacy_value="${legacy_value%?}" ;;
      *) break ;;
    esac
  done
  case "$legacy_value" in
    ""|0|[Ff][Aa][Ll][Ss][Ee]|[Nn][Oo]|[Oo][Ff][Ff]) return 1 ;;
    *) return 0 ;;
  esac
}

canonical_analytics_disabled() {
  analytics_value="${CTX_ANALYTICS_ENABLED-}"
  while :; do
    case "$analytics_value" in
      [[:space:]]*) analytics_value="${analytics_value#?}" ;;
      *) break ;;
    esac
  done
  while :; do
    case "$analytics_value" in
      *[[:space:]]) analytics_value="${analytics_value%?}" ;;
      *) break ;;
    esac
  done
  case "$analytics_value" in
    0|[Ff][Aa][Ll][Ss][Ee]|[Nn][Oo]|[Oo][Ff][Ff]) return 0 ;;
    *) return 1 ;;
  esac
}

valid_install_attempt_id() {
  attempt_id_value="$1"
  case "$attempt_id_value" in
    ia_*) ;;
    *) return 1 ;;
  esac
  case "$attempt_id_value" in
    *[!A-Za-z0-9_-]*) return 1 ;;
  esac
  attempt_id_length="${#attempt_id_value}"
  [ "$attempt_id_length" -ge 11 ] && [ "$attempt_id_length" -le 131 ]
}

deprecated_control_warning=
note_deprecated_control() {
  deprecated_mapping="$1 -> $2"
  if [ -n "$deprecated_control_warning" ]; then
    deprecated_control_warning="$deprecated_control_warning; $deprecated_mapping"
  else
    deprecated_control_warning="$deprecated_mapping"
  fi
}

apply_deprecated_controls() {
  if [ "${CTX_ANALYTICS_OFF+x}" = x ]; then
    note_deprecated_control CTX_ANALYTICS_OFF CTX_ANALYTICS_ENABLED=false
    if legacy_control_truthy "$CTX_ANALYTICS_OFF"; then
      CTX_ANALYTICS_ENABLED=false
      export CTX_ANALYTICS_ENABLED
    fi
  fi
  if [ "${CTX_DISABLE_ANALYTICS+x}" = x ]; then
    note_deprecated_control CTX_DISABLE_ANALYTICS CTX_ANALYTICS_ENABLED=false
    if legacy_control_truthy "$CTX_DISABLE_ANALYTICS"; then
      CTX_ANALYTICS_ENABLED=false
      export CTX_ANALYTICS_ENABLED
    fi
  fi
  if [ "${CTX_INSTALL_DIAGNOSTICS_OFF+x}" = x ]; then
    note_deprecated_control CTX_INSTALL_DIAGNOSTICS_OFF CTX_ANALYTICS_ENABLED=false
    if legacy_control_truthy "$CTX_INSTALL_DIAGNOSTICS_OFF"; then
      CTX_ANALYTICS_ENABLED=false
      export CTX_ANALYTICS_ENABLED
    fi
  fi
  if [ "${CTX_DAEMON_OFF+x}" = x ]; then
    note_deprecated_control CTX_DAEMON_OFF CTX_DAEMON_ENABLED=false
    if legacy_control_truthy "$CTX_DAEMON_OFF"; then
      CTX_DAEMON_ENABLED=false
      export CTX_DAEMON_ENABLED
    fi
  fi
  if [ "${CTX_DISABLE_DAEMON+x}" = x ]; then
    note_deprecated_control CTX_DISABLE_DAEMON CTX_DAEMON_ENABLED=false
    if legacy_control_truthy "$CTX_DISABLE_DAEMON"; then
      CTX_DAEMON_ENABLED=false
      export CTX_DAEMON_ENABLED
    fi
  fi
  if [ "${CTX_UPGRADE_OFF+x}" = x ]; then
    note_deprecated_control CTX_UPGRADE_OFF CTX_UPGRADE_AUTO=off
    if legacy_control_truthy "$CTX_UPGRADE_OFF"; then
      CTX_UPGRADE_AUTO=off
      export CTX_UPGRADE_AUTO
    fi
  fi
  if [ "${CTX_DISABLE_AUTO_UPGRADE+x}" = x ]; then
    note_deprecated_control CTX_DISABLE_AUTO_UPGRADE CTX_UPGRADE_AUTO=off
    if legacy_control_truthy "$CTX_DISABLE_AUTO_UPGRADE"; then
      CTX_UPGRADE_AUTO=off
      export CTX_UPGRADE_AUTO
    fi
  fi
  unset CTX_ANALYTICS_OFF CTX_DISABLE_ANALYTICS CTX_INSTALL_DIAGNOSTICS_OFF
  unset CTX_DAEMON_OFF CTX_DISABLE_DAEMON CTX_UPGRADE_OFF CTX_DISABLE_AUTO_UPGRADE
  if [ -n "$deprecated_control_warning" ]; then
    log "warning: deprecated environment variables detected: $deprecated_control_warning. Update your environment to use the replacements."
  fi
}

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

append_skill_agent() {
  agent="$1"
  test -n "$agent" || fail "--skill-agent requires a value"
  case "$agent" in
    *'
'*) fail "invalid skill agent: $agent" ;;
  esac
  if [ -n "$skill_agents" ]; then
    skill_agents="$skill_agents
$agent"
  else
    skill_agents="$agent"
  fi
}

ci_environment() {
  ci_value="$(
    printf '%s' "${CI-}" |
      LC_ALL=C sed 's/^[[:space:]]*//; s/[[:space:]]*$//'
  )"
  case "$ci_value" in
    1|[Tt][Rr][Uu][Ee]|[Yy][Ee][Ss]|[Oo][Nn]) return 0 ;;
    *) return 1 ;;
  esac
}

has_controlling_tty() {
  ( : </dev/tty ) 2>/dev/null && ( : >/dev/tty ) 2>/dev/null
}

release_functions_base="${CTX_UPGRADE_FUNCTIONS_BASE:-https://cli.ctx.rs/functions/v2}"
install_telemetry_endpoint="https://cli.ctx.rs/functions/v1/install-attempt"
channel="${CTX_UPGRADE_CHANNEL:-stable}"
if [ "$channel" != "stable" ] &&
   [ "$release_functions_base" = "https://cli.ctx.rs/functions/v2" ] &&
   [ -z "${CTX_UPGRADE_FUNCTIONS_BASE:-}" ]; then
  release_functions_base="https://cli.ctx.rs/functions/v1"
fi
install_attempt_id="ia_d-L-aXMaG7rySOrmK8slaMVg"
if [ -n "${CTX_INSTALL_ATTEMPT_ID-}" ] && valid_install_attempt_id "$CTX_INSTALL_ATTEMPT_ID"; then
  install_attempt_id="$CTX_INSTALL_ATTEMPT_ID"
fi
unset CTX_INSTALL_ATTEMPT_ID
explicit_metadata=0
if [ -n "${CTX_RELEASE_METADATA_URL:-}${CTX_RELEASE_METADATA_SIGNATURE_URL:-}${CTX_UPGRADE_FUNCTIONS_BASE:-}" ]; then
  explicit_metadata=1
fi
metadata_url="${CTX_RELEASE_METADATA_URL:-${release_functions_base%/}/releases/$channel/ctx-release-metadata.env}"
metadata_signature_url="${CTX_RELEASE_METADATA_SIGNATURE_URL:-$metadata_url.sig}"
bin_dir="${CTX_BIN_DIR:-${HOME:-}/.local/bin}"
man_dir="${CTX_MAN_DIR:-${HOME:-}/.local/share/man/man1}"
run_setup=1
setup_no_daemon=0
run_skill=1
modify_path=1
no_skill_requested=0
explicit_skill_request=0
all_skill_agents=0
skill_agents=
install_man=1
semantic_enabled=0
pro_trial_accept_requested=0
pro_trial_skip_requested=0
pro_trial_selection=skip
pro_trial_offer_enabled=1

while [ "$#" -gt 0 ]; do
  case "$1" in
    --semantic)
      semantic_enabled=1
      ;;
    --no-setup)
      run_setup=0
      ;;
    --no-daemon)
      setup_no_daemon=1
      ;;
    --pro-trial)
      pro_trial_accept_requested=1
      ;;
    --no-pro-trial)
      pro_trial_skip_requested=1
      ;;
    --no-skill)
      run_skill=0
      no_skill_requested=1
      ;;
    --skill-agent)
      shift
      append_skill_agent "${1:-}"
      explicit_skill_request=1
      ;;
    --all-skill-agents)
      all_skill_agents=1
      explicit_skill_request=1
      ;;
    --no-modify-path)
      modify_path=0
      ;;
    --no-man)
      install_man=0
      ;;
    --man-dir)
      shift
      man_dir="${1:-}"
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    *)
      fail "unknown argument: $1"
      ;;
  esac
  shift
done

test -n "$bin_dir" || fail "CTX_BIN_DIR is empty and HOME is unavailable"
test -n "$man_dir" || fail "CTX_MAN_DIR is empty and --man-dir was not provided"
secure_bin_dir="${bin_dir%/}"
case "$secure_bin_dir" in
  /*) ;;
  *) fail "ctx install directory must be an absolute path" ;;
esac
case "$secure_bin_dir$man_dir" in
  *[[:cntrl:]]*) fail "installer paths must not contain control characters" ;;
esac
[ ! -L "$secure_bin_dir" ] || fail "ctx install directory must not be a symlink"

need_cmd awk
need_cmd curl
need_cmd date
need_cmd id
need_cmd install
need_cmd ln
need_cmd mktemp
need_cmd openssl
need_cmd sort
need_cmd stat
need_cmd uname

detect_platform() {
  os="$(uname -s 2>/dev/null || printf unknown)"
  arch="$(uname -m 2>/dev/null || printf unknown)"
  case "$os:$arch" in
    Linux:x86_64|Linux:amd64) printf 'linux-x64' ;;
    Linux:aarch64|Linux:arm64) printf 'linux-aarch64' ;;
    Darwin:arm64|Darwin:aarch64) printf 'macos-arm64' ;;
    Darwin:x86_64|Darwin:amd64) printf 'macos-x64' ;;
    *) return 1 ;;
  esac
}

write_bounded_file() {
  # Apple's system sh uses Bash 3.2's fixed 1024-byte file-limit units even
  # in POSIX mode. Supported Linux system sh implementations use 512 bytes.
  case "$(uname -s)" in
    Darwin) file_limit_unit=1024 ;;
    Linux) file_limit_unit=512 ;;
    *) fail "cannot determine file-limit units for this host" ;;
  esac
  file_limit_blocks=$((($1 + file_limit_unit - 1) / file_limit_unit))
  shift
  # Keep the limit in the prescribed system shell, independent of caller mode.
  /bin/sh -c 'ulimit -c 0 || exit 1; ulimit -f "$1" || exit 1; shift; exec "$@"' \
    ctx-bounded "$file_limit_blocks" "$@"
}

download_file() {
  url="$1"
  dest="$2"
  case "$url" in
    https://*) ;;
    *) fail "refusing non-HTTPS download URL: $url" ;;
  esac
  # curl may finish one last attempt after retry-max-time; it is not a total
  # operation deadline. Each attempt, including redirects/body, has max-time.
  write_bounded_file "${3:-268435456}" curl --proto '=https' --tlsv1.2 -fsSL \
    --retry 3 --connect-timeout 20 --max-time "${4:-3600}" \
    --retry-max-time "${4:-3600}" "$url" -o "$dest"
}

download_release_artifact() {
  raw_url="$1"
  raw_dest="$2"
  gzip_url="$raw_url.gz"
  gzip_dest="$raw_dest.gz"
  artifact_compression="identity"

  if command -v gzip >/dev/null 2>&1; then
    # Official single-member gzip producers: G(C)=C+ceil(C/8)+ceil(C/64)+32.
    # C=256MiB, G(C)=292MiB+32; round up to the system shell's file-limit unit.
    if download_file "$gzip_url" "$gzip_dest" 306184224 3600 2>/dev/null; then
      if ! write_bounded_file 268435456 gzip -dc "$gzip_dest" >"$raw_dest"; then
        fail "could not decompress release artifact: $gzip_url"
      fi
      artifact_compression="gzip"
      return 0
    fi
    rm -f "$gzip_dest"
  fi

  download_file "$raw_url" "$raw_dest"
}

write_metadata_public_key() {
  cat >"$1" <<'CTX_METADATA_PUBLIC_KEY'
-----BEGIN PUBLIC KEY-----
MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAyBPNIx3H/NwWlN9CPHY5
kOEe9kQEshOJEMpv3Atq086H1FWqliTm3BCWiO4s/89wNMn11Pla2JetCWNiWsbx
m3BIxCd1o6cq8y9ur6Zk1RGOQBLQgqhFm5BpcTTavhtlc3FdV2KSm2UU1IEJAiFX
JyMlbgmf3tXfO8Cji/3mG11rWCXfnEzXJmig5/WWA21ZgsafPJGH9ow7FsLok5G1
kvOeVDXcv0gzmxWH+2O40kCGWo7BK7P/2DPD2GbXc81Mf6S7vWi7CeFiBeGH8EGZ
6MgBM0UnAFEqtx/WvY47O+LHzFrGlJTpss3xlxsSQOTmXDJdOzmQVi04GkbOtBEl
+dIyYsxZGusLBMGDqkZekO4Z5LvqA8zHt4JAElZCs8SGTlV70MSlnyZb5/rkKx9k
Mvb7YjuYbY6vnN5Pp3P7gMhOKehP+62U80cgyj1m6Sk5bByrs54ne2mM+cwNXXgK
p5UntmkefDcfKP7MmISy93U/kg3fWojE/a+X6TNV/k5fAgMBAAE=
-----END PUBLIC KEY-----
CTX_METADATA_PUBLIC_KEY
}

verify_release_metadata_signature() {
  metadata_path="$1"
  signature_path="$2"
  public_key_path="$3"
  raw_signature_path="$tmp_dir/metadata.sig.raw"

  if ! openssl enc -A -d -base64 -in "$signature_path" -out "$raw_signature_path" 2>/dev/null; then
    fail "metadata signature is not base64-encoded RSA-SHA256 bytes"
  fi
  [ -s "$raw_signature_path" ] || fail "metadata signature is empty"
  if ! openssl dgst -sha256 -verify "$public_key_path" -signature "$raw_signature_path" "$metadata_path" >/dev/null 2>&1; then
    fail "metadata signature verification failed"
  fi
}

metadata_value() {
  file="$1"
  key="$2"
  awk -F= -v key="$key" '
    $0 ~ /^[[:space:]]*#/ { next }
    $1 == key { print substr($0, length(key) + 2); found = 1; exit }
    END { if (!found) exit 1 }
  ' "$file"
}

metadata_value_optional() {
  file="$1"
  key="$2"
  metadata_value "$file" "$key" 2>/dev/null || true
}

load_persisted_config_controls() {
  persisted_semantic_enabled=0
  persisted_daemon_disabled=0
  if [ -n "${CTX_DATA_ROOT:-}" ]; then
    persisted_config_file="${CTX_DATA_ROOT%/}/config.toml"
  else
    [ -n "${HOME:-}" ] || return 0
    persisted_config_file="${HOME%/}/.ctx/config.toml"
  fi
  [ -f "$persisted_config_file" ] || return 0

  if persisted_config_values="$(LC_ALL=C awk -v config_path="$persisted_config_file" '
    function trim(value, before, position, width) {
      while (1) {
        before = value
        sub(/^[[:space:]]+/, "", value)
        for (position = 1; position <= unicode_space_count; position += 1) {
          width = length(unicode_space[position])
          if (substr(value, 1, width) == unicode_space[position]) {
            value = substr(value, width + 1)
            break
          }
        }
        if (value == before) {
          break
        }
      }
      while (1) {
        before = value
        sub(/[[:space:]]+$/, "", value)
        for (position = 1; position <= unicode_space_count; position += 1) {
          width = length(unicode_space[position])
          if (substr(value, length(value) - width + 1) == unicode_space[position]) {
            value = substr(value, 1, length(value) - width)
            break
          }
        }
        if (value == before) {
          break
        }
      }
      return value
    }
    function continuation(byte) {
      return byte >= 128 && byte <= 191
    }
    function valid_utf8(value, position, value_length, first, second, third, fourth) {
      value_length = length(value)
      position = 1
      while (position <= value_length) {
        first = byte_value[substr(value, position, 1)]
        if (first <= 127) {
          position += 1
        } else if (first >= 194 && first <= 223) {
          if (position + 1 > value_length) return 0
          second = byte_value[substr(value, position + 1, 1)]
          if (!continuation(second)) return 0
          position += 2
        } else if (first == 224) {
          if (position + 2 > value_length) return 0
          second = byte_value[substr(value, position + 1, 1)]
          third = byte_value[substr(value, position + 2, 1)]
          if (second < 160 || second > 191 || !continuation(third)) return 0
          position += 3
        } else if ((first >= 225 && first <= 236) || (first >= 238 && first <= 239)) {
          if (position + 2 > value_length) return 0
          second = byte_value[substr(value, position + 1, 1)]
          third = byte_value[substr(value, position + 2, 1)]
          if (!continuation(second) || !continuation(third)) return 0
          position += 3
        } else if (first == 237) {
          if (position + 2 > value_length) return 0
          second = byte_value[substr(value, position + 1, 1)]
          third = byte_value[substr(value, position + 2, 1)]
          if (second < 128 || second > 159 || !continuation(third)) return 0
          position += 3
        } else if (first == 240) {
          if (position + 3 > value_length) return 0
          second = byte_value[substr(value, position + 1, 1)]
          third = byte_value[substr(value, position + 2, 1)]
          fourth = byte_value[substr(value, position + 3, 1)]
          if (second < 144 || second > 191 ||
              !continuation(third) || !continuation(fourth)) return 0
          position += 4
        } else if (first >= 241 && first <= 243) {
          if (position + 3 > value_length) return 0
          second = byte_value[substr(value, position + 1, 1)]
          third = byte_value[substr(value, position + 2, 1)]
          fourth = byte_value[substr(value, position + 3, 1)]
          if (!continuation(second) ||
              !continuation(third) || !continuation(fourth)) return 0
          position += 4
        } else if (first == 244) {
          if (position + 3 > value_length) return 0
          second = byte_value[substr(value, position + 1, 1)]
          third = byte_value[substr(value, position + 2, 1)]
          fourth = byte_value[substr(value, position + 3, 1)]
          if (second < 128 || second > 143 ||
              !continuation(third) || !continuation(fourth)) return 0
          position += 4
        } else {
          return 0
        }
      }
      return 1
    }
    function strip_comment(line, position, char, in_single, in_double, escaped) {
      in_single = 0
      in_double = 0
      escaped = 0
      for (position = 1; position <= length(line); position += 1) {
        char = substr(line, position, 1)
        if (in_double) {
          if (escaped) {
            escaped = 0
          } else if (char == "\\") {
            escaped = 1
          } else if (char == "\"") {
            in_double = 0
          }
          continue
        }
        if (in_single) {
          if (char == sprintf("%c", 39)) {
            in_single = 0
          }
          continue
        }
        if (char == "#") {
          return substr(line, 1, position - 1)
        }
        if (char == "\"") {
          in_double = 1
        } else if (char == sprintf("%c", 39)) {
          in_single = 1
        }
      }
      return line
    }
    function reject(message) {
      print message
      invalid = 1
      exit 2
    }
    function quoted_string(value, first, last) {
      if (length(value) < 2) {
        return 0
      }
      first = substr(value, 1, 1)
      last = substr(value, length(value), 1)
      return (first == "\"" && last == "\"") ||
        (first == sprintf("%c", 39) && last == sprintf("%c", 39))
    }
    # Only validate values consumed by installer preflight. Core owns the full schema.
    function validate_value(full_key, value, line_number, string_value) {
      if (full_key == "daemon.enabled" ||
          full_key == "search.semantic") {
        if (value != "true" && value != "false") {
          reject(full_key " at line " line_number " must be a boolean")
        }
        return
      }
      if (full_key == "indexing.mode") {
        if (!quoted_string(value)) {
          reject(full_key " at line " line_number " must be a quoted string")
        }
        string_value = tolower(substr(value, 2, length(value) - 2))
        if (string_value != "auto" && string_value != "automatic" && string_value != "manual") {
          reject(full_key " at line " line_number " must be either "auto" or "manual"")
        }
        return
      }
    }
    BEGIN {
      for (byte = 0; byte <= 255; byte += 1) {
        byte_value[sprintf("%c", byte)] = byte
      }
      unicode_space[++unicode_space_count] = sprintf("%c%c", 194, 133)
      unicode_space[++unicode_space_count] = sprintf("%c%c", 194, 160)
      unicode_space[++unicode_space_count] = sprintf("%c%c%c", 225, 154, 128)
      for (byte = 128; byte <= 138; byte += 1) {
        unicode_space[++unicode_space_count] = sprintf("%c%c%c", 226, 128, byte)
      }
      unicode_space[++unicode_space_count] = sprintf("%c%c%c", 226, 128, 168)
      unicode_space[++unicode_space_count] = sprintf("%c%c%c", 226, 128, 169)
      unicode_space[++unicode_space_count] = sprintf("%c%c%c", 226, 128, 175)
      unicode_space[++unicode_space_count] = sprintf("%c%c%c", 226, 129, 159)
      unicode_space[++unicode_space_count] = sprintf("%c%c%c", 227, 128, 128)
      semantic_enabled = 0
      legacy_daemon_disabled = 0
      indexing_mode_set = 0
      indexing_disabled = 0
    }
    {
      if (!valid_utf8($0)) {
        reject("persisted config is not valid UTF-8: " config_path)
      }
      line = trim(strip_comment($0))
      if (line == "") {
        next
      }
      if (substr(line, 1, 1) == "[") {
        if (substr(line, length(line), 1) != "]") {
          reject("invalid config section header at line " NR ": " line)
        }
        section = trim(substr(line, 2, length(line) - 2))
        if (section == "") {
          reject("empty config section header at line " NR)
        }
        next
      }
      equals = index(line, "=")
      if (equals == 0) {
        reject("invalid config line " NR ": expected `[section]` or `key = value`")
      }
      key = trim(substr(line, 1, equals - 1))
      if (key == "") {
        reject("empty config key at line " NR)
      }
      value = trim(substr(line, equals + 1))
      full_key = section == "" ? key : section "." key
      if (full_key in first_line) {
        reject(sprintf("duplicate config key `%s` at line %d; first set at line %d", full_key, NR, first_line[full_key]))
      }
      first_line[full_key] = NR
      validate_value(full_key, value, NR)
      if (full_key == "search.semantic") {
        semantic_enabled = value == "true"
      } else if (full_key == "daemon.enabled") {
        legacy_daemon_disabled = value == "false"
      } else if (full_key == "indexing.mode") {
        indexing_mode_set = 1
        indexing_value = tolower(substr(value, 2, length(value) - 2))
        indexing_disabled = indexing_value == "manual"
      }
    }
    END {
      if (!invalid) {
        daemon_disabled = indexing_mode_set ? indexing_disabled : legacy_daemon_disabled
        printf "%d %d\n", semantic_enabled, daemon_disabled
      }
    }
  ' "$persisted_config_file")"; then
    set -- $persisted_config_values
    [ "$#" -eq 2 ] || fail "could not parse persisted config controls"
    persisted_semantic_enabled="$1"
    persisted_daemon_disabled="$2"
  else
    [ -n "$persisted_config_values" ] ||
      persisted_config_values="could not read persisted config: $persisted_config_file"
    fail "$persisted_config_values"
  fi
}

canonical_daemon_disabled() {
  [ "${CTX_DAEMON_ENABLED+x}" = "x" ] || return 1
  daemon_control_value="$CTX_DAEMON_ENABLED"
  while :; do
    case "$daemon_control_value" in
      [[:space:]]*) daemon_control_value="${daemon_control_value#?}" ;;
      *) break ;;
    esac
  done
  while :; do
    case "$daemon_control_value" in
      *[[:space:]]) daemon_control_value="${daemon_control_value%?}" ;;
      *) break ;;
    esac
  done
  while :; do
    case "$daemon_control_value" in
      '"'* ) daemon_control_value="${daemon_control_value#?}" ;;
      *) break ;;
    esac
  done
  while :; do
    case "$daemon_control_value" in
      *'"') daemon_control_value="${daemon_control_value%?}" ;;
      *) break ;;
    esac
  done
  case "$daemon_control_value" in
    0|[Ff][Aa][Ll][Ss][Ee]|[Nn][Oo]|[Oo][Ff][Ff]) return 0 ;;
    *) return 1 ;;
  esac
}

absolute_path_to_file_uri() {
  file_path="$1"
  case "$file_path" in
    /*) ;;
    *) fail "cannot create file URI from non-absolute path: $file_path" ;;
  esac
  printf 'file://'
  printf '%s' "$file_path" | LC_ALL=C od -A n -v -t x1 | awk '
    {
      for (i = 1; i <= NF; i++) {
        if ($i == "2f") {
          printf "/"
        } else {
          printf "%%%s", toupper($i)
        }
      }
    }
    END { printf "\n" }
  '
}

validate_safe_value() {
  name="$1"
  value="$2"
  case "$value" in
    *'
'*|*'..'*|*'/'*|*'\'*) fail "unsafe $name: $value" ;;
  esac
}

profile_has_path_line() {
  profile="$1"
  needle="$2"
  [ -f "$profile" ] || return 1
  awk -v needle="$needle" '
    $0 ~ /^[[:space:]]*#/ { next }
    index($0, needle) && ($0 ~ /PATH/ || $0 ~ /fish_user_paths/ || $0 ~ /fish_add_path/) {
      found = 1
      exit
    }
    END { exit found ? 0 : 1 }
  ' "$profile"
}

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

path_setup_profile() {
  shell_name="$1"
  test -n "${HOME:-}" || return 1

  case "$shell_name" in
    fish)
      printf '%s/.config/fish/config.fish\n' "$HOME"
      ;;
    zsh)
      printf '%s/.zshrc\n' "${ZDOTDIR:-$HOME}"
      ;;
    bash)
      if [ -f "$HOME/.bashrc" ]; then
        printf '%s/.bashrc\n' "$HOME"
      elif [ -f "$HOME/.bash_profile" ]; then
        printf '%s/.bash_profile\n' "$HOME"
      elif [ -f "$HOME/.profile" ]; then
        printf '%s/.profile\n' "$HOME"
      else
        case "$platform" in
          macos-*) printf '%s/.bash_profile\n' "$HOME" ;;
          *) printf '%s/.bashrc\n' "$HOME" ;;
        esac
      fi
      ;;
    *)
      printf '%s/.profile\n' "$HOME"
      ;;
  esac
}

profile_contains_path_setup() {
  profile="$1"
  dir="${2%/}"
  profile_has_path_line "$profile" "$dir" && return 0
  dir_escaped="$(shell_double_quote_escape "$dir")"
  profile_has_path_line "$profile" "$dir_escaped" && return 0

  if [ -n "${HOME:-}" ]; then
    home_prefix="${HOME%/}/"
    case "$dir" in
      "$home_prefix"*)
        rel="${dir#"$home_prefix"}"
        profile_has_path_line "$profile" "\$HOME/$rel" && return 0
        profile_has_path_line "$profile" "~/$rel" && return 0
        ;;
    esac
  fi

  return 1
}

path_setup_snippet() {
  shell_name="$1"
  dir_escaped="$(shell_double_quote_escape "$2")"

  case "$shell_name" in
    fish)
      cat <<EOF
# >>> ctx installer PATH setup >>>
if test "\$PATH[1]" != "$dir_escaped"
    set -gx PATH "$dir_escaped" \$PATH
end
# <<< ctx installer PATH setup <<<
EOF
      ;;
    *)
      cat <<EOF
# >>> ctx installer PATH setup >>>
case "\${PATH}:" in
  "$dir_escaped:"*) ;;
  *) export PATH="$dir_escaped:\${PATH}" ;;
esac
# <<< ctx installer PATH setup <<<
EOF
      ;;
  esac
}

bare_ctx_resolves_to_install() {
  resolved_ctx="$(command -v ctx 2>/dev/null || true)"
  [ -n "$resolved_ctx" ] || return 1
  resolved_ctx="$(canonical_file_path "$resolved_ctx" 2>/dev/null || true)"
  [ -n "$resolved_ctx" ] && [ "$resolved_ctx" = "$install_path" ]
}

configure_path_if_needed() {
  dir="${bin_dir%/}"
  path_result=
  path_export_command=
  path_profile_persisted=0
  path_profile_semantics=0
  if bare_ctx_resolves_to_install; then
    return 0
  fi

  dir_escaped="$(shell_double_quote_escape "$dir")"
  path_export_command="export PATH=\"$dir_escaped:\$PATH\""
  path_display_dir="$dir"
  if [ -n "${HOME:-}" ] && [ "$dir" = "${HOME%/}/.local/bin" ]; then
    path_display_dir='$HOME/.local/bin'
    path_export_command='export PATH="$HOME/.local/bin:$PATH"'
  fi
  shell_name="${SHELL:-}"
  shell_name="${shell_name##*/}"
  [ -n "$shell_name" ] || shell_name="sh"

  if [ "$modify_path" != "1" ]; then
    path_result=1
    return 0
  fi

  if [ -n "${GITHUB_PATH:-}" ]; then
    printf '%s\n' "$dir" >>"$GITHUB_PATH" || true
    path_result=1
    return 0
  fi

  if [ "${CI:-}" = "1" ] || [ "${CI:-}" = "true" ]; then
    path_result=1
    return 0
  fi

  if ! profile="$(path_setup_profile "$shell_name")"; then
    path_result=1
    return 0
  fi

  if profile_contains_path_setup "$profile" "$dir"; then
    path_profile_semantics=1
    path_result=1
  else
    profile_dir="$(dirname "$profile")"
    profile_existed=0
    if [ -e "$profile" ] || [ -L "$profile" ]; then
      profile_existed=1
    fi
    profile_snippet="$tmp_dir/path-profile-snippet"
    path_setup_snippet "$shell_name" "$dir" >"$profile_snippet"
    if [ "$profile_existed" = "1" ] &&
       { [ ! -f "$profile" ] ||
         [ -L "$profile" ] ||
         [ "$(path_owner_uid "$profile" 2>/dev/null || true)" != "$(id -u)" ] ||
         [ "$(stat -c '%h' "$profile" 2>/dev/null || stat -f '%l' "$profile" 2>/dev/null || true)" != "1" ]; }; then
      path_result=1
    elif mkdir -p "$profile_dir" &&
         { [ "$profile_existed" != "1" ] ||
           [ ! -s "$profile" ] ||
           [ -z "$(tail -c 1 "$profile" 2>/dev/null)" ] ||
           printf '\n' >>"$profile"; } &&
         cat "$profile_snippet" >>"$profile"; then
      if [ "$profile_existed" = "1" ]; then
        record_owned_integration profile-block "$(sha256_file "$profile_snippet")" "$profile"
      else
        record_owned_integration profile-file "$(sha256_file "$profile")" "$profile"
      fi
      path_profile_persisted=1
      path_result=1
    else
      path_result=1
    fi
  fi
}

json_scalar_field() {
  json_file="$1"
  json_field="$2"
  awk -v field="$json_field" '
    $0 ~ "^  \"" field "\"[[:space:]]*:" {
      value = $0
      sub(/^[^:]*:[[:space:]]*/, "", value)
      sub(/[[:space:]]*,?[[:space:]]*$/, "", value)
      gsub(/^"|"$/, "", value)
      print value
      exit
    }
  ' "$json_file"
}

json_document_is_well_formed() {
  json_file="$1"
  json_document_size="$(LC_ALL=C wc -c < "$json_file" 2>/dev/null)" || return 1
  json_document_size="${json_document_size##* }"
  case "$json_document_size" in
    ""|*[!0-9]*) return 1 ;;
  esac
  [ "$json_document_size" -le 1048576 ] || return 1
  LC_ALL=C awk '
    BEGIN {
      quote = sprintf("%c", 34)
      backslash = sprintf("%c", 92)
      delete_character = sprintf("%c", 127)
      tab = sprintf("%c", 9)
      newline = sprintf("%c", 10)
      carriage_return = sprintf("%c", 13)
      for (byte = 0; byte <= 255; byte++) {
        byte_value[sprintf("%c", byte)] = byte
      }
    }
    function skip_whitespace(    character) {
      while (position <= document_length) {
        character = substr(document, position, 1)
        if (character != " " && character != tab &&
            character != newline && character != carriage_return) return
        position++
      }
    }
    function consume_utf8_scalar(    first, second, third, fourth) {
      first = byte_value[substr(document, position, 1)]
      if (first >= 194 && first <= 223) {
        if (position + 1 > document_length) return 0
        second = byte_value[substr(document, position + 1, 1)]
        if (second < 128 || second > 191) return 0
        position += 2
        return 1
      }
      if (first == 224) {
        if (position + 2 > document_length) return 0
        second = byte_value[substr(document, position + 1, 1)]
        third = byte_value[substr(document, position + 2, 1)]
        if (second < 160 || second > 191 ||
            third < 128 || third > 191) return 0
        position += 3
        return 1
      }
      if ((first >= 225 && first <= 236) ||
          (first >= 238 && first <= 239)) {
        if (position + 2 > document_length) return 0
        second = byte_value[substr(document, position + 1, 1)]
        third = byte_value[substr(document, position + 2, 1)]
        if (second < 128 || second > 191 ||
            third < 128 || third > 191) return 0
        position += 3
        return 1
      }
      if (first == 237) {
        if (position + 2 > document_length) return 0
        second = byte_value[substr(document, position + 1, 1)]
        third = byte_value[substr(document, position + 2, 1)]
        if (second < 128 || second > 159 ||
            third < 128 || third > 191) return 0
        position += 3
        return 1
      }
      if (first == 240) {
        if (position + 3 > document_length) return 0
        second = byte_value[substr(document, position + 1, 1)]
        third = byte_value[substr(document, position + 2, 1)]
        fourth = byte_value[substr(document, position + 3, 1)]
        if (second < 144 || second > 191 ||
            third < 128 || third > 191 ||
            fourth < 128 || fourth > 191) return 0
        position += 4
        return 1
      }
      if (first >= 241 && first <= 243) {
        if (position + 3 > document_length) return 0
        second = byte_value[substr(document, position + 1, 1)]
        third = byte_value[substr(document, position + 2, 1)]
        fourth = byte_value[substr(document, position + 3, 1)]
        if (second < 128 || second > 191 ||
            third < 128 || third > 191 ||
            fourth < 128 || fourth > 191) return 0
        position += 4
        return 1
      }
      if (first == 244) {
        if (position + 3 > document_length) return 0
        second = byte_value[substr(document, position + 1, 1)]
        third = byte_value[substr(document, position + 2, 1)]
        fourth = byte_value[substr(document, position + 3, 1)]
        if (second < 128 || second > 143 ||
            third < 128 || third > 191 ||
            fourth < 128 || fourth > 191) return 0
        position += 4
        return 1
      }
      return 0
    }
    function hex_value(character,    offset) {
      offset = index("0123456789abcdef", tolower(character))
      return offset == 0 ? -1 : offset - 1
    }
    function parse_hex_quad(start,    offset, digit, value) {
      value = 0
      for (offset = 0; offset < 4; offset++) {
        digit = hex_value(substr(document, start + offset, 1))
        if (digit < 0) return -1
        value = value * 16 + digit
      }
      return value
    }
    function parse_string(    character, escape, codepoint, low_surrogate, byte) {
      if (substr(document, position, 1) != quote) return 0
      position++
      while (position <= document_length) {
        character = substr(document, position, 1)
        if (character == quote) {
          position++
          return 1
        }
        if (character == backslash) {
          position++
          if (position > document_length) return 0
          escape = substr(document, position, 1)
          if (escape == "u") {
            codepoint = parse_hex_quad(position + 1)
            if (codepoint < 0) return 0
            if (codepoint >= 55296 && codepoint <= 56319) {
              if (substr(document, position + 5, 2) != backslash "u") return 0
              low_surrogate = parse_hex_quad(position + 7)
              if (low_surrogate < 56320 || low_surrogate > 57343) return 0
              position += 11
            } else {
              if (codepoint >= 56320 && codepoint <= 57343) return 0
              position += 5
            }
          } else if (escape == quote || escape == backslash ||
                     escape == "/" || escape == "b" || escape == "f" ||
                     escape == "n" || escape == "r" || escape == "t") {
            position++
          } else {
            return 0
          }
        } else {
          byte = byte_value[character]
          if (byte >= 128) {
            if (!consume_utf8_scalar()) return 0
          } else {
            if (character ~ /[[:cntrl:]]/ &&
                character != delete_character) return 0
            position++
          }
        }
      }
      return 0
    }
    function parse_number(    character) {
      if (substr(document, position, 1) == "-") position++
      character = substr(document, position, 1)
      if (character == "0") {
        position++
      } else if (character ~ /^[1-9]$/) {
        do {
          position++
          character = substr(document, position, 1)
        } while (character ~ /^[0-9]$/)
      } else {
        return 0
      }
      if (substr(document, position, 1) == ".") {
        position++
        if (substr(document, position, 1) !~ /^[0-9]$/) return 0
        while (substr(document, position, 1) ~ /^[0-9]$/) position++
      }
      character = substr(document, position, 1)
      if (character == "e" || character == "E") {
        position++
        character = substr(document, position, 1)
        if (character == "+" || character == "-") position++
        if (substr(document, position, 1) !~ /^[0-9]$/) return 0
        while (substr(document, position, 1) ~ /^[0-9]$/) position++
      }
      return 1
    }
    function parse_object(depth,    character) {
      position++
      skip_whitespace()
      if (substr(document, position, 1) == "}") {
        position++
        return 1
      }
      while (position <= document_length) {
        if (!parse_string()) return 0
        skip_whitespace()
        if (substr(document, position, 1) != ":") return 0
        position++
        if (!parse_value(depth + 1)) return 0
        skip_whitespace()
        character = substr(document, position, 1)
        if (character == "}") {
          position++
          return 1
        }
        if (character != ",") return 0
        position++
        skip_whitespace()
      }
      return 0
    }
    function parse_array(depth,    character) {
      position++
      skip_whitespace()
      if (substr(document, position, 1) == "]") {
        position++
        return 1
      }
      while (position <= document_length) {
        if (!parse_value(depth + 1)) return 0
        skip_whitespace()
        character = substr(document, position, 1)
        if (character == "]") {
          position++
          return 1
        }
        if (character != ",") return 0
        position++
        skip_whitespace()
      }
      return 0
    }
    function parse_value(depth,    character) {
      if (depth > 64) return 0
      skip_whitespace()
      character = substr(document, position, 1)
      if (character == "{") return parse_object(depth)
      if (character == "[") return parse_array(depth)
      if (character == quote) return parse_string()
      if (character == "-" || character ~ /^[0-9]$/) return parse_number()
      if (substr(document, position, 4) == "true") {
        position += 4
        return 1
      }
      if (substr(document, position, 5) == "false") {
        position += 5
        return 1
      }
      if (substr(document, position, 4) == "null") {
        position += 4
        return 1
      }
      return 0
    }
    {
      if (NR > 1024) too_many_records = 1
      if (!too_many_records) {
        if (NR > 1) document = document newline
        document = document $0
        if (length(document) > 1048576) too_large = 1
      }
    }
    END {
      if (too_large || too_many_records) exit 1
      document_length = length(document)
      position = 1
      if (!parse_value(0)) exit 1
      skip_whitespace()
      if (position != document_length + 1) exit 1
    }
  ' "$json_file"
}

json_top_level_boolean_field() {
  json_file="$1"
  json_field="$2"
  awk -v field="$json_field" '
    BEGIN {
      quote = sprintf("%c", 34)
      backslash = sprintf("%c", 92)
    }
    function update_depth(text, i, character, escaped) {
      escaped = 0
      for (i = 1; i <= length(text); i++) {
        character = substr(text, i, 1)
        if (in_string) {
          if (escaped) {
            escaped = 0
          } else if (character == backslash) {
            escaped = 1
          } else if (character == quote) {
            in_string = 0
          }
        } else if (character == quote) {
          in_string = 1
        } else if (character == "{") {
          depth++
        } else if (character == "}") {
          depth--
        }
      }
    }
    depth == 1 &&
      $0 ~ "^[[:space:]]*\"" field "\"[[:space:]]*:" {
      count++
      value = $0
      sub(/^[^:]*:[[:space:]]*/, "", value)
      sub(/[[:space:]]*,?[[:space:]]*$/, "", value)
      if (value != "true" && value != "false") invalid = 1
      result = value
    }
    {
      update_depth($0)
      if (depth < 0) invalid = 1
    }
    END {
      if (depth != 0 || in_string || count != 1 || invalid) exit 1
      print result
    }
  ' "$json_file"
}

json_top_level_string_field() {
  json_file="$1"
  json_field="$2"
  awk -v field="$json_field" '
    BEGIN {
      quote = sprintf("%c", 34)
      backslash = sprintf("%c", 92)
    }
    function update_depth(text, i, character, escaped) {
      escaped = 0
      for (i = 1; i <= length(text); i++) {
        character = substr(text, i, 1)
        if (in_string) {
          if (escaped) escaped = 0
          else if (character == backslash) escaped = 1
          else if (character == quote) in_string = 0
        } else if (character == quote) in_string = 1
        else if (character == "{") depth++
        else if (character == "}") depth--
      }
    }
    depth == 1 &&
      $0 ~ "^[[:space:]]*\"" field "\"[[:space:]]*:" {
      count++
      value = $0
      sub(/^[^:]*:[[:space:]]*"/, "", value)
      sub(/"[[:space:]]*,?[[:space:]]*$/, "", value)
      if (value !~ /^[a-z][a-z0-9_]*$/) invalid = 1
      result = value
    }
    {
      update_depth($0)
      if (depth < 0) invalid = 1
    }
    END {
      if (depth != 0 || in_string || count != 1 || invalid) exit 1
      print result
    }
  ' "$json_file"
}

json_top_level_unsigned_integer_or_null_field() {
  json_file="$1"
  json_field="$2"
  awk -v field="$json_field" '
    BEGIN {
      quote = sprintf("%c", 34)
      backslash = sprintf("%c", 92)
    }
    function update_depth(text, i, character, escaped) {
      escaped = 0
      for (i = 1; i <= length(text); i++) {
        character = substr(text, i, 1)
        if (in_string) {
          if (escaped) {
            escaped = 0
          } else if (character == backslash) {
            escaped = 1
          } else if (character == quote) {
            in_string = 0
          }
        } else if (character == quote) {
          in_string = 1
        } else if (character == "{") {
          depth++
        } else if (character == "}") {
          depth--
        }
      }
    }
    depth == 1 &&
      $0 ~ "^[[:space:]]*\"" field "\"[[:space:]]*:" {
      count++
      value = $0
      sub(/^[^:]*:[[:space:]]*/, "", value)
      sub(/[[:space:]]*,?[[:space:]]*$/, "", value)
      if (value != "null" && value !~ /^(0|[1-9][0-9]*)$/) invalid = 1
      result = value
    }
    {
      update_depth($0)
      if (depth < 0) invalid = 1
    }
    END {
      if (depth != 0 || in_string || count > 1 || invalid) exit 1
      if (count == 0) {
        print "null"
      } else {
        print result
      }
    }
  ' "$json_file"
}

json_object_string_field() {
  json_file="$1"
  json_object="$2"
  json_field="$3"
  awk -v object="$json_object" -v field="$json_field" '
    BEGIN {
      quote = sprintf("%c", 34)
      backslash = sprintf("%c", 92)
    }
    function update_depth(text, i, character, escaped) {
      escaped = 0
      for (i = 1; i <= length(text); i++) {
        character = substr(text, i, 1)
        if (in_string) {
          if (escaped) {
            escaped = 0
          } else if (character == backslash) {
            escaped = 1
          } else if (character == quote) {
            in_string = 0
          }
        } else if (character == quote) {
          in_string = 1
        } else if (character == "{") {
          depth++
        } else if (character == "}") {
          depth--
        }
      }
    }
    !in_object && depth == 1 &&
      $0 ~ "^[[:space:]]*\"" object "\"[[:space:]]*:[[:space:]]*{" {
      object_count++
      update_depth($0)
      in_object = 1
      object_depth = depth
      next
    }
    in_object && depth == object_depth &&
      $0 ~ "^[[:space:]]*\"" field "\"[[:space:]]*:" {
      field_count++
      value = $0
      sub(/^[^:]*:[[:space:]]*"/, "", value)
      sub(/"[[:space:]]*,?[[:space:]]*$/, "", value)
      if (value !~ /^[a-z][a-z0-9_]*$/) invalid = 1
      result = value
    }
    {
      update_depth($0)
      if (depth < 0) invalid = 1
      if (in_object && depth < object_depth) {
        in_object = 0
      }
    }
    END {
      if (depth != 0 || in_string || object_count != 1 ||
          field_count != 1 || invalid) exit 1
      print result
    }
  ' "$json_file"
}

json_object_boolean_field() {
  json_file="$1"
  json_object="$2"
  json_field="$3"
  awk -v object="$json_object" -v field="$json_field" '
    BEGIN {
      quote = sprintf("%c", 34)
      backslash = sprintf("%c", 92)
    }
    function update_depth(text, i, character, escaped) {
      escaped = 0
      for (i = 1; i <= length(text); i++) {
        character = substr(text, i, 1)
        if (in_string) {
          if (escaped) {
            escaped = 0
          } else if (character == backslash) {
            escaped = 1
          } else if (character == quote) {
            in_string = 0
          }
        } else if (character == quote) {
          in_string = 1
        } else if (character == "{") {
          depth++
        } else if (character == "}") {
          depth--
        }
      }
    }
    !in_object && depth == 1 &&
      $0 ~ "^[[:space:]]*\"" object "\"[[:space:]]*:[[:space:]]*{" {
      object_count++
      update_depth($0)
      in_object = 1
      object_depth = depth
      next
    }
    in_object && depth == object_depth &&
      $0 ~ "^[[:space:]]*\"" field "\"[[:space:]]*:" {
      field_count++
      value = $0
      sub(/^[^:]*:[[:space:]]*/, "", value)
      sub(/[[:space:]]*,?[[:space:]]*$/, "", value)
      if (value != "true" && value != "false") invalid = 1
      result = value
    }
    {
      update_depth($0)
      if (depth < 0) invalid = 1
      if (in_object && depth < object_depth) {
        in_object = 0
      }
    }
    END {
      if (depth != 0 || in_string || object_count != 1 ||
          field_count != 1 || invalid) exit 1
      print result
    }
  ' "$json_file"
}

json_object_string_or_null_field() {
  json_file="$1"
  json_object="$2"
  json_field="$3"
  json_required="${4:-1}"
  LC_ALL=C awk -v object="$json_object" -v field="$json_field"     -v required="$json_required" '
    BEGIN {
      quote = sprintf("%c", 34)
      backslash = sprintf("%c", 92)
    }
    function update_depth(text, i, character, escaped) {
      escaped = 0
      for (i = 1; i <= length(text); i++) {
        character = substr(text, i, 1)
        if (in_string) {
          if (escaped) escaped = 0
          else if (character == backslash) escaped = 1
          else if (character == quote) in_string = 0
        } else if (character == quote) in_string = 1
        else if (character == "{") depth++
        else if (character == "}") depth--
      }
    }
    !in_object && depth == 1 &&
      $0 ~ "^[[:space:]]*\"" object "\"[[:space:]]*:[[:space:]]*{" {
      object_count++
      update_depth($0)
      in_object = 1
      object_depth = depth
      next
    }
    in_object && depth == object_depth &&
      $0 ~ "^[[:space:]]*\"" field "\"[[:space:]]*:" {
      field_count++
      value = $0
      sub(/^[^:]*:[[:space:]]*/, "", value)
      sub(/[[:space:]]*,?[[:space:]]*$/, "", value)
      if (value == "null") {
        result = ""
      } else if (value ~ /^"[ -~]*"$/ && index(value, backslash) == 0) {
        sub(/^"/, "", value)
        sub(/"$/, "", value)
        result = value
      } else {
        invalid = 1
      }
    }
    {
      update_depth($0)
      if (depth < 0) invalid = 1
      if (in_object && depth < object_depth) in_object = 0
    }
    END {
      if (depth != 0 || in_string || object_count != 1 ||
          field_count > 1 || (required == 1 && field_count != 1) || invalid) exit 1
      print result
    }
  ' "$json_file"
}

valid_iso_date() {
  LC_ALL=C awk -v value="$1" '
    BEGIN {
      if (value !~ /^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]$/) exit 1
      year = substr(value, 1, 4) + 0
      month = substr(value, 6, 2) + 0
      day = substr(value, 9, 2) + 0
      days[1] = 31; days[2] = 28; days[3] = 31; days[4] = 30
      days[5] = 31; days[6] = 30; days[7] = 31; days[8] = 31
      days[9] = 30; days[10] = 31; days[11] = 30; days[12] = 31
      if ((year % 400 == 0) || (year % 4 == 0 && year % 100 != 0)) days[2] = 29
      if (month < 1 || month > 12 || day < 1 || day > days[month]) exit 1
    }
  '
}

valid_https_action_url() {
  action_url="$1"
  case "$action_url" in
    https://*) ;;
    *) return 1 ;;
  esac
  printf '%s' "$action_url" | LC_ALL=C awk '
    function valid_port(port) {
      return port ~ /^[0-9]+$/ && (port + 0) <= 65535
    }
    function valid_ipv4(value, octets, count, i, octet) {
      count = split(value, octets, ".")
      if (count != 4) return 0
      for (i = 1; i <= count; i++) {
        octet = octets[i]
        if (octet !~ /^[0-9]+$/ || length(octet) > 3 ||
            (length(octet) > 1 && substr(octet, 1, 1) == "0") ||
            (octet + 0) > 255) return 0
      }
      return 1
    }
    function valid_ipv6(value, compact, groups, count, i, group, explicit_groups) {
      if (value !~ /:/ || value ~ /:::/) return 0
      compact = index(value, "::") != 0
      if (compact && index(substr(value, index(value, "::") + 2), "::") != 0) return 0
      if (!compact && (substr(value, 1, 1) == ":" || substr(value, length(value), 1) == ":")) return 0
      gsub(/::/, ":", value)
      count = split(value, groups, ":")
      explicit_groups = 0
      for (i = 1; i <= count; i++) {
        group = groups[i]
        if (group == "") continue
        if (index(group, ".") != 0) {
          if (i != count || !valid_ipv4(group)) return 0
          explicit_groups += 2
        } else {
          if (group !~ /^[0-9A-Fa-f]+$/ || length(group) > 4) return 0
          explicit_groups++
        }
      }
      return compact ? explicit_groups < 8 : explicit_groups == 8
    }
    function valid_reg_name(value, labels, count, i, label) {
      if (length(value) > 253 || value ~ /^\./ || value ~ /\.$/ || value ~ /\.\./) return 0
      count = split(value, labels, "\\.")
      for (i = 1; i <= count; i++) {
        label = labels[i]
        if (length(label) > 63 ||
            label !~ /^[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?$/) return 0
      }
      return 1
    }
    /^[!-~]+$/ && length($0) <= 4096 {
      rest = substr($0, 9)
      authority = rest
      sub(/[/?#].*$/, "", authority)
      if (authority == "" || authority ~ /@/) next
      if (substr(authority, 1, 1) == "[") {
        bracket_end = index(authority, "]")
        if (bracket_end == 0) next
        host = substr(authority, 2, bracket_end - 2)
        suffix = substr(authority, bracket_end + 1)
        if (suffix != "" && suffix !~ /^:[0-9]+$/) next
        if (suffix != "" && !valid_port(substr(suffix, 2))) next
        if (valid_ipv6(host)) ok = 1
      } else {
        colon = index(authority, ":")
        if (colon == 0) {
          host = authority
        } else {
          host = substr(authority, 1, colon - 1)
          port = substr(authority, colon + 1)
          if (!valid_port(port)) next
        }
        if (host != "" && valid_reg_name(host)) ok = 1
      }
    }
    END { if (!ok) exit 1 }
  '
}

is_unsigned_integer() {
  case "$1" in
    ""|*[!0-9]*) return 1 ;;
    *) return 0 ;;
  esac
}

format_count() {
  awk -v value="$1" '
    BEGIN {
      formatted = value
      suffix = ""
      while (length(formatted) > 3) {
        suffix = "," substr(formatted, length(formatted) - 2) suffix
        formatted = substr(formatted, 1, length(formatted) - 3)
      }
      print formatted suffix
    }
  '
}

path_owner_uid() {
  stat -c '%u' "$1" 2>/dev/null || stat -f '%u' "$1" 2>/dev/null
}

path_link_count() {
  stat -c '%h' "$1" 2>/dev/null || stat -f '%l' "$1" 2>/dev/null
}

path_size_bytes() {
  stat -c '%s' "$1" 2>/dev/null || stat -f '%z' "$1" 2>/dev/null
}

sha256_file() {
  path="$1"
  if command -v sha256sum >/dev/null 2>&1; then
    sha256sum "$path" | awk '{ print $1 }'
    return 0
  fi
  if command -v shasum >/dev/null 2>&1; then
    shasum -a 256 "$path" | awk '{ print $1 }'
    return 0
  fi
  if command -v sha256 >/dev/null 2>&1; then
    sha256 -q "$path"
    return 0
  fi
  fail "sha256sum, shasum, or sha256 is required"
}

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

valid_sha256() {
  digest_value="$1"
  [ "${#digest_value}" -eq 64 ] || return 1
  case "$digest_value" in
    *[!0-9a-f]*) return 1 ;;
  esac
}

ownership_json_string_field() {
  json_path="$1"
  json_field="$2"
  awk -v field="$json_field" '
    $0 ~ "^  \"" field "\"[[:space:]]*:" {
      count++
      value = $0
      sub(/^[^:]*:[[:space:]]*"/, "", value)
      sub(/"[[:space:]]*,?[[:space:]]*$/, "", value)
      result = value
    }
    END {
      if (count != 1) exit 1
      print result
    }
  ' "$json_path"
}

ownership_json_field_count() {
  json_path="$1"
  json_field="$2"
  awk -v field="$json_field" '
    $0 ~ "^  \"" field "\"[[:space:]]*:" {
      count++
    }
    END {
      print count + 0
    }
  ' "$json_path"
}

ownership_json_number_field() {
  json_path="$1"
  json_field="$2"
  awk -v field="$json_field" '
    $0 ~ "^  \"" field "\"[[:space:]]*:" {
      count++
      value = $0
      sub(/^[^:]*:[[:space:]]*/, "", value)
      sub(/[[:space:]]*,?[[:space:]]*$/, "", value)
      result = value
    }
    END {
      if (count != 1) exit 1
      print result
    }
  ' "$json_path"
}

canonical_file_path() {
  requested_path="$1"
  case "$requested_path" in
    /*) ;;
    *) return 1 ;;
  esac
  requested_name="${requested_path##*/}"
  requested_parent="${requested_path%/*}"
  [ -n "$requested_name" ] && [ "$requested_parent" != "$requested_path" ] || return 1
  resolved_parent="$(cd -P "$requested_parent" 2>/dev/null && pwd -P)" || return 1
  printf '%s/%s\n' "${resolved_parent%/}" "$requested_name"
}

valid_prior_owned_path() {
  candidate="$1"
  case "$candidate" in
    /*) ;;
    *) return 1 ;;
  esac
  case "/$candidate/" in
    */../*|*/./*) return 1 ;;
  esac
  carriage_return="$(printf '\r')"
  case "$candidate" in
    *"$carriage_return"*) return 1 ;;
  esac
  case "$candidate" in
    *'
'*|*'	'*) return 1 ;;
  esac
}

valid_prior_profile_path() {
  candidate="$1"
  valid_prior_owned_path "$candidate" || return 1
  case "$candidate" in
    "$HOME/.bashrc"|"$HOME/.bash_profile"|"$HOME/.profile"|    "${ZDOTDIR:-$HOME}/.zshrc"|"$HOME/.config/fish/config.fish") return 0 ;;
    *) return 1 ;;
  esac
}

prior_regular_file_matches() {
  candidate="$1"
  expected_digest="$2"
  [ -f "$candidate" ] && [ ! -L "$candidate" ] ||
    return 1
  [ "$(path_owner_uid "$candidate" 2>/dev/null || true)" = "$current_uid" ] ||
    return 1
  [ "$(path_link_count "$candidate" 2>/dev/null || true)" = "1" ] ||
    return 1
  actual_digest="$(sha256_file "$candidate" 2>/dev/null | tr 'A-F' 'a-f' || true)"
  [ "$actual_digest" = "$expected_digest" ]
}

prior_profile_block_matches() {
  candidate="$1"
  expected_digest="$2"
  valid_prior_profile_path "$candidate" ||
    return 1
  [ -f "$candidate" ] && [ ! -L "$candidate" ] ||
    return 1
  [ "$(path_owner_uid "$candidate" 2>/dev/null || true)" = "$current_uid" ] ||
    return 1
  [ "$(path_link_count "$candidate" 2>/dev/null || true)" = "1" ] ||
    return 1
  profile_extract="$tmp_dir/prior-profile-block"
  if ! awk '
    /^# >>> ctx installer PATH setup >>>$/ {
      blocks++
      copying = 1
    }
    copying { print }
    /^# <<< ctx installer PATH setup <<<$/{ copying = 0 }
    END {
      if (blocks != 1 || copying) exit 1
    }
  ' "$candidate" >"$profile_extract"; then
    return 1
  fi
  [ "$(sha256_file "$profile_extract" | tr 'A-F' 'a-f')" = "$expected_digest" ]
}

prior_skill_matches() {
  candidate="$1"
  expected_digest="$2"
  valid_prior_owned_path "$candidate" || return 1
  case "$candidate" in
    */skills/ctx-agent-history-search) ;;
    *) return 1 ;;
  esac
  skill_body="$candidate/SKILL.md"
  skill_marker="$candidate/.ctx-skill.json"
  [ -d "$candidate" ] && [ ! -L "$candidate" ] ||
    return 1
  [ -f "$skill_body" ] && [ ! -L "$skill_body" ] ||
    return 1
  [ -f "$skill_marker" ] && [ ! -L "$skill_marker" ] ||
    return 1
  for skill_owned_file in "$skill_body" "$skill_marker"; do
    [ "$(path_owner_uid "$skill_owned_file" 2>/dev/null || true)" = "$current_uid" ] ||
      return 1
    [ "$(path_link_count "$skill_owned_file" 2>/dev/null || true)" = "1" ] ||
      return 1
  done
  skill_schema="$(json_scalar_field "$skill_marker" schema_version 2>/dev/null || true)"
  skill_installer="$(json_scalar_field "$skill_marker" installer 2>/dev/null || true)"
  skill_name="$(json_scalar_field "$skill_marker" skill_name 2>/dev/null || true)"
  skill_hash="$(json_scalar_field "$skill_marker" skill_hash 2>/dev/null || true)"
  skill_actual_sha256="$(sha256_file "$skill_body" 2>/dev/null | tr 'A-F' 'a-f' || true)"
  [ "$skill_schema" = "1" ] &&
    [ "$skill_installer" = "ctx-cli" ] &&
    [ "$skill_name" = "ctx-agent-history-search" ] &&
    [ "$skill_hash" = "sha256:$skill_actual_sha256" ] ||
    return 1
  skill_ownership_copy="$tmp_dir/prior-skill-ownership"
  cat "$skill_body" "$skill_marker" >"$skill_ownership_copy"
  [ "$(sha256_file "$skill_ownership_copy" | tr 'A-F' 'a-f')" = "$expected_digest" ]
}

prior_record_matches() {
  prior_kind="$1"
  prior_digest="$2"
  prior_target="$3"
  case "$prior_kind" in
    man)
      valid_prior_owned_path "$prior_target" || return 1
      prior_name="${prior_target##*/}"
      case "$prior_name" in ctx*.1) ;; *) return 1 ;; esac
      [ "$(canonical_file_path "$prior_target" 2>/dev/null || true)" = "$prior_target" ] ||
        return 1
      prior_regular_file_matches "$prior_target" "$prior_digest"
      ;;
    profile-file)
      valid_prior_profile_path "$prior_target" &&
        prior_regular_file_matches "$prior_target" "$prior_digest"
      ;;
    profile-block)
      prior_profile_block_matches "$prior_target" "$prior_digest"
      ;;
    skill)
      prior_skill_matches "$prior_target" "$prior_digest"
      ;;
    *) return 1 ;;
  esac
}

load_previous_integration_ownership() {
  previous_marker="$install_path.install.json"
  previous_integration_fixed="$install_path.install-integrations"
  previous_integration="$previous_integration_fixed"
  if [ -f "$previous_marker" ] && [ ! -L "$previous_marker" ]; then
    previous_path_fields="$(ownership_json_field_count "$previous_marker" integrations_path 2>/dev/null || true)"
    previous_digest_fields="$(ownership_json_field_count "$previous_marker" integrations_sha256 2>/dev/null || true)"
    case "$previous_path_fields:$previous_digest_fields" in
      0:0) ;;
      1:1)
        previous_marker_digest="$(ownership_json_string_field "$previous_marker" integrations_sha256 2>/dev/null | tr 'A-F' 'a-f' || true)"
        valid_sha256 "$previous_marker_digest" ||
          fail "prior managed integration ownership is not bound to its marker"
        previous_marker_path="$(ownership_json_string_field "$previous_marker" integrations_path 2>/dev/null || true)"
        previous_generation="$previous_integration_fixed.$previous_marker_digest"
        if [ "$previous_marker_path" = "$(json_escape "$previous_integration_fixed")" ]; then
          previous_integration="$previous_integration_fixed"
        elif [ "$previous_marker_path" = "$(json_escape "$previous_generation")" ]; then
          previous_integration="$previous_generation"
        else
          fail "prior managed integration ownership is not bound to its marker"
        fi
        ;;
      *) fail "prior managed integration ownership is not bound to its marker" ;;
    esac
  fi
  if [ "$previous_integration" = "$previous_integration_fixed" ]; then
    recover_interrupted_integration_publication "$previous_marker" "$previous_integration"
  fi
  if [ ! -e "$previous_integration" ] && [ ! -L "$previous_integration" ]; then
    if [ -f "$previous_marker" ] && [ ! -L "$previous_marker" ]; then
      missing_integration_path_fields="$(ownership_json_field_count "$previous_marker" integrations_path 2>/dev/null || true)"
      missing_integration_digest_fields="$(ownership_json_field_count "$previous_marker" integrations_sha256 2>/dev/null || true)"
      if [ "$missing_integration_path_fields" != "0" ] ||
         [ "$missing_integration_digest_fields" != "0" ]; then
        fail "prior managed integration ownership is absent"
      fi
      load_previous_man_page_receipt "$previous_marker"
    fi
    return 0
  fi

  [ -f "$install_path" ] && [ ! -L "$install_path" ] && [ -x "$install_path" ] &&
    [ -f "$previous_marker" ] && [ ! -L "$previous_marker" ] &&
    [ -f "$previous_integration" ] && [ ! -L "$previous_integration" ] ||
    fail "cannot replace invalid prior managed integration ownership"
  current_uid="$(id -u)"
  for previous_owned_path in "$install_path" "$previous_marker" "$previous_integration"; do
    [ "$(path_owner_uid "$previous_owned_path")" = "$current_uid" ] ||
      fail "prior managed install ownership is not owned by the current user"
    [ "$(path_link_count "$previous_owned_path")" = "1" ] ||
      fail "prior managed install ownership must not be hard-linked"
  done
  [ "$(path_size_bytes "$previous_marker")" -le 65536 ] &&
    [ "$(path_size_bytes "$previous_integration")" -le 1048576 ] ||
    fail "prior managed install ownership exceeds its size limit"

  [ "$(ownership_json_number_field "$previous_marker" schema_version 2>/dev/null || true)" = "1" ] ||
    fail "prior managed install marker has an invalid schema"
  [ "$(ownership_json_string_field "$previous_marker" manager 2>/dev/null || true)" = "ctx-hosted-installer" ] ||
    fail "prior managed install marker has an invalid manager"
  [ "$(ownership_json_string_field "$previous_marker" install_path 2>/dev/null || true)" = "$(json_escape "$install_path")" ] ||
    fail "prior managed install marker does not own the install path"
  [ "$(ownership_json_string_field "$previous_marker" platform 2>/dev/null || true)" = "$platform" ] ||
    fail "prior managed install marker platform does not match"
  previous_binary_digest="$(ownership_json_string_field "$previous_marker" sha256 2>/dev/null | tr 'A-F' 'a-f' || true)"
  previous_binary_actual="$(sha256_file "$install_path" | tr 'A-F' 'a-f')"
  valid_sha256 "$previous_binary_digest" &&
    { [ "$previous_binary_actual" = "$previous_binary_digest" ] ||
      { [ -n "$pair_envelope_artifact" ] &&
        [ "$previous_binary_actual" = "$(printf '%s' "$actual_checksum" | tr 'A-F' 'a-f')" ]; }; } ||
    fail "prior managed binary differs from its install marker"
  previous_version="$(ownership_json_string_field "$previous_marker" version 2>/dev/null || true)"
  case "$previous_version" in
    ""|*[!0-9A-Za-z.+-]*) fail "prior managed install marker contains an invalid version" ;;
  esac
  previous_integration_field="$(ownership_json_string_field "$previous_marker" integrations_path 2>/dev/null || true)"
  [ "$previous_integration_field" = "$(json_escape "$previous_integration")" ] ||
    fail "prior managed integration ownership is not bound to its marker"
  previous_integration_digest="$(ownership_json_string_field "$previous_marker" integrations_sha256 2>/dev/null | tr 'A-F' 'a-f' || true)"
  valid_sha256 "$previous_integration_digest" &&
    [ "$(sha256_file "$previous_integration" | tr 'A-F' 'a-f')" = "$previous_integration_digest" ] ||
    fail "prior managed integration ownership differs from its marker"
  [ "$(sed -n '1p' "$previous_integration")" = "CTX_INSTALL_INTEGRATIONS_V1" ] ||
    fail "prior managed integration ownership has an invalid schema"

  prior_records_header="$(sed -n '2p' "$previous_integration")"
  tab="$(printf '\t')"
  IFS="$tab" read -r prior_header_kind prior_records_digest prior_header_extra <<EOF
$prior_records_header
EOF
  [ "$prior_header_kind" = "records_sha256" ] &&
    valid_sha256 "$prior_records_digest" &&
    [ -z "$prior_header_extra" ] ||
    fail "prior managed integration ownership has an invalid records digest"
  prior_records_copy="$tmp_dir/prior-integration-records"
  awk 'NR > 2 { print }' "$previous_integration" >"$prior_records_copy"
  [ "$(sha256_file "$prior_records_copy" | tr 'A-F' 'a-f')" = "$prior_records_digest" ] ||
    fail "prior managed integration ownership records digest does not match"
  awk 'length($0) == 0 { exit 1 }' "$prior_records_copy" ||
    fail "prior managed integration ownership contains a noncanonical blank record"

  prior_seen_targets="$tmp_dir/prior-integration-targets"
  : >"$prior_seen_targets"
  while IFS="$tab" read -r prior_kind prior_digest prior_target prior_extra; do
    [ -n "$prior_kind$prior_digest$prior_target$prior_extra" ] || continue
    [ -z "$prior_extra" ] && valid_sha256 "$prior_digest" &&
      valid_prior_owned_path "$prior_target" ||
      fail "prior managed integration ownership contains an invalid record"
    case "$prior_kind" in
      man)
        prior_name="${prior_target##*/}"
        case "$prior_name" in
          ctx*.1) ;;
          *) fail "prior managed integration ownership contains an unsafe man-page path" ;;
        esac
        ;;
      profile-file|profile-block)
        valid_prior_profile_path "$prior_target" ||
          fail "prior managed integration ownership contains an unsafe profile path"
        ;;
      skill)
        case "$prior_target" in
          */skills/ctx-agent-history-search) ;;
          *) fail "prior managed integration ownership contains an unsafe skill path" ;;
        esac
        ;;
      *) fail "prior managed integration ownership contains an unknown record type" ;;
    esac
    prior_duplicate=0
    while IFS= read -r prior_seen_target; do
      if [ "$prior_seen_target" = "$prior_target" ]; then
        prior_duplicate=1
        break
      fi
    done <"$prior_seen_targets"
    [ "$prior_duplicate" = "0" ] ||
      fail "prior managed integration ownership contains duplicate targets"
    printf '%s\n' "$prior_target" >>"$prior_seen_targets"
    if prior_record_matches "$prior_kind" "$prior_digest" "$prior_target"; then
      printf '%s\t%s\t%s\n' "$prior_kind" "$prior_digest" "$prior_target" >>"$integration_records_tmp"
    fi
  done <"$prior_records_copy"
  integration_path="$previous_integration"
  integration_sha256="$previous_integration_digest"
  load_previous_man_page_receipt "$previous_marker"
}

skill_agent_list() {
  joined=
  old_ifs="$IFS"
  IFS='
'
  for agent in $skill_agents; do
    if [ -n "$joined" ]; then
      joined="$joined,$agent"
    else
      joined="$agent"
    fi
  done
  IFS="$old_ifs"
  printf '%s' "$joined"
}

run_skill_install() {
  set -- integrations install skills
  if [ "$all_skill_agents" = "1" ]; then
    set -- "$@" --all-agents
  elif [ -n "$skill_agents" ]; then
    old_ifs="$IFS"
    IFS='
'
    for agent in $skill_agents; do
      set -- "$@" --agent "$agent"
    done
    IFS="$old_ifs"
  fi
  set -- "$@" --format=json
  "$install_path" "$@" >"$tmp_dir/skill-install.out" 2>&1
}

record_owned_integration() {
  integration_kind="$1"
  integration_digest="$(printf '%s' "$2" | tr 'A-F' 'a-f')"
  integration_target="$3"
  case "$integration_kind" in
    man|profile-file|profile-block|skill) ;;
    *) fail "invalid installer integration ownership kind" ;;
  esac
  case "$integration_digest" in
    *[!0-9a-f]*) fail "invalid installer integration ownership digest" ;;
  esac
  [ "${#integration_digest}" -eq 64 ] ||
    fail "invalid installer integration ownership digest"
  case "$integration_target" in
    /*) ;;
    *) fail "installer integration ownership path must be absolute" ;;
  esac
  case "$integration_target" in
    *'
'*|*'	'*) fail "installer integration ownership path contains a forbidden character" ;;
  esac
  integration_merge_tmp="$tmp_dir/integration-records-merge"
  : >"$integration_merge_tmp"
  tab="$(printf '\t')"
  while IFS="$tab" read -r existing_kind existing_digest existing_target existing_extra; do
    [ -n "$existing_kind$existing_digest$existing_target$existing_extra" ] || continue
    [ -z "$existing_extra" ] ||
      fail "installer integration ownership contains an ambiguous record"
    if [ "$existing_target" = "$integration_target" ]; then
      continue
    fi
    printf '%s\t%s\t%s\n' "$existing_kind" "$existing_digest" "$existing_target" >>"$integration_merge_tmp"
  done <"$integration_records_tmp"
  printf '%s\t%s\t%s\n' "$integration_kind" "$integration_digest" "$integration_target" >>"$integration_merge_tmp"
  mv "$integration_merge_tmp" "$integration_records_tmp"
}

owned_integration_matches() {
  expected_kind="$1"
  expected_digest="$2"
  expected_target="$3"
  tab="$(printf '\t')"
  while IFS="$tab" read -r existing_kind existing_digest existing_target existing_extra; do
    [ -z "$existing_extra" ] || return 1
    if [ "$existing_kind" = "$expected_kind" ] &&
       [ "$existing_digest" = "$expected_digest" ] &&
       [ "$existing_target" = "$expected_target" ]; then
      return 0
    fi
  done <"$integration_records_tmp"
  return 1
}

stage_integration_ownership() {
  integration_path="$install_path.install-integrations"
  integration_records_sha256="$(sha256_file "$integration_records_tmp" | tr 'A-F' 'a-f')"
  {
    printf '%s\n' "CTX_INSTALL_INTEGRATIONS_V1"
    printf '%s\t%s\n' "records_sha256" "$integration_records_sha256"
    cat "$integration_records_tmp"
  } >"$integration_manifest_tmp"
  integration_sha256="$(sha256_file "$integration_manifest_tmp" | tr 'A-F' 'a-f')"
}

integration_generation_path() {
  generation_digest="$1"
  valid_sha256 "$generation_digest" || return 1
  printf '%s.%s\n' "$install_path.install-integrations" "$generation_digest"
}

valid_integration_sidecar_file() {
  sidecar_path="$1"
  sidecar_digest="$2"
  [ -f "$sidecar_path" ] && [ ! -L "$sidecar_path" ] || return 1
  [ "$(path_owner_uid "$sidecar_path" 2>/dev/null || true)" = "$(id -u)" ] || return 1
  [ "$(path_link_count "$sidecar_path" 2>/dev/null || true)" = "1" ] || return 1
  integration_sidecar_size="$(path_size_bytes "$sidecar_path" 2>/dev/null || true)"
  case "$integration_sidecar_size" in ""|*[!0-9]*) return 1 ;; esac
  [ "$integration_sidecar_size" -le 1048576 ] || return 1
  [ "$(sha256_file "$sidecar_path" 2>/dev/null | tr 'A-F' 'a-f' || true)" = "$sidecar_digest" ]
}

install_integration_sidecar_atomically() {
  sidecar_source="$1"
  sidecar_destination="$2"
  sidecar_digest="$3"
  integration_sidecar_tmp_path="$(mktemp "$sidecar_destination.tmp.XXXXXX")" ||
    fail "could not stage managed integration ownership"
  if ! install -m 0600 "$sidecar_source" "$integration_sidecar_tmp_path" ||
     ! valid_integration_sidecar_file "$integration_sidecar_tmp_path" "$sidecar_digest"; then
    rm -f "$integration_sidecar_tmp_path"
    integration_sidecar_tmp_path=
    fail "could not stage managed integration ownership"
  fi
  if ! mv -f "$integration_sidecar_tmp_path" "$sidecar_destination"; then
    rm -f "$integration_sidecar_tmp_path"
    integration_sidecar_tmp_path=
    fail "could not publish managed integration ownership"
  fi
  integration_sidecar_tmp_path=
}

ensure_integration_generation() {
  generation_source="$1"
  generation_path="$2"
  generation_digest="$3"
  if [ -e "$generation_path" ] || [ -L "$generation_path" ]; then
    valid_integration_sidecar_file "$generation_path" "$generation_digest" ||
      fail "managed integration ownership generation is invalid"
    return 0
  fi
  install_integration_sidecar_atomically "$generation_source" "$generation_path" "$generation_digest"
}

recover_interrupted_integration_publication() {
  recovery_marker="$1"
  recovery_path="$2"
  integration_recovered_generation_path=
  integration_recovered_generation_digest=
  [ -f "$recovery_marker" ] && [ ! -L "$recovery_marker" ] &&
    [ -f "$recovery_path" ] && [ ! -L "$recovery_path" ] || return 0
  recovery_actual_digest="$(sha256_file "$recovery_path" 2>/dev/null | tr 'A-F' 'a-f' || true)"
  valid_sha256 "$recovery_actual_digest" || return 0
  recovery_actual_generation="$(integration_generation_path "$recovery_actual_digest")" || return 0
  valid_integration_sidecar_file "$recovery_path" "$recovery_actual_digest" &&
    valid_integration_sidecar_file "$recovery_actual_generation" "$recovery_actual_digest" || return 0

  recovery_path_fields="$(ownership_json_field_count "$recovery_marker" integrations_path 2>/dev/null || true)"
  recovery_digest_fields="$(ownership_json_field_count "$recovery_marker" integrations_sha256 2>/dev/null || true)"
  if [ "$recovery_path_fields:$recovery_digest_fields" = "0:0" ]; then
    rm -f "$recovery_path" || fail "could not recover interrupted managed integration publication"
    integration_recovered_generation_path="$recovery_actual_generation"
    integration_recovered_generation_digest="$recovery_actual_digest"
    return 0
  fi
  [ "$recovery_path_fields:$recovery_digest_fields" = "1:1" ] || return 0
  recovery_marker_path="$(ownership_json_string_field "$recovery_marker" integrations_path 2>/dev/null || true)"
  recovery_marker_digest="$(ownership_json_string_field "$recovery_marker" integrations_sha256 2>/dev/null | tr 'A-F' 'a-f' || true)"
  [ "$recovery_marker_path" = "$(json_escape "$recovery_path")" ] &&
    valid_sha256 "$recovery_marker_digest" || return 0
  [ "$recovery_actual_digest" != "$recovery_marker_digest" ] || return 0
  recovery_marker_generation="$(integration_generation_path "$recovery_marker_digest")" || return 0
  valid_integration_sidecar_file "$recovery_marker_generation" "$recovery_marker_digest" || return 0
  install_integration_sidecar_atomically     "$recovery_marker_generation" "$recovery_path" "$recovery_marker_digest"
  integration_recovered_generation_path="$recovery_actual_generation"
  integration_recovered_generation_digest="$recovery_actual_digest"
}

publish_integration_ownership() {
  stage_integration_ownership
  integration_published_generation_path="$(integration_generation_path "$integration_sha256")" ||
    fail "could not derive managed integration ownership generation"
  integration_published_generation_digest="$integration_sha256"
  ensure_integration_generation     "$integration_manifest_tmp" "$integration_published_generation_path" "$integration_sha256"

  integration_previous_generation_path=
  integration_previous_generation_digest=
  publication_marker="$install_path.install.json"
  if [ -f "$publication_marker" ] && [ ! -L "$publication_marker" ]; then
    publication_marker_path="$(ownership_json_string_field "$publication_marker" integrations_path 2>/dev/null || true)"
    publication_marker_digest="$(ownership_json_string_field "$publication_marker" integrations_sha256 2>/dev/null | tr 'A-F' 'a-f' || true)"
    if [ "$publication_marker_path" = "$(json_escape "$integration_path")" ] &&
       valid_sha256 "$publication_marker_digest" &&
       valid_integration_sidecar_file "$integration_path" "$publication_marker_digest"; then
      integration_previous_generation_path="$(integration_generation_path "$publication_marker_digest")" ||
        fail "could not derive prior managed integration ownership generation"
      integration_previous_generation_digest="$publication_marker_digest"
      ensure_integration_generation         "$integration_path" "$integration_previous_generation_path" "$publication_marker_digest"
    fi
  fi
  if [ -e "$integration_path" ] || [ -L "$integration_path" ]; then
    [ -n "$integration_previous_generation_path" ] ||
      fail "managed integration ownership destination is not bound to its marker"
  fi
  install_integration_sidecar_atomically     "$integration_published_generation_path" "$integration_path" "$integration_sha256"
}

cleanup_published_integration_generations() {
  for cleanup_generation_role in previous published recovered; do
    case "$cleanup_generation_role" in
      previous)
        cleanup_generation_path="${integration_previous_generation_path:-}"
        cleanup_generation_digest="${integration_previous_generation_digest:-}"
        ;;
      published)
        cleanup_generation_path="${integration_published_generation_path:-}"
        cleanup_generation_digest="${integration_published_generation_digest:-}"
        ;;
      recovered)
        cleanup_generation_path="${integration_recovered_generation_path:-}"
        cleanup_generation_digest="${integration_recovered_generation_digest:-}"
        ;;
    esac
    [ -n "$cleanup_generation_path" ] &&
      valid_integration_sidecar_file "$cleanup_generation_path" "$cleanup_generation_digest" || continue
    rm -f "$cleanup_generation_path" 2>/dev/null || :
  done
}

json_result_paths() {
  awk '
    BEGIN {
      quote = sprintf("%c", 34)
      backslash = sprintf("%c", 92)
    }
    {
      input = input $0 "\n"
    }
    function parse_string(start, i, character, escaped) {
      if (substr(input, start, 1) != quote) return 0
      parsed_value = ""
      escaped = 0
      for (i = start + 1; i <= length(input); i++) {
        character = substr(input, i, 1)
        if (escaped) {
          if (character == quote || character == backslash || character == "/") {
            parsed_value = parsed_value character
          } else if (character == "b") {
            parsed_value = parsed_value sprintf("%c", 8)
          } else if (character == "f") {
            parsed_value = parsed_value sprintf("%c", 12)
          } else if (character == "n") {
            return 0
          } else if (character == "r") {
            return 0
          } else if (character == "t") {
            return 0
          } else {
            return 0
          }
          escaped = 0
        } else if (character == backslash) {
          escaped = 1
        } else if (character == quote) {
          parsed_end = i
          return 1
        } else {
          parsed_value = parsed_value character
        }
      }
      return 0
    }
    END {
      i = 1
      while (i <= length(input)) {
        if (substr(input, i, 1) != quote) {
          i++
          continue
        }
        if (!parse_string(i)) exit 1
        key = parsed_value
        i = parsed_end + 1
        j = i
        while (substr(input, j, 1) ~ /[[:space:]]/) j++
        if (key != "path" || substr(input, j, 1) != ":") continue
        j++
        while (substr(input, j, 1) ~ /[[:space:]]/) j++
        if (!parse_string(j)) exit 1
        print parsed_value
        i = parsed_end + 1
      }
    }
  ' "$1"
}

record_installed_skills() {
  skill_paths_file="$tmp_dir/skill-paths"
  if ! json_result_paths "$tmp_dir/skill-install.out" >"$skill_paths_file"; then
    return 1
  fi
  while IFS= read -r skill_path; do
    [ -n "$skill_path" ] || continue
    case "$skill_path" in
      /*/skills/ctx-agent-history-search) ;;
      *) continue ;;
    esac
    skill_body="$skill_path/SKILL.md"
    skill_marker="$skill_path/.ctx-skill.json"
    [ -d "$skill_path" ] && [ ! -L "$skill_path" ] ||
      continue
    [ -f "$skill_body" ] && [ ! -L "$skill_body" ] ||
      continue
    [ -f "$skill_marker" ] && [ ! -L "$skill_marker" ] ||
      continue
    skill_schema="$(json_scalar_field "$skill_marker" schema_version 2>/dev/null || true)"
    skill_installer="$(json_scalar_field "$skill_marker" installer 2>/dev/null || true)"
    skill_name="$(json_scalar_field "$skill_marker" skill_name 2>/dev/null || true)"
    skill_hash="$(json_scalar_field "$skill_marker" skill_hash 2>/dev/null || true)"
    skill_actual_sha256="$(sha256_file "$skill_body" | tr 'A-F' 'a-f')"
    if [ "$skill_schema" = "1" ] &&
       [ "$skill_installer" = "ctx-cli" ] &&
       [ "$skill_name" = "ctx-agent-history-search" ] &&
       [ "$skill_hash" = "sha256:$skill_actual_sha256" ]; then
      skill_ownership_copy="$tmp_dir/skill-ownership"
      cat "$skill_body" "$skill_marker" >"$skill_ownership_copy"
      record_owned_integration skill "$(sha256_file "$skill_ownership_copy")" "$skill_path"
    fi
  done <"$skill_paths_file"
}

man_page_receipt_directory() {
  case "$man_dir" in
    /*) printf '%s' "$man_dir" ;;
    *) printf '%s/%s' "$(pwd -P)" "$man_dir" ;;
  esac
}

owner_safe_man_directory() {
  candidate="$1"
  [ "$(path_owner_uid "$candidate" 2>/dev/null || true)" = "$(id -u)" ] || return 1
  candidate_mode="$(stat -c '%a' "$candidate" 2>/dev/null || stat -f '%Lp' "$candidate" 2>/dev/null)" || return 1
  case "$candidate_mode" in *[!0-7]*) return 1 ;; esac
  [ "${#candidate_mode}" -ge 3 ] || return 1
  other_mode="${candidate_mode#"${candidate_mode%?}"}"
  group_modes="${candidate_mode%?}"
  group_mode="${group_modes#"${group_modes%?}"}"
  case "$group_mode$other_mode" in *[2367]*) return 1 ;; esac
}

record_owned_man_page() {
  man_page_name="$1"
  man_page_digest="$(printf '%s' "$2" | tr 'A-F' 'a-f')"
  case "$man_page_name" in
    ctx*.1) ;;
    *) fail "invalid installer-owned man page name" ;;
  esac
  case "$man_page_name" in
    *[!A-Za-z0-9._-]*) fail "invalid installer-owned man page name" ;;
  esac
  case "$man_page_digest" in
    *[!0-9a-f]*) fail "invalid installer-owned man page digest" ;;
  esac
  [ "${#man_page_digest}" -eq 64 ] ||
    fail "invalid installer-owned man page digest"
  printf '%s\t%s\n' "$man_page_name" "$man_page_digest" >>"$man_page_records_tmp"
  man_page_owned_count="$((man_page_owned_count + 1))"
}

build_installed_man_page_receipt() {
  man_page_directory="$(man_page_receipt_directory)"
  man_page_sorted_records="$tmp_dir/install-man-page-records-sorted"
  LC_ALL=C sort "$man_page_records_tmp" >"$man_page_sorted_records"
  man_page_files='['
  man_page_separator=
  tab="$(printf '\t')"
  while IFS="$tab" read -r man_page_name man_page_digest man_page_extra; do
    [ -n "$man_page_name$man_page_digest$man_page_extra" ] || continue
    [ -z "$man_page_extra" ] || fail "ambiguous installer-owned man page record"
    man_page_files="$man_page_files$man_page_separator{\"name\":\"$(json_escape "$man_page_name")\",\"sha256\":\"$man_page_digest\"}"
    man_page_separator=,
  done <"$man_page_sorted_records"
  man_page_files="$man_page_files]"
  man_pages_json='{"schema_version":1,"status":"installed","directory":"'"$(json_escape "$man_page_directory")"'","files":'"$man_page_files"',"binary_sha256":"'"$(printf '%s' "$actual_checksum" | tr 'A-F' 'a-f')"'"}'
  man_pages_receipt_present=1
}

initialize_man_page_receipt() {
  man_pages_json=null
  man_pages_receipt_present=0
  if [ "$install_man" != "1" ]; then
    man_pages_json='{"schema_version":1,"status":"disabled"}'
    man_pages_receipt_present=1
  fi
}

ownership_json_object_field() {
  json_path="$1"
  json_field="$2"
  awk -v field="$json_field" '
    BEGIN {
      quote = sprintf("%c", 34)
      backslash = sprintf("%c", 92)
    }
    function update_depth(text, i, character, escaped) {
      escaped = 0
      for (i = 1; i <= length(text); i++) {
        character = substr(text, i, 1)
        if (in_string) {
          if (escaped) escaped = 0
          else if (character == backslash) escaped = 1
          else if (character == quote) in_string = 0
        } else if (character == quote) in_string = 1
        else if (character == "{") depth++
        else if (character == "}") depth--
      }
    }
    collecting {
      result = result ORS $0
      update_depth($0)
      if (depth == 0) collecting = 0
      if (depth < 0) invalid = 1
      next
    }
    $0 ~ "^  \"" field "\"[[:space:]]*:" {
      count++
      value = $0
      sub(/^[^:]*:[[:space:]]*/, "", value)
      first = value
      sub(/^[[:space:]]*/, "", first)
      if (substr(first, 1, 1) != "{") invalid = 1
      result = value
      update_depth(value)
      if (depth > 0) collecting = 1
      if (depth < 0) invalid = 1
      next
    }
    END {
      if (count != 1 || collecting || depth != 0 || in_string || invalid) exit 1
      sub(/[[:space:]]*,[[:space:]]*$/, "", result)
      print result
    }
  ' "$json_path"
}

load_previous_man_page_receipt() {
  previous_man_page_marker="$1"
  previous_man_pages_count="$(ownership_json_field_count "$previous_man_page_marker" man_pages 2>/dev/null || true)"
  case "$previous_man_pages_count" in
    0) return 0 ;;
    1) ;;
    *) fail "prior managed install marker has ambiguous man-page ownership" ;;
  esac
  previous_man_pages_path="$tmp_dir/prior-man-pages.json"
  if ! ownership_json_object_field "$previous_man_page_marker" man_pages >"$previous_man_pages_path" ||
     ! json_document_is_well_formed "$previous_man_pages_path"; then
    fail "prior managed install marker has invalid man-page ownership"
  fi
  man_pages_json="$(cat "$previous_man_pages_path")"
  man_pages_receipt_present=1
}
install_stage_delivery_enabled=1
report_install_stage() {
  report_stage="$1"
  report_status="$2"
  [ "$install_stage_delivery_enabled" = "1" ] || return 0
  canonical_analytics_disabled && return 0
  command -v curl >/dev/null 2>&1 || return 0
  case "$install_telemetry_endpoint" in
    https://*) ;;
    *) return 0 ;;
  esac

  payload='{"event_name":"install_stage","event_version":1,"install_attempt_id":"'"$(json_escape "$install_attempt_id")"'","stage":"'"$(json_escape "$report_stage")"'","status":"'"$(json_escape "$report_status")"'","platform":"'"$(json_escape "$telemetry_platform")"'","arch":"'"$(json_escape "$telemetry_arch")"'","script_family":"posix"}'
  if ! curl -fsS --connect-timeout 1 --max-time 1 -H "content-type: application/json" -X POST --data "$payload" "$install_telemetry_endpoint" >/dev/null 2>&1; then
    install_stage_delivery_enabled=0
  fi
  return 0
}

apply_deprecated_controls
load_persisted_config_controls

if [ "${CTX_INSTALL_NO_SETUP:-0}" = "1" ]; then
  run_setup=0
fi

if [ "${CTX_INSTALL_NO_DAEMON:-0}" = "1" ]; then
  setup_no_daemon=1
fi

case "${CTX_INSTALL_PRO_TRIAL-}" in
  ""|0) ;;
  1) pro_trial_accept_requested=1 ;;
  *) fail "CTX_INSTALL_PRO_TRIAL must be 0 or 1" ;;
esac

case "${CTX_INSTALL_NO_PRO_TRIAL-}" in
  ""|0) ;;
  1) pro_trial_skip_requested=1 ;;
  *) fail "CTX_INSTALL_NO_PRO_TRIAL must be 0 or 1" ;;
esac

if [ "$pro_trial_accept_requested" = "1" ] &&
   [ "$pro_trial_skip_requested" = "1" ]; then
  fail "cannot combine ctx pro trial accept and skip controls"
fi

if [ "$pro_trial_accept_requested" = "1" ] &&
   [ "$pro_trial_offer_enabled" != "1" ]; then
  fail "ctx pro trials are not available from this installer"
fi

if [ "$pro_trial_accept_requested" = "1" ] && [ "$run_setup" != "1" ]; then
  fail "cannot start a ctx pro trial when setup is disabled"
fi

case "${CTX_INSTALL_SEMANTIC:-0}" in
  0|""|[Ff][Aa][Ll][Ss][Ee]|[Nn][Oo]|[Oo][Ff][Ff]) ;;
  1|[Tt][Rr][Uu][Ee]|[Yy][Ee][Ss]|[Oo][Nn]) semantic_enabled=1 ;;
  *) fail "CTX_INSTALL_SEMANTIC must be a canonical boolean" ;;
esac

semantic_search_control=unset
if [ "${CTX_SEARCH_SEMANTIC+x}" = "x" ]; then
  if semantic_search_control_value="$(
    printf '%s' "$CTX_SEARCH_SEMANTIC" |
      LC_ALL=C awk '
          function trim(value, before, position, width) {
            while (1) {
              before = value
              sub(/^[[:space:]]+/, "", value)
              for (position = 1; position <= unicode_space_count; position += 1) {
                width = length(unicode_space[position])
                if (substr(value, 1, width) == unicode_space[position]) {
                  value = substr(value, width + 1)
                  break
                }
              }
              if (value == before) break
            }
            while (1) {
              before = value
              sub(/[[:space:]]+$/, "", value)
              for (position = 1; position <= unicode_space_count; position += 1) {
                width = length(unicode_space[position])
                if (substr(value, length(value) - width + 1) == unicode_space[position]) {
                  value = substr(value, 1, length(value) - width)
                  break
                }
              }
              if (value == before) break
            }
            return value
          }
          function continuation(byte) {
            return byte >= 128 && byte <= 191
          }
          function valid_utf8(value, position, value_length, first, second, third, fourth) {
            value_length = length(value)
            position = 1
            while (position <= value_length) {
              first = byte_value[substr(value, position, 1)]
              if (first <= 127) {
                position += 1
              } else if (first >= 194 && first <= 223) {
                if (position + 1 > value_length) return 0
                second = byte_value[substr(value, position + 1, 1)]
                if (!continuation(second)) return 0
                position += 2
              } else if (first == 224) {
                if (position + 2 > value_length) return 0
                second = byte_value[substr(value, position + 1, 1)]
                third = byte_value[substr(value, position + 2, 1)]
                if (second < 160 || second > 191 || !continuation(third)) return 0
                position += 3
              } else if ((first >= 225 && first <= 236) ||
                         (first >= 238 && first <= 239)) {
                if (position + 2 > value_length) return 0
                second = byte_value[substr(value, position + 1, 1)]
                third = byte_value[substr(value, position + 2, 1)]
                if (!continuation(second) || !continuation(third)) return 0
                position += 3
              } else if (first == 237) {
                if (position + 2 > value_length) return 0
                second = byte_value[substr(value, position + 1, 1)]
                third = byte_value[substr(value, position + 2, 1)]
                if (second < 128 || second > 159 || !continuation(third)) return 0
                position += 3
              } else if (first == 240) {
                if (position + 3 > value_length) return 0
                second = byte_value[substr(value, position + 1, 1)]
                third = byte_value[substr(value, position + 2, 1)]
                fourth = byte_value[substr(value, position + 3, 1)]
                if (second < 144 || second > 191 ||
                    !continuation(third) || !continuation(fourth)) return 0
                position += 4
              } else if (first >= 241 && first <= 243) {
                if (position + 3 > value_length) return 0
                second = byte_value[substr(value, position + 1, 1)]
                third = byte_value[substr(value, position + 2, 1)]
                fourth = byte_value[substr(value, position + 3, 1)]
                if (!continuation(second) ||
                    !continuation(third) || !continuation(fourth)) return 0
                position += 4
              } else if (first == 244) {
                if (position + 3 > value_length) return 0
                second = byte_value[substr(value, position + 1, 1)]
                third = byte_value[substr(value, position + 2, 1)]
                fourth = byte_value[substr(value, position + 3, 1)]
                if (second < 128 || second > 143 ||
                    !continuation(third) || !continuation(fourth)) return 0
                position += 4
              } else {
                return 0
              }
            }
            return 1
          }
          BEGIN {
            for (byte = 0; byte <= 255; byte += 1) {
              byte_value[sprintf("%c", byte)] = byte
            }
            unicode_space[++unicode_space_count] = sprintf("%c%c", 194, 133)
            unicode_space[++unicode_space_count] = sprintf("%c%c", 194, 160)
            unicode_space[++unicode_space_count] = sprintf("%c%c%c", 225, 154, 128)
            for (byte = 128; byte <= 138; byte += 1) {
              unicode_space[++unicode_space_count] = sprintf("%c%c%c", 226, 128, byte)
            }
            unicode_space[++unicode_space_count] = sprintf("%c%c%c", 226, 128, 168)
            unicode_space[++unicode_space_count] = sprintf("%c%c%c", 226, 128, 169)
            unicode_space[++unicode_space_count] = sprintf("%c%c%c", 226, 128, 175)
            unicode_space[++unicode_space_count] = sprintf("%c%c%c", 226, 129, 159)
            unicode_space[++unicode_space_count] = sprintf("%c%c%c", 227, 128, 128)
          }
          {
            if (!valid_utf8($0)) {
              invalid = 1
              exit 2
            }
            if (NR > 1) value = value "\n"
            value = value $0
          }
          END {
            if (!invalid) printf "%s", trim(value)
          }
        '
  )"; then
    :
  else
    semantic_search_control_value=
  fi
  while :; do
    case "$semantic_search_control_value" in
      \"*) semantic_search_control_value="${semantic_search_control_value#?}" ;;
      *) break ;;
    esac
  done
  while :; do
    case "$semantic_search_control_value" in
      *\") semantic_search_control_value="${semantic_search_control_value%?}" ;;
      *) break ;;
    esac
  done
  case "$semantic_search_control_value" in
    "")
      ;;
    0|[Ff][Aa][Ll][Ss][Ee]|[Nn][Oo]|[Oo][Ff][Ff])
      semantic_search_control=false
      ;;
    1|[Tt][Rr][Uu][Ee]|[Yy][Ee][Ss]|[Oo][Nn])
      semantic_search_control=true
      semantic_enabled=1
      ;;
    *) fail "CTX_SEARCH_SEMANTIC must be a canonical boolean" ;;
  esac
fi

if [ "$semantic_enabled" != "1" ] &&
   [ "$semantic_search_control" != "false" ] &&
   [ "$persisted_semantic_enabled" = "1" ]; then
  semantic_enabled=1
fi

daemon_configuration_disabled=0
if canonical_daemon_disabled || [ "$persisted_daemon_disabled" = "1" ]; then
  daemon_configuration_disabled=1
fi
daemon_enabled=1
if [ "$setup_no_daemon" = "1" ] || [ "$daemon_configuration_disabled" = "1" ]; then
  daemon_enabled=0
fi

if [ "$semantic_enabled" = "1" ] && [ "$daemon_enabled" != "1" ]; then
  fail "Semantic installation requires an enabled daemon; remove installer no-daemon controls, clear daemon-disable environment controls, or set [daemon] enabled = true"
fi

if [ "$pro_trial_accept_requested" = "1" ]; then
  pro_trial_selection=compatibility
elif [ "$pro_trial_skip_requested" = "1" ] || [ "$run_setup" != "1" ]; then
  pro_trial_selection=skip
elif [ "$pro_trial_offer_enabled" = "1" ] &&
     [ "$daemon_enabled" = "1" ] &&
     ! ci_environment && has_controlling_tty; then
  pro_trial_selection=automatic
fi

stage_install_marker() {
  marker_path="$install_path.install.json"
  installed_at="$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
  marker_tmp_pattern="${1:-$marker_path.tmp.XXXXXX}"
  marker_tmp_path="$(mktemp "$marker_tmp_pattern")" ||
    fail "could not create managed install marker"
  if ! chmod 0600 "$marker_tmp_path"; then
    rm -f "$marker_tmp_path"
    marker_tmp_path=
    fail "could not secure managed install marker"
  fi
  marker_man_pages_line=
  if [ "$man_pages_receipt_present" = "1" ] || [ "${2:-0}" = "1" ]; then
    marker_man_pages_line=',
  "man_pages": '"$man_pages_json"
  fi
  marker_integrations_lines=
  if [ "${integration_path+x}" = "x" ] || [ "${integration_sha256+x}" = "x" ]; then
    [ -n "${integration_path:-}" ] && [ -n "${integration_sha256:-}" ] || {
      rm -f "$marker_tmp_path"
      marker_tmp_path=
      fail "managed integration ownership is incomplete"
    }
    marker_integrations_lines=',
  "integrations_path": "'"$(json_escape "$integration_path")"'",
  "integrations_sha256": "'"$(json_escape "$integration_sha256")"'"'
  fi
  marker_pair_line=
  if [ -n "${pair_envelope_artifact:-}" ]; then
    marker_pair_line=',
  "managed_pair": true'
  fi
  if ! cat >"$marker_tmp_path" <<EOF
{
  "schema_version": 1,
  "manager": "ctx-hosted-installer",
  "install_attempt_id": "$(json_escape "$install_attempt_id")",
  "install_path": "$(json_escape "$install_path")",
  "platform": "$(json_escape "$platform")",
  "channel": "$(json_escape "$release_channel")",
  "version": "$(json_escape "$version")",
  "sha256": "$(json_escape "$actual_checksum")",
  "metadata_url": "$(json_escape "$metadata_url")",
  "artifact_url": "$(json_escape "$artifact_url")",
  "source_commit": "$(json_escape "$source_commit")",
  "published_at": "$(json_escape "$published_at")",
  "installed_at": "$(json_escape "$installed_at")"$marker_pair_line$marker_man_pages_line$marker_integrations_lines
}
EOF
  then
    rm -f "$marker_tmp_path"
    marker_tmp_path=
    fail "could not write managed install marker"
  fi
}

write_install_marker() {
  stage_install_marker "$marker_path.tmp.XXXXXX"
  if [ -L "$marker_path" ] ||
     { [ -e "$marker_path" ] && [ ! -f "$marker_path" ]; }; then
    rm -f "$marker_tmp_path"
    marker_tmp_path=
    fail "managed install marker destination is not a regular file"
  fi
  if ! mv -f "$marker_tmp_path" "$marker_path"; then
    rm -f "$marker_tmp_path"
    marker_tmp_path=
    fail "could not publish managed install marker"
  fi
  marker_tmp_path=
}
disable_core_man_pages_before_upgrade() {
  preupgrade_marker="$install_path.install.json"
  if ! awk '
    /^  "man_pages"[[:space:]]*:/ { found = 1 }
    END { exit(found ? 0 : 1) }
  ' "$preupgrade_marker"; then
    return 0
  fi
  "$install_path" --ctx-core-disable-managed-man-pages-v1 ||
    fail "installed ctx could not disable automatic man-page refresh"
}
validate_existing_managed_install() {
  previous_marker="$install_path.install.json"
  [ -f "$install_path" ] && [ ! -L "$install_path" ] && [ -x "$install_path" ] ||
    fail "an existing ctx install must be a regular executable owned by the hosted installer"
  [ -f "$previous_marker" ] && [ ! -L "$previous_marker" ] ||
    fail "an existing ctx install requires its regular hosted-install marker"
  current_uid="$(id -u)"
  for previous_owned_path in "$install_path" "$previous_marker"; do
    [ "$(path_owner_uid "$previous_owned_path" 2>/dev/null || true)" = "$current_uid" ] ||
      fail "prior managed install ownership is not owned by the current user"
    [ "$(path_link_count "$previous_owned_path" 2>/dev/null || true)" = "1" ] ||
      fail "prior managed install ownership must not be hard-linked"
  done
  previous_marker_size="$(path_size_bytes "$previous_marker")" ||
    fail "could not determine prior managed install marker size"
  [ "$previous_marker_size" -ge 2 ] && [ "$previous_marker_size" -le 65536 ] ||
    fail "prior managed install marker has an invalid size"
  [ "$(ownership_json_number_field "$previous_marker" schema_version 2>/dev/null || true)" = "1" ] ||
    fail "prior managed install marker has an invalid schema"
  [ "$(ownership_json_string_field "$previous_marker" manager 2>/dev/null || true)" = "ctx-hosted-installer" ] ||
    fail "prior managed install marker has an invalid manager"
  [ "$(ownership_json_string_field "$previous_marker" install_path 2>/dev/null || true)" = "$(json_escape "$install_path")" ] ||
    fail "prior managed install marker does not own the install path"
  [ "$(ownership_json_string_field "$previous_marker" platform 2>/dev/null || true)" = "$platform" ] ||
    fail "prior managed install marker platform does not match"
  previous_binary_digest="$(ownership_json_string_field "$previous_marker" sha256 2>/dev/null | tr 'A-F' 'a-f' || true)"; previous_binary_actual="$(sha256_file "$install_path" | tr 'A-F' 'a-f')"
  valid_sha256 "$previous_binary_digest" && { [ "$previous_binary_actual" = "$previous_binary_digest" ] || { [ -n "$pair_envelope_artifact" ] &&
      [ "$previous_binary_actual" = "$(printf '%s' "$actual_checksum" | tr 'A-F' 'a-f')" ]; }; } ||
    fail "prior managed binary differs from its install marker"
  previous_version="$(ownership_json_string_field "$previous_marker" version 2>/dev/null || true)"
  case "$previous_version" in
    ""|*[!0-9A-Za-z.+-]*) fail "prior managed install marker contains an invalid version" ;;
  esac
}

previous_install_predates_persistent_daemon() {
  previous_version_core="${previous_version%%[-+]*}"
  case "$previous_version_core" in
    *.*.*) ;;
    *) return 1 ;;
  esac
  previous_version_major="${previous_version_core%%.*}"
  previous_version_remainder="${previous_version_core#*.}"
  previous_version_minor="${previous_version_remainder%%.*}"
  case "$previous_version_major:$previous_version_minor" in
    *[!0-9:]*|:|*:) return 1 ;;
  esac
  [ "$previous_version_major" -eq 0 ] && [ "$previous_version_minor" -le 25 ]
}

managed_pair_requires_candidate_apply() {
  previous_install_predates_persistent_daemon && return 0
  candidate_digest="$(printf '%s' "$actual_checksum" | tr 'A-F' 'a-f')"
  [ "$previous_binary_digest" != "$previous_binary_actual" ] &&
    [ "$previous_binary_actual" = "$candidate_digest" ]
}

validate_managed_upgrade_result() {
  upgrade_result_path="$1"
  expected_install_path="$(json_escape "$install_path")"
  awk     -v expected_version="$version"     -v expected_channel="$channel"     -v expected_platform="$platform"     -v expected_install_path="$expected_install_path" '
    function trim(value) {
      sub(/^[[:space:]]+/, "", value)
      sub(/[[:space:]]+$/, "", value)
      return value
    }
    function update_depth(text, position, character, escaped) {
      escaped = 0
      for (position = 1; position <= length(text); position++) {
        character = substr(text, position, 1)
        if (in_string) {
          if (escaped) {
            escaped = 0
          } else if (character == backslash) {
            escaped = 1
          } else if (character == quote) {
            in_string = 0
          }
        } else if (character == quote) {
          in_string = 1
        } else if (character == "{") {
          depth++
        } else if (character == "}") {
          depth--
          if (depth < 0) invalid = 1
        }
      }
    }
    BEGIN {
      quote = sprintf("%c", 34)
      backslash = sprintf("%c", 92)
      required["schema_version"] = 1
      required["command"] = 1
      required["ok"] = 1
      required["status"] = 1
      required["message"] = 1
      required["current_version"] = 1
      required["latest_version"] = 1
      required["update_available"] = 1
      required["update_was_available"] = 1
      required["channel"] = 1
      required["platform"] = 1
      required["metadata_url"] = 1
      required["artifact_url"] = 1
      required["install_path"] = 1
      required["managed"] = 1
      required["applied"] = 1
      required["dry_run"] = 1
      required["warnings"] = 1
      required["upgrade_attempt_id"] = 1
      required_count = 19
    }
    {
      line = trim($0)
      before = depth
      if (before == 1 && line ~ /^"[A-Za-z_]+"[[:space:]]*:/) {
        key = line
        sub(/^"/, "", key)
        sub(/".*$/, "", key)
        value = line
        sub(/^[^:]*:[[:space:]]*/, "", value)
        sub(/,[[:space:]]*$/, "", value)
        value = trim(value)
        if (!(key in required) || seen[key]) {
          invalid = 1
        } else {
          seen[key] = 1
          seen_count++
          if (key == "schema_version" && value != "1") invalid = 1
          else if (key == "command" && value != quote "upgrade" quote) invalid = 1
          else if (key == "ok" && value != "true") invalid = 1
          else if (key == "status") {
            status = value
            if (status != quote "applied" quote &&
                status != quote "up_to_date" quote) invalid = 1
          } else if (key == "message" && value !~ /^".*"$/) invalid = 1
          else if (key == "current_version" &&
                   value != quote expected_version quote) invalid = 1
          else if (key == "latest_version" &&
                   value != quote expected_version quote) invalid = 1
          else if ((key == "update_available" || key == "update_was_available") &&
                   value != "true" && value != "false") invalid = 1
          else if (key == "channel" &&
                   value != quote expected_channel quote) invalid = 1
          else if (key == "platform" &&
                   value != quote expected_platform quote) invalid = 1
          else if ((key == "metadata_url" || key == "artifact_url") &&
                   value !~ /^".*"$/) invalid = 1
          else if (key == "install_path" &&
                   value != quote expected_install_path quote) invalid = 1
          else if (key == "managed" && value != "true") invalid = 1
          else if (key == "applied") applied = value
          else if (key == "dry_run" && value != "false") invalid = 1
          else if (key == "warnings" && value != "[]") invalid = 1
          else if (key == "upgrade_attempt_id" &&
                   value != "null" && value !~ /^"[^"]+"$/) invalid = 1
        }
      }
      update_depth(line)
    }
    END {
      if (in_string || depth != 0 || invalid || seen_count != required_count) exit 1
      for (key in required) {
        if (!seen[key]) exit 1
      }
      if (status == quote "applied" quote && applied != "true") exit 1
      if (status == quote "up_to_date" quote && applied != "false") exit 1
    }
  ' "$upgrade_result_path"
}

verify_installed_target_identity() {
  target_marker="$install_path.install.json"
  [ -f "$install_path" ] && [ ! -L "$install_path" ] && [ -x "$install_path" ] ||
    fail "managed ctx upgrade did not retain a regular executable"
  [ -f "$target_marker" ] && [ ! -L "$target_marker" ] ||
    fail "managed ctx upgrade did not retain its regular install marker"
  [ "$(path_link_count "$install_path" 2>/dev/null || true)" = "1" ] &&
    [ "$(path_link_count "$target_marker" 2>/dev/null || true)" = "1" ] ||
    fail "managed ctx upgrade produced a hard-linked install identity"
  [ "$(sha256_file "$install_path" | tr 'A-F' 'a-f')" = "$(printf '%s' "$actual_checksum" | tr 'A-F' 'a-f')" ] ||
    fail "managed ctx upgrade did not publish the signed executable"
  [ "$(ownership_json_number_field "$target_marker" schema_version 2>/dev/null || true)" = "1" ] &&
    [ "$(ownership_json_string_field "$target_marker" manager 2>/dev/null || true)" = "ctx-hosted-installer" ] &&
    [ "$(ownership_json_string_field "$target_marker" install_path 2>/dev/null || true)" = "$(json_escape "$install_path")" ] &&
    [ "$(ownership_json_string_field "$target_marker" platform 2>/dev/null || true)" = "$platform" ] &&
    [ "$(ownership_json_string_field "$target_marker" version 2>/dev/null || true)" = "$version" ] &&
    [ "$(ownership_json_string_field "$target_marker" sha256 2>/dev/null | tr 'A-F' 'a-f' || true)" = "$(printf '%s' "$actual_checksum" | tr 'A-F' 'a-f')" ] ||
    fail "managed ctx upgrade returned a mismatched install marker"
  if [ "$release_phase" = "final" ] && [ -n "$pair_envelope_artifact" ]; then
    [ "$(json_top_level_boolean_field "$target_marker" managed_pair 2>/dev/null || true)" = "true" ] ||
      fail "managed ctx did not complete the signed Core/companion pair"
  fi
}

run_managed_core_upgrade() {
  # Clear progress in the parent shell before the isolated handoff can fail.
  stop_install_animation
  (
  if [ "$release_phase" = "bridge" ]; then
    CTX_SEARCH_SEMANTIC=0
    export CTX_SEARCH_SEMANTIC
  fi
  upgrade_metadata_uri="$(absolute_path_to_file_uri "$metadata_file")"
  upgrade_signature_uri="$(absolute_path_to_file_uri "$metadata_signature_file")"
  managed_upgrade_output="$tmp_dir/managed-upgrade.json"
  managed_upgrade_error="$tmp_dir/managed-upgrade.err"
  if CTX_RELEASE_METADATA_URL="$upgrade_metadata_uri"      CTX_RELEASE_METADATA_SIGNATURE_URL="$upgrade_signature_uri"      "$install_path" upgrade --channel "$channel" --format=json        >"$managed_upgrade_output" 2>"$managed_upgrade_error"; then
    :
  else
    managed_upgrade_status="$?"
    relay_bounded_child_stderr "$managed_upgrade_error"
    fail "installed ctx could not complete its managed lifecycle handoff (status $managed_upgrade_status); resolve the reported error before retrying"
  fi
  managed_upgrade_size="$(path_size_bytes "$managed_upgrade_output")" ||
    fail "could not determine managed ctx upgrade receipt size"
  [ "$managed_upgrade_size" -ge 2 ] && [ "$managed_upgrade_size" -le 65536 ] ||
    fail "managed ctx upgrade returned an invalid receipt; rerun this installer to finish any retained attempt"
  validate_managed_upgrade_result "$managed_upgrade_output" ||
    fail "managed ctx upgrade did not return typed lifecycle proof; rerun this installer to finish any retained attempt"
  verify_installed_target_identity
  )
}

publish_fresh_or_legacy_binary() {
  chmod 0700 "$artifact_path" || fail "could not prepare the verified ctx candidate"
  hosted_transaction_output="$tmp_dir/hosted-install-transaction.json"
  if ! "$artifact_path" upgrade     --hosted-transaction install     --install-path "$install_path"     --attempt-id "$install_attempt_id"     --marker-source "$marker_tmp_path"     --ownership-source "$integration_manifest_tmp"     --binary-sha256 "$actual_checksum"     >"$hosted_transaction_output"; then
    fail "ctx could not complete its crash-recoverable hosted install transaction"
  fi
  [ "$(ownership_json_number_field "$hosted_transaction_output" schema_version 2>/dev/null || true)" = "1" ] &&
    [ "$(ownership_json_string_field "$hosted_transaction_output" command 2>/dev/null || true)" = "hosted_install_transaction" ] &&
    [ "$(ownership_json_string_field "$hosted_transaction_output" status 2>/dev/null || true)" = "committed" ] &&
    [ "$(ownership_json_string_field "$hosted_transaction_output" install_path 2>/dev/null || true)" = "$(json_escape "$install_path")" ] &&
    [ "$(ownership_json_string_field "$hosted_transaction_output" binary_sha256 2>/dev/null | tr 'A-F' 'a-f' || true)" = "$(printf '%s' "$actual_checksum" | tr 'A-F' 'a-f')" ] ||
    fail "ctx returned invalid hosted install transaction proof"
}

platform="${CTX_PLATFORM:-}"
if [ -z "$platform" ]; then
  host_os="$(uname -s 2>/dev/null || printf unknown)"
  if [ "$host_os" = "FreeBSD" ]; then
    fail "FreeBSD has no prebuilt ctx binary; build ctx from source: https://github.com/ctxrs/ctx/blob/main/docs/unmanaged-installs.md#source-builds"
  fi
  platform="$(detect_platform)" || fail "cannot detect this host platform; set CTX_PLATFORM"
fi

case "$platform" in
  linux-x64|linux-aarch64|macos-arm64|macos-x64) ;;
  *) fail "unsupported platform: $platform" ;;
esac

case "$platform" in
  linux-*) telemetry_platform="linux" ;;
  macos-*) telemetry_platform="macos" ;;
esac
case "$platform" in
  *-x64) telemetry_arch="x64" ;;
  *-aarch64|*-arm64) telemetry_arch="arm64" ;;
esac

report_install_stage "installer" "started"

tmp_dir="$(mktemp -d "${TMPDIR:-/tmp}/ctx-cli-install.XXXXXX")"
tmp_dir="$(cd "$tmp_dir" && pwd -P)" || fail "could not resolve temporary directory"
integration_manifest_tmp="$tmp_dir/install-integrations"
integration_records_tmp="$tmp_dir/install-integration-records"
man_page_records_tmp="$tmp_dir/install-man-page-records"
marker_tmp_path=
binary_tmp_path=
integration_sidecar_tmp_path=
install_animation_pid=
: >"$integration_records_tmp"
: >"$man_page_records_tmp"
stop_install_animation() {
  if [ -n "$install_animation_pid" ]; then
    kill "$install_animation_pid" 2>/dev/null || true
    wait "$install_animation_pid" 2>/dev/null || true
    install_animation_pid=
    printf '\rInstalling ctx %s...\n' "$version" >&2
  fi
}

cleanup() {
  status="$?"
  trap - EXIT INT TERM
  stop_install_animation
  if [ "$status" -ne 0 ]; then
    report_install_stage "installer" "failed"
  fi
  if [ -n "$marker_tmp_path" ]; then
    rm -f "$marker_tmp_path"
  fi
  if [ -n "$binary_tmp_path" ]; then
    rm -f "$binary_tmp_path"
  fi
  if [ -n "$integration_sidecar_tmp_path" ]; then
    rm -f "$integration_sidecar_tmp_path"
  fi
  rm -rf "$tmp_dir"
  exit "$status"
}
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM

if [ "${CTX_INSTALL_NO_MAN:-0}" = "1" ]; then
  install_man=0
fi

if [ "${CTX_INSTALL_NO_MODIFY_PATH:-0}" = "1" ]; then
  modify_path=0
fi

if [ "${CTX_INSTALL_ALL_SKILL_AGENTS:-0}" = "1" ]; then
  all_skill_agents=1
  explicit_skill_request=1
fi

if [ -n "${CTX_INSTALL_SKILL_AGENTS:-}" ]; then
  explicit_skill_request=1
  old_ifs="$IFS"
  IFS=,
  for raw_agent in $CTX_INSTALL_SKILL_AGENTS; do
    agent="$(printf '%s' "$raw_agent" | tr -d '[:space:]')"
    if [ -n "$agent" ]; then
      append_skill_agent "$agent"
    fi
  done
  IFS="$old_ifs"
fi

if [ "${CTX_INSTALL_NO_SKILL:-0}" = "1" ]; then
  run_skill=0
  no_skill_requested=1
fi

if [ "$no_skill_requested" = "1" ] && [ "$explicit_skill_request" = "1" ]; then
  fail "cannot combine --no-skill or CTX_INSTALL_NO_SKILL=1 with skill agent options"
fi

if [ "$all_skill_agents" = "1" ] && [ -n "$skill_agents" ]; then
  fail "cannot combine --all-skill-agents with --skill-agent or CTX_INSTALL_SKILL_AGENTS"
fi

if [ "$run_setup" != "1" ] && [ "$explicit_skill_request" != "1" ]; then
  run_skill=0
fi

start_install_animation() {
  if [ "$styled_output" != "1" ]; then
    log "Installing ctx $version..."
    return
  fi
  (
    dots=...
    while :; do
      printf '\rInstalling ctx %s%s' "$version" "$dots" >&2
      case "$dots" in
        ...) dots='.  ' ;;
        '.  ') dots='.. ' ;;
        *) dots=... ;;
      esac
      sleep 0.1
    done
  ) &
  install_animation_pid="$!"
}

load_release_phase_metadata() {
phase_dir="$tmp_dir/$release_phase"
mkdir -p "$phase_dir"
metadata_file="$phase_dir/metadata.env"
metadata_signature_file="$phase_dir/metadata.env.sig"
metadata_public_key_file="$tmp_dir/metadata-public-key.pem"
artifact_path="$phase_dir/ctx"
companion_artifact_path="$phase_dir/ctx-pro"
pair_envelope_path="$phase_dir/managed-pair-envelope.json"
if [ ! -f "$metadata_file" ]; then
  download_file "$metadata_url" "$metadata_file" 1048576 300
  download_file "$metadata_signature_url" "$metadata_signature_file" 65536 300
fi
write_metadata_public_key "$metadata_public_key_file"
verify_release_metadata_signature "$metadata_file" "$metadata_signature_file" "$metadata_public_key_file"

schema_version="$(metadata_value "$metadata_file" CTX_RELEASE_SCHEMA_VERSION)" || fail "metadata missing CTX_RELEASE_SCHEMA_VERSION"
version="$(metadata_value "$metadata_file" CTX_RELEASE_VERSION)" || fail "metadata missing CTX_RELEASE_VERSION"
base_url="$(metadata_value "$metadata_file" CTX_RELEASE_BASE_URL)" || fail "metadata missing CTX_RELEASE_BASE_URL"
platform_key="$(printf '%s\n' "$platform" | tr '-' '_')"
pair_envelope_artifact="$(metadata_value_optional "$metadata_file" "CTX_RELEASE_MANAGED_PAIR_ENVELOPE_$platform_key")"
pair_core_object_key="$(metadata_value_optional "$metadata_file" "CTX_RELEASE_MANAGED_PAIR_CORE_OBJECT_$platform_key")"
pair_core_checksum="$(metadata_value_optional "$metadata_file" "CTX_RELEASE_MANAGED_PAIR_CORE_SHA256_$platform_key")"
pair_companion_object_key="$(metadata_value_optional "$metadata_file" "CTX_RELEASE_MANAGED_PAIR_COMPANION_OBJECT_$platform_key")"
pair_companion_checksum="$(metadata_value_optional "$metadata_file" "CTX_RELEASE_MANAGED_PAIR_COMPANION_SHA256_$platform_key")"
artifact="$(metadata_value_optional "$metadata_file" "CTX_RELEASE_ARTIFACT_$platform_key")"
checksum="$(metadata_value_optional "$metadata_file" "CTX_RELEASE_SHA256_$platform_key")"
release_channel="$(metadata_value_optional "$metadata_file" CTX_RELEASE_CHANNEL)"
source_commit="$(metadata_value_optional "$metadata_file" CTX_RELEASE_SOURCE_COMMIT)"
published_at="$(metadata_value_optional "$metadata_file" CTX_RELEASE_PUBLISHED_AT)"
[ -n "$release_channel" ] || release_channel="$channel"

[ "$schema_version" = "1" ] || fail "unsupported metadata schema: $schema_version"
[ "$release_channel" = "$channel" ] || fail "metadata channel $release_channel does not match requested channel $channel"
case "$base_url" in https://*) ;; *) fail "metadata base URL must be HTTPS" ;; esac
case "$base_url" in
  https://cli.ctx.rs/storage/v1/object/public/releases/artifacts/*) ;;
  *)
    [ "${CTX_ALLOW_CUSTOM_RELEASE_BASE_URL:-0}" = "1" ] || fail "metadata base URL must be under https://cli.ctx.rs/storage/v1/object/public/releases/artifacts/"
    ;;
esac
is_sha256() {
  [ "${#1}" = "64" ] || return 1
  case "$1" in *[!0-9a-fA-F]*) return 1 ;; esac
}

validate_pair_object_key() {
  object_label="$1"
  object_key="$2"
  object_algorithm="${object_key%%/*}"
  object_rest="${object_key#*/}"
  object_digest="${object_rest%%/*}"
  object_name="${object_rest#*/}"
  [ "$object_algorithm" = "sha256" ] &&
    [ "$object_rest" != "$object_key" ] &&
    [ "$object_name" != "$object_rest" ] &&
    [ "${object_name#*/}" = "$object_name" ] &&
    is_sha256 "$object_digest" ||
    fail "$object_label object key is invalid"
  validate_safe_value "$object_label artifact name" "$object_name"
}

if [ -n "$pair_envelope_artifact" ]; then
  [ -n "$pair_core_object_key" ] || fail "metadata missing managed-pair Core object key"
  [ -n "$pair_core_checksum" ] || fail "metadata missing managed-pair Core checksum"
  [ -n "$pair_companion_object_key" ] || fail "metadata missing managed-pair companion object key"
  [ -n "$pair_companion_checksum" ] || fail "metadata missing managed-pair companion checksum"
  validate_safe_value "managed-pair envelope name" "$pair_envelope_artifact"
  validate_pair_object_key "managed-pair Core" "$pair_core_object_key"
  validate_pair_object_key "managed-pair companion" "$pair_companion_object_key"
  is_sha256 "$pair_core_checksum" || fail "managed-pair Core checksum is invalid"
  is_sha256 "$pair_companion_checksum" || fail "managed-pair companion checksum is invalid"
  pair_core_key_digest="${pair_core_object_key#sha256/}"
  pair_core_key_digest="${pair_core_key_digest%%/*}"
  pair_companion_key_digest="${pair_companion_object_key#sha256/}"
  pair_companion_key_digest="${pair_companion_key_digest%%/*}"
  [ "$(printf '%s' "$pair_core_key_digest" | tr 'A-F' 'a-f')" = "$(printf '%s' "$pair_core_checksum" | tr 'A-F' 'a-f')" ] ||
    fail "managed-pair Core object key does not match its checksum"
  [ "$(printf '%s' "$pair_companion_key_digest" | tr 'A-F' 'a-f')" = "$(printf '%s' "$pair_companion_checksum" | tr 'A-F' 'a-f')" ] ||
    fail "managed-pair companion object key does not match its checksum"
  artifact="${pair_core_object_key##*/}"
  checksum="$pair_core_checksum"
  companion_artifact="${pair_companion_object_key##*/}"
  download_file "${base_url%/}/$pair_envelope_artifact" "$pair_envelope_path" 2097152 300
else
  [ -z "$pair_core_object_key$pair_core_checksum$pair_companion_object_key$pair_companion_checksum" ] ||
    fail "managed-pair component metadata is present without an envelope"
  [ -n "$artifact" ] || fail "metadata missing artifact for $platform"
  [ -n "$checksum" ] || fail "metadata missing checksum for $platform"
fi
if [ -n "$pair_envelope_artifact" ] && [ "${bin_dir##*/}" != "bin" ]; then
  fail "managed-pair install directory must be <root>/bin"
fi
case "$checksum" in
  [0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) ;;
  *) fail "checksum for $platform is not a SHA-256 hex digest" ;;
esac
[ "$checksum" != "0000000000000000000000000000000000000000000000000000000000000000" ] || fail "checksum for $platform is a placeholder"
validate_safe_value "artifact name" "$artifact"

if [ -n "$pair_envelope_artifact" ]; then
  artifact_url="${base_url%/}/$pair_core_object_key"
  companion_artifact_url="${base_url%/}/$pair_companion_object_key"
else
  artifact_url="${base_url%/}/$artifact"
  companion_artifact_url=
fi
install_path="${bin_dir%/}/ctx"

}

download_release_phase_artifacts() {
start_install_animation

report_install_stage "artifact_download" "started"
download_release_artifact "$artifact_url" "$artifact_path"
if [ -n "$pair_envelope_artifact" ]; then
  download_release_artifact "$companion_artifact_url" "$companion_artifact_path"
fi
report_install_stage "artifact_download" "completed"
actual_checksum="$(sha256_file "$artifact_path")"
if [ "$(printf '%s' "$actual_checksum" | tr 'A-F' 'a-f')" != "$(printf '%s' "$checksum" | tr 'A-F' 'a-f')" ]; then
  fail "checksum mismatch for $artifact: expected $checksum, got $actual_checksum"
fi

}


publish_release_phase() {
(umask 022; mkdir -p "$bin_dir")
[ ! -L "$secure_bin_dir" ] || fail "ctx install directory must not be a symlink"
bin_dir_owner="$(path_owner_uid "$secure_bin_dir")" ||
  fail "could not verify ctx install directory ownership"
[ "$bin_dir_owner" = "$(id -u)" ] ||
  fail "ctx install directory must be owned by the current user"
bin_dir="$(cd -P "$secure_bin_dir" && pwd -P)" ||
  fail "could not resolve the ctx install directory"
secure_bin_dir="$bin_dir"
install_path="$bin_dir/ctx"
managed_reinstall=0
managed_core_handoff=0
preserve_core_man_pages=0
man_pages_json=null
man_pages_receipt_present=0
if [ -L "$install_path.install.json" ] ||
   { [ -e "$install_path.install.json" ] && [ ! -f "$install_path.install.json" ]; }; then
  fail "managed install marker destination is not a regular file"
fi
relay_bounded_child_stderr() {
  child_stderr_path="$1"
  [ -f "$child_stderr_path" ] && [ ! -L "$child_stderr_path" ] || return 0
  child_stderr_size="$(path_size_bytes "$child_stderr_path" 2>/dev/null || true)"
  case "$child_stderr_size" in ""|*[!0-9]*) return 0 ;; esac
  [ "$child_stderr_size" -gt 0 ] || return 0
  stop_install_animation
  log "ctx child output:"
  LC_ALL=C dd if="$child_stderr_path" bs=8192 count=1 2>/dev/null | awk '
    NR > 40 { next }
    {
      line = $0
      gsub(/[^ -~	]/, "?", line)
      gsub(/[Bb][Ee][Aa][Rr][Ee][Rr][ 	]+[^ 	]+/, "Bearer <redacted>", line)
      gsub(/[Tt][Oo][Kk][Ee][Nn][ 	]*=[ 	]*[^ 	]+/, "token=<redacted-credential>", line)
      gsub(/[Ss][Ee][Cc][Rr][Ee][Tt][ 	]*=[ 	]*[^ 	]+/, "secret=<redacted-credential>", line)
      gsub(/[?&][Tt][Oo][Kk][Ee][Nn]=[^& 	]+/, "?<redacted-credential>", line)
      gsub(/[?&][Ss][Ee][Cc][Rr][Ee][Tt]=[^& 	]+/, "?<redacted-credential>", line)
      print line > "/dev/stderr"
    }
  ' || :
  [ "$child_stderr_size" -le 8192 ] || log "... ctx child output truncated ..."
}

apply_managed_pair_candidate() {
  pair_apply_marker="$1"
  pair_apply_required="$2"
  case "${bin_dir##*/}" in
    bin) pair_install_root="${bin_dir%/*}" ;;
    *) [ "$pair_apply_required" = "0" ] && return 1
       fail "managed-pair install directory must be <root>/bin" ;;
  esac
  case "$pair_install_root" in
    /*) ;;
    *) [ "$pair_apply_required" = "0" ] && return 1
       fail "managed-pair install root must be absolute" ;;
  esac
  chmod 0700 "$artifact_path" || fail "could not prepare the verified ctx candidate"
  pair_apply_receipt="$tmp_dir/managed-pair-apply.json"
  pair_apply_error="$tmp_dir/managed-pair-apply.err"
  pair_apply_status=0
  "$artifact_path" --ctx-core-managed-pair-apply-v1 "$pair_install_root" - "$pair_envelope_path" "$artifact_path" "$companion_artifact_path" "$pair_apply_marker" >"$pair_apply_receipt" 2>"$pair_apply_error" || pair_apply_status="$?"
  if [ "$pair_apply_status" != "0" ]; then
    [ "$pair_apply_required" = "0" ] && return 1
    relay_bounded_child_stderr "$pair_apply_error"
    fail "ctx managed-pair installation did not complete; resolve the error above before retrying (release $version, exit code $pair_apply_status)"
  fi
  managed_pair_success_receipt "$pair_apply_receipt" managed_pair_apply || {
    [ "$pair_apply_required" = "0" ] && return 1
    relay_bounded_child_stderr "$pair_apply_error"
    fail "candidate Core returned invalid managed-pair apply proof (release $version); rerun this installer command to retry safely"
  }
  while IFS= read -r pair_warning; do
    receipt_warning "$pair_warning"
  done <"$tmp_dir/managed_pair_apply.warnings"
  return 0
}

managed_pair_success_receipt() {
  receipt_path="$1"
  receipt_command="$2"
  warnings_path="$tmp_dir/$receipt_command.warnings"
  : >"$warnings_path" || return 1
  [ "$(path_size_bytes "$receipt_path" 2>/dev/null || true)" -ge 1 ] &&
    [ "$(path_size_bytes "$receipt_path" 2>/dev/null || true)" -le 512 ] || return 1
  awk -v command="$receipt_command" '
    BEGIN { quote = sprintf("%c", 34); prefix = "{" quote "schema_version" quote ":1," quote "command" quote ":" quote command quote "," quote "ok" quote ":true," quote "status" quote ":" quote "committed" quote }
    NR != 1 { invalid = 1 }
    {
      receipt = $0
      if (receipt == prefix "}") next
      expected = prefix "," quote "warnings" quote ":["
      if (index(receipt, expected) != 1 || substr(receipt, length(receipt) - 1) != "]}") { invalid = 1; next }
      warnings = substr(receipt, length(expected) + 1, length(receipt) - length(expected) - 2)
      while (length(warnings)) {
        if (substr(warnings, 1, 1) != quote) { invalid = 1; break }
        quote_end = index(substr(warnings, 2), quote)
        if (!quote_end) { invalid = 1; break }
        warning = substr(warnings, 2, quote_end - 1)
        if (length(warning) < 1 || length(warning) > 160 || warning !~ /^[A-Za-z0-9 .,:;()\/_-]+$/) { invalid = 1; break }
        print warning
        count++
        warnings = substr(warnings, quote_end + 2)
        if (!length(warnings)) break
        if (substr(warnings, 1, 1) != ",") { invalid = 1; break }
        warnings = substr(warnings, 2)
      }
      if (count < 1 || count > 4) invalid = 1
    }
    END { exit invalid }
  ' "$receipt_path" >"$warnings_path" || return 1
  return 0
}

try_resume_interrupted_managed_pair() {
  [ -n "$pair_envelope_artifact" ] &&
    [ -f "$bin_dir/.ctx.upgrade-install-transaction.json" ] &&
    [ ! -L "$bin_dir/.ctx.upgrade-install-transaction.json" ] &&
    [ -f "$install_path.install.json" ] &&
    [ ! -L "$install_path.install.json" ] || return 0
  if [ -f "$install_path" ] && [ ! -L "$install_path" ] &&
     [ "$(sha256_file "$install_path" 2>/dev/null | tr 'A-F' 'a-f')" = \
       "$(ownership_json_string_field "$install_path.install.json" sha256 2>/dev/null | tr 'A-F' 'a-f')" ]; then
    return 0
  fi
  stage_install_marker "$tmp_dir/install-marker.XXXXXX"
  apply_managed_pair_candidate "$marker_tmp_path" 0 || :
}
try_resume_interrupted_managed_pair
if [ -e "$install_path" ] || [ -L "$install_path" ] ||
   [ -e "$install_path.install.json" ] || [ -L "$install_path.install.json" ]; then
  validate_existing_managed_install
  if [ "$stable_bridge" = "1" ]; then
    phase_order="$(compare_release_versions "$version" "$previous_version")" ||
      fail "invalid managed release version: $previous_version"
    [ "$phase_order" != "-1" ] || fail "refusing to downgrade the managed ctx installation"
  fi
  managed_reinstall=1
  if [ "$install_man" = "1" ]; then
    preserve_core_man_pages=1
  fi
fi
load_previous_integration_ownership
if [ "$managed_reinstall" != "1" ]; then
  initialize_man_page_receipt
fi
if [ -n "$pair_envelope_artifact" ]; then
  actual_companion_checksum="$(sha256_file "$companion_artifact_path")"
  [ "$(printf '%s' "$actual_companion_checksum" | tr 'A-F' 'a-f')" = "$(printf '%s' "$pair_companion_checksum" | tr 'A-F' 'a-f')" ] ||
    fail "checksum mismatch for $companion_artifact: expected $pair_companion_checksum, got $actual_companion_checksum"
  if [ "$managed_reinstall" = "1" ]; then
    if managed_pair_requires_candidate_apply; then
      stage_install_marker "$tmp_dir/install-marker.XXXXXX"
      apply_managed_pair_candidate "$marker_tmp_path" 1
      managed_core_handoff=1
    else
      managed_core_handoff=1
      if [ "$install_man" != "1" ] && [ "$release_phase" != "bridge" ]; then
        disable_core_man_pages_before_upgrade
      fi
      run_managed_core_upgrade
    fi
  else
    stage_install_marker "$tmp_dir/install-marker.XXXXXX"
    apply_managed_pair_candidate "$marker_tmp_path" 1
    managed_core_handoff=1
  fi
else
if [ "$managed_reinstall" = "1" ] &&
   ! previous_install_predates_persistent_daemon; then
  managed_core_handoff=1
  if [ "$install_man" != "1" ] && [ "$release_phase" != "bridge" ]; then
    # Core serializes the opt-out with runtime refresh and upgrade publication.
    disable_core_man_pages_before_upgrade
  fi
  run_managed_core_upgrade
else
  if [ "$managed_reinstall" = "1" ] && [ "$preserve_core_man_pages" = "1" ]; then
    # Pre-daemon managed releases use the direct publication path, so there is
    # no Core marker receipt to preserve.
    preserve_core_man_pages=0
  fi
    stage_integration_ownership
    stage_install_marker "$tmp_dir/install-marker.XXXXXX"
    publish_fresh_or_legacy_binary
  fi
fi
reconcile_managed_pair_integration() {
  case "${bin_dir##*/}" in
    bin) pair_reconcile_root="${bin_dir%/*}" ;;
    *) return 1 ;;
  esac
  case "$pair_reconcile_root" in /*) ;; *) return 1 ;; esac
  pair_reconcile_receipt="$tmp_dir/managed-pair-reconcile.json"
  pair_reconcile_error="$tmp_dir/managed-pair-reconcile.err"
  if ! "$install_path" --ctx-core-managed-pair-reconcile-integration-v1 "$pair_reconcile_root" - "$integration_manifest_tmp" >"$pair_reconcile_receipt" 2>"$pair_reconcile_error"; then
    relay_bounded_child_stderr "$pair_reconcile_error"
    return 1
  fi
  if ! managed_pair_success_receipt "$pair_reconcile_receipt" managed_pair_reconcile_integration; then
    relay_bounded_child_stderr "$pair_reconcile_error"
    return 1
  fi
  while IFS= read -r pair_warning; do
    receipt_warning "$pair_warning"
  done <"$tmp_dir/managed_pair_reconcile_integration.warnings"
  return 0
}
if [ "$managed_core_handoff" != "1" ]; then
  verify_installed_target_identity
fi
report_install_stage "binary_install" "completed"
stop_install_animation

}

compare_release_versions() {
  LC_ALL=C awk -v left="$1" -v right="$2" '
    function numeric(s) { return s ~ /^[0-9]+$/ }
    function number(s) { return numeric(s) && (s == "0" || s !~ /^0/) }
    function cmp(a,b) {
      if (length(a) != length(b)) return length(a) < length(b) ? -1 : 1
      return ("x" a) == ("x" b) ? 0 : (("x" a) < ("x" b) ? -1 : 1)
    }
    function parse(s,parts, p,n,i,build,pre,core) {
      if (length(s) > 128 || s ~ /[^0-9A-Za-z.+-]/) return 0
      p=index(s,"+")
      if (p) {
        build=substr(s,p+1); s=substr(s,1,p-1)
        n=split(build,parts,".")
        for(i=1;i<=n;i++) if(parts[i] !~ /^[0-9A-Za-z-]+$/) return 0
        if (!n) return 0
      }
      p=index(s,"-"); pre=""
      if(p) { pre=substr(s,p+1); s=substr(s,1,p-1); if(pre == "") return 0 }
      n=split(s,core,"."); if(n != 3) return 0
      for(i=1;i<=3;i++) if(!number(core[i])) return 0
      parts[1]=core[1]; parts[2]=core[2]; parts[3]=core[3]; parts[4]=pre
      if(pre != "") {
        n=split(pre,core,".")
        for(i=1;i<=n;i++) if(core[i] !~ /^[0-9A-Za-z-]+$/ || (numeric(core[i]) && !number(core[i]))) return 0
      }
      return 1
    }
    BEGIN {
      if(!parse(left,a) || !parse(right,b)) exit 2
      result=0
      for(i=1;i<=3;i++) { result=cmp(a[i],b[i]); if(result) break }
      if(!result && a[4] != b[4]) {
        if(a[4] == "") result=1
        else if(b[4] == "") result=-1
        else {
          na=split(a[4],ap,"."); nb=split(b[4],bp,".")
          for(i=1;i<=na && i<=nb;i++) {
            if(numeric(ap[i]) && numeric(bp[i])) result=cmp(ap[i],bp[i])
            else if(numeric(ap[i]) != numeric(bp[i])) result=numeric(ap[i]) ? -1 : 1
            else result=("x" ap[i]) == ("x" bp[i]) ? 0 : (("x" ap[i]) < ("x" bp[i]) ? -1 : 1)
            if(result) break
          }
          if(!result && na != nb) result=na < nb ? -1 : 1
        }
      }
      print result
    }
  '
}
final_metadata_url="$metadata_url"
final_metadata_signature_url="$metadata_signature_url"
release_phase=final
load_release_phase_metadata
final_version="$version"
final_checksum="$checksum"
bridge_required=0
stable_bridge=1
[ "$channel" = "stable" ] || stable_bridge=0
if [ "$stable_bridge" = "1" ]; then
  final_order="$(compare_release_versions "$final_version" "1.3.2")" ||
    fail "invalid release version: $final_version"
  [ "$final_order" != "-1" ] || fail "stable installer targets before 1.3.2 are unsupported"
fi
if [ "$explicit_metadata" = "1" ] && [ "$semantic_enabled" = "1" ]; then
  fail "explicit metadata cannot authorize Semantic repair through the installed release; use the default installer feed"
fi
if [ -L "$install_path.install.json" ] ||
   { [ -e "$install_path.install.json" ] && [ ! -f "$install_path.install.json" ]; }; then
  fail "managed install marker destination is not a regular file"
fi
if [ -e "$install_path" ] || [ -L "$install_path" ] ||
   [ -e "$install_path.install.json" ] || [ -L "$install_path.install.json" ]; then
  if [ "$explicit_metadata" = "1" ] && [ "0" != "1" ]; then
    fail "managed reinstall cannot honor an explicit metadata target; use the default installer feed"
  fi
  # An intact old image still selects B while its owner resumes a pending B.
  # A partially published image is classified after the existing recovery call.
  if [ ! -e "$bin_dir/.ctx.upgrade-install-transaction.json" ] ||
     (actual_checksum=; validate_existing_managed_install >/dev/null 2>&1); then
    actual_checksum="$checksum"
    validate_existing_managed_install
    if [ "$stable_bridge" = "1" ]; then
      installed_order="$(compare_release_versions "$final_version" "$previous_version")" ||
        fail "invalid managed release version: $previous_version"
      [ "$installed_order" != "-1" ] || fail "refusing to downgrade the managed ctx installation"
      if [ "$installed_order" = "0" ] && [ "$previous_binary_actual" = "$previous_binary_digest" ] &&
         [ "$previous_binary_digest" != "$(printf '%s' "$checksum" | tr 'A-F' 'a-f')" ]; then
        fail "signed release differs from the installed identity at the same version"
      fi
      bridge_order="$(compare_release_versions "$previous_version" "1.3.2")" ||
        fail "invalid managed release version: $previous_version"
      if [ "$previous_binary_digest" = "$previous_binary_actual" ] && [ "$bridge_order" = "-1" ]; then
        bridge_required=1
      fi
    fi
  fi
fi
if [ "$bridge_required" = "1" ]; then
  release_phase=bridge
  metadata_url="https://cli.ctx.rs/functions/v1/releases/stable/1.3.2/ctx-release-metadata.env"
  metadata_signature_url="$metadata_url.sig"
  load_release_phase_metadata
  [ "$version" = "1.3.2" ] && [ -n "$pair_envelope_artifact" ] ||
    fail "frozen bridge metadata does not identify the required signed 1.3.2 pair"
  if [ "$final_version" = "$version" ] && [ "$checksum" != "$final_checksum" ]; then
    fail "final metadata conflicts with the frozen bridge identity"
  fi
  download_release_phase_artifacts
  publish_release_phase
fi
release_phase=final
metadata_url="$final_metadata_url"
metadata_signature_url="$final_metadata_signature_url"
load_release_phase_metadata
download_release_phase_artifacts
publish_release_phase
receipt_item "Installed and verified"

if [ "$semantic_enabled" = "1" ]; then
  repair_metadata_uri="$(absolute_path_to_file_uri "$metadata_file")"
  repair_metadata_signature_uri="$(absolute_path_to_file_uri "$metadata_signature_file")"
  if ! CTX_SEARCH_SEMANTIC=1     CTX_RELEASE_METADATA_URL="$repair_metadata_uri"     CTX_RELEASE_METADATA_SIGNATURE_URL="$repair_metadata_signature_uri"     "$install_path" upgrade --channel "$channel" --format=json >"$tmp_dir/semantic-upgrade.out" 2>&1; then
    fail "ctx Semantic runtime repair failed"
  fi
fi

man_install_failed=0
man_page_owned_count=0
record_man_install_failure() {
  man_install_failed=1
}

install_new_man_page() {
  generated_page="$1"
  generated_name="$2"
  (
    cd -P "$man_dir" || exit 1
    staged_page="$(mktemp .ctx-man-install.XXXXXX)" || exit 1
    trap 'rm -f "$staged_page"' EXIT
    trap 'exit 1' HUP INT TERM
    install -m 0644 "$generated_page" "$staged_page" || exit 1
    # A relative hard link is an atomic no-replace publication. The pinned
    # working directory prevents an ancestor rename/symlink swap redirect.
    ln "$staged_page" "$generated_name"
  )
}

replace_owned_man_page() {
  generated_page="$1"
  generated_name="$2"
  expected_digest="$3"
  (
    cd -P "$man_dir" || exit 1
    [ -f "$generated_name" ] && [ ! -L "$generated_name" ] || exit 1
    [ "$(path_owner_uid "$generated_name" 2>/dev/null || true)" = "$(id -u)" ] || exit 1
    [ "$(path_link_count "$generated_name" 2>/dev/null || true)" = "1" ] || exit 1
    [ "$(sha256_file "$generated_name" 2>/dev/null | tr 'A-F' 'a-f' || true)" = "$expected_digest" ] || exit 1
    staged_page="$(mktemp .ctx-man-install.XXXXXX)" || exit 1
    trap 'rm -f "$staged_page"' EXIT
    trap 'exit 1' HUP INT TERM
    install -m 0644 "$generated_page" "$staged_page" || exit 1
    mv -f "$staged_page" "$generated_name"
  )
}

if [ "$install_man" = "1" ] && [ "$preserve_core_man_pages" != "1" ]; then
  generated_man_dir="$tmp_dir/generated-man"
  mkdir -p "$generated_man_dir"
  if "$install_path" docs man --out "$generated_man_dir" >"$tmp_dir/man-install.out" 2>&1; then
    if ! mkdir -p "$man_dir"; then
      record_man_install_failure "$man_dir" "could not create directory"
    elif [ -L "$man_dir" ]; then
      record_man_install_failure "$man_dir" "destination is a symlink"
    else
      if ! man_dir="$(cd -P "$man_dir" && pwd -P)"; then
        record_man_install_failure "$man_dir" "could not resolve directory"
      elif ! owner_safe_man_directory "$man_dir"; then
        record_man_install_failure "$man_dir" "directory is not owner-safe"
      else
        generated_man_count=0
        for generated_man_path in "$generated_man_dir"/ctx*.1; do
          [ -f "$generated_man_path" ] && [ ! -L "$generated_man_path" ] || continue
          generated_man_count="$((generated_man_count + 1))"
          generated_man_name="${generated_man_path##*/}"
          installed_man_path="$man_dir/$generated_man_name"
          generated_man_digest="$(sha256_file "$generated_man_path" | tr 'A-F' 'a-f')"
          if [ -e "$installed_man_path" ] || [ -L "$installed_man_path" ]; then
            if [ ! -f "$installed_man_path" ] || [ -L "$installed_man_path" ] ||
               [ "$(path_owner_uid "$installed_man_path" 2>/dev/null || true)" != "$(id -u)" ] ||
               [ "$(path_link_count "$installed_man_path" 2>/dev/null || true)" != "1" ]; then
              record_man_install_failure "$installed_man_path" "existing destination is not an owner-safe regular file"
              continue
            fi
            existing_man_digest="$(sha256_file "$installed_man_path" 2>/dev/null | tr 'A-F' 'a-f' || true)"
            if [ "$existing_man_digest" = "$generated_man_digest" ]; then
              if owned_integration_matches man "$existing_man_digest" "$installed_man_path"; then
                record_owned_man_page "$generated_man_name" "$existing_man_digest"
              else
                # Matching unmanaged content remains unowned and cannot seed a
                # future automatic replacement receipt.
                record_man_install_failure "$installed_man_path" "matching page is not installer-owned"
              fi
              continue
            else
              if owned_integration_matches man "$existing_man_digest" "$installed_man_path" &&
                 replace_owned_man_page "$generated_man_path" "$generated_man_name" "$existing_man_digest"; then
                installed_man_digest="$(sha256_file "$installed_man_path" | tr 'A-F' 'a-f')"
                record_owned_integration man "$installed_man_digest" "$installed_man_path"
                record_owned_man_page "$generated_man_name" "$installed_man_digest"
              else
                record_man_install_failure "$installed_man_path" "existing page differs and is not installer-owned"
              fi
              continue
            fi
          fi
          if install_new_man_page "$generated_man_path" "$generated_man_name"; then
            installed_man_digest="$(sha256_file "$installed_man_path" | tr 'A-F' 'a-f')"
            record_owned_integration man "$installed_man_digest" "$installed_man_path"
            record_owned_man_page "$generated_man_name" "$installed_man_digest"
          else
            record_man_install_failure "$installed_man_path" "could not install generated page"
          fi
        done
        if [ "$generated_man_count" = "0" ]; then
          record_man_install_failure "$man_dir" "no generated ctx*.1 pages"
        fi
      fi
    fi
  else
    record_man_install_failure "$man_dir" "ctx docs man failed"
  fi
fi

if [ "$preserve_core_man_pages" = "1" ]; then
  : # The new binary's first-start reconciler owns managed reruns.
elif [ "$managed_reinstall" = "1" ] || [ "$install_man" != "1" ]; then
  : # Receipts are created only by fresh installs.
elif [ "$man_install_failed" = "0" ] &&
     [ "${generated_man_count:-0}" -gt 0 ] &&
     [ "$man_page_owned_count" -eq "$generated_man_count" ]; then
  build_installed_man_page_receipt
else
  man_pages_json=null
  man_pages_receipt_present=0
fi

skill_install_failed=0
if [ "$run_skill" = "1" ]; then
  report_install_stage "skill_install" "started"
  if run_skill_install; then
    if record_installed_skills; then
      report_install_stage "skill_install" "completed"
    else
      report_install_stage "skill_install" "failed"
      skill_install_failed=1
    fi
  else
    report_install_stage "skill_install" "failed"
    skill_install_failed=1
  fi
else
  report_install_stage "skill_install" "skipped"
fi

setup_status=0
setup_verified=0
setup_initialized=
setup_mode=invalid
indexed_sessions=
indexed_items=
pro_setup_requested=0
pro_status=skipped
pro_account_state=
pro_trial_started=false
pro_trial_ends_on=
pro_action_url=
pro_live_disclosure=0; pro_cta_relayed=0; setup_wait_requested=0
if [ "$run_setup" = "1" ] && [ "$daemon_enabled" = "1" ]; then
  case "$pro_trial_selection" in
    compatibility) pro_setup_requested=1 ;;
    automatic)
      if [ "$managed_reinstall" != "1" ]; then
        pro_setup_requested=1
      fi
      ;;
  esac
fi
if [ "$run_setup" = "1" ]; then
  setup_progress="${CTX_SETUP_PROGRESS:-auto}"
  if [ "$setup_no_daemon" != "1" ]; then
    setup_wait_requested=1
  fi
  if [ "$pro_setup_requested" != "1" ]; then
    receipt_warning "ctx pro was not started. Run: ctx pro"
    pro_cta_relayed=1
  fi
  if [ "$pro_setup_requested" = "1" ] && [ -t 2 ] &&
     ! ci_environment && has_controlling_tty; then
    case "$setup_progress" in auto|plain) pro_live_disclosure=1 ;; esac
  fi
  report_install_stage "setup" "started"
  if [ "$setup_progress" != "none" ] && [ -t 2 ]; then
    log ""
  fi
  managed_setup=0
if [ -n "$pair_envelope_artifact" ]; then
  managed_setup=1
  set -- setup
  if [ "$pro_setup_requested" = "1" ]; then
    set -- "$@" --pro
  fi
else
  set -- setup --quiet --format json
  if [ "$pro_setup_requested" = "1" ]; then
    set -- "$@" --pro
  fi
fi
if [ "$setup_wait_requested" = "1" ]; then
  set -- "$@" --wait
fi
if [ "$semantic_enabled" = "1" ]; then
  set -- "$@" --semantic
fi
set -- "$@" --progress "$setup_progress"
if [ "$setup_no_daemon" = "1" ]; then
  set -- "$@" --no-daemon
fi
run_hosted_setup() {
  CTX_HOSTED_INSTALLER_SETUP=1 "$install_path" "$@"
}
if [ "$managed_setup" = "1" ]; then
  run_hosted_setup "$@" || setup_status="$?"
  if [ "$setup_status" = "0" ]; then
    setup_verified=1
    if [ "$pro_setup_requested" = "1" ]; then
      pro_status=native
    fi
  fi
elif [ "$setup_progress" = "none" ]; then
  run_hosted_setup "$@"     >"$tmp_dir/setup-receipt.json" 2>"$tmp_dir/setup.err" || setup_status="$?"
else
  run_hosted_setup "$@"     >"$tmp_dir/setup-receipt.json" || setup_status="$?"
fi
  if [ "$setup_status" = "0" ] && [ "$managed_setup" != "1" ]; then
    if json_document_is_well_formed "$tmp_dir/setup-receipt.json"; then
      setup_schema_version="$(json_top_level_unsigned_integer_or_null_field "$tmp_dir/setup-receipt.json" schema_version)" ||
        setup_schema_version=
      setup_initialized="$(json_top_level_boolean_field "$tmp_dir/setup-receipt.json" initialized)" ||
        setup_initialized=
      setup_mode="$(json_top_level_string_field "$tmp_dir/setup-receipt.json" mode)" ||
        setup_mode=invalid
      indexed_sessions="$(json_top_level_unsigned_integer_or_null_field "$tmp_dir/setup-receipt.json" indexed_sessions)" ||
        indexed_sessions=
      indexed_items="$(json_top_level_unsigned_integer_or_null_field "$tmp_dir/setup-receipt.json" indexed_items)" ||
        indexed_items=
      if { [ "$setup_schema_version" = "2" ] || [ "$setup_schema_version" = "3" ]; } &&
         { [ "$setup_initialized" = "true" ] || [ "$setup_initialized" = "false" ]; } &&
         { [ "$indexed_sessions" = "null" ] || is_unsigned_integer "$indexed_sessions"; } &&
         { [ "$indexed_items" = "null" ] || is_unsigned_integer "$indexed_items"; }; then
        case "$setup_mode" in
          ready|pending|stale|unavailable) setup_verified=1 ;;
        esac
      fi
      if [ "$pro_setup_requested" = "1" ]; then
        pro_receipt_valid=1
        pro_status="$(json_object_string_field "$tmp_dir/setup-receipt.json" pro status)" ||
          pro_receipt_valid=0
        pro_account_state="$(json_object_string_or_null_field "$tmp_dir/setup-receipt.json" pro account_state 0)" ||
          pro_receipt_valid=0
        pro_trial_started="$(json_object_boolean_field "$tmp_dir/setup-receipt.json" pro trial_started)" ||
          pro_receipt_valid=0
        pro_trial_ends_on="$(json_object_string_or_null_field "$tmp_dir/setup-receipt.json" pro trial_ends_on)" ||
          pro_receipt_valid=0
        pro_action_url="$(json_object_string_or_null_field "$tmp_dir/setup-receipt.json" pro action_url)" ||
          pro_receipt_valid=0
        pro_next_command="$(json_object_string_or_null_field "$tmp_dir/setup-receipt.json" pro next_command)" ||
          pro_receipt_valid=0
        case "$pro_status" in ready|skipped|unavailable) ;; *) pro_receipt_valid=0 ;; esac
        [ "$pro_next_command" = "ctx pro" ] || pro_receipt_valid=0
        if [ -n "$pro_trial_ends_on" ] && ! valid_iso_date "$pro_trial_ends_on"; then
          pro_receipt_valid=0
        fi
        if [ -n "$pro_action_url" ] && ! valid_https_action_url "$pro_action_url"; then
          pro_receipt_valid=0
        fi
        case "$pro_status:$pro_trial_started:$pro_account_state" in
          ready:true:|ready:true:trial) ;;
          ready:false:browser_handoff_pending|unavailable:false:browser_handoff_pending)
            if [ -n "$pro_trial_ends_on" ] || [ -z "$pro_action_url" ]; then
              pro_receipt_valid=0
            fi
            ;;
          ready:false:|ready:false:active|ready:false:canceling_paid)
            if [ -n "$pro_trial_ends_on" ] || [ -n "$pro_action_url" ]; then
              pro_receipt_valid=0
            fi
            ;;
          skipped:false:|unavailable:false:)
            if [ -n "$pro_trial_ends_on" ] || [ -n "$pro_action_url" ]; then
              pro_receipt_valid=0
            fi
            ;;
          *) pro_receipt_valid=0 ;;
        esac
        if [ "$pro_receipt_valid" != "1" ]; then
          pro_status=invalid
          pro_account_state=
          pro_trial_started=false
          pro_trial_ends_on=
          pro_action_url=
        fi
      fi
    fi

    if [ "$setup_verified" != "1" ]; then
      setup_status=1
    fi
  fi
  if [ "$setup_status" = "0" ]; then
    report_install_stage "setup" "completed"
  else
    [ "$pro_setup_requested" = "1" ] && pro_status=setup_failed
    report_install_stage "setup" "failed"
  fi
else
  report_install_stage "setup" "skipped"
fi

configure_path_if_needed
if [ "$managed_core_handoff" = "1" ]; then
  stage_integration_ownership
  if ! reconcile_managed_pair_integration; then
    receipt_warning "ctx installed, but integration ownership reconciliation is pending. Rerun this installer command to retry safely"
  fi
else
  publish_integration_ownership
  write_install_marker
  cleanup_published_integration_generations
fi

if [ "$pro_account_state" = "browser_handoff_pending" ]; then
  pro_browser_handoff_pending_receipt
elif [ "$pro_trial_started" = "true" ]; then
  if [ "$pro_live_disclosure" = "1" ] &&
     [ -n "$pro_trial_ends_on" ] && [ -n "$pro_action_url" ]; then
    receipt_item "ctx pro trial started"
  else
    pro_trial_started_receipt
  fi
fi
case "$pro_status" in
  native) ;;
  ready)
    if [ "$pro_trial_started" != "true" ] &&
       [ "$pro_account_state" != "browser_handoff_pending" ]; then
      receipt_item "ctx pro ready"
    fi
    ;;
  skipped)
    [ "$pro_cta_relayed" = "1" ] ||
      receipt_warning "ctx pro was not started. Run: ctx pro"
    ;;
  setup_failed) receipt_warning "ctx setup did not finish. Any completed ctx Pro activation was kept. Retry: ctx setup" ;;
  unavailable|invalid) [ "$pro_account_state" = "browser_handoff_pending" ] || pro_trial_failure_receipt ;;
esac

found_count=
found_unit=
if [ "$setup_initialized" = "true" ] &&
   is_unsigned_integer "$indexed_sessions" && [ "$indexed_sessions" != "0" ]; then
  found_count="$indexed_sessions"
  found_unit=sessions
elif [ "$setup_initialized" = "true" ] && is_unsigned_integer "$indexed_items"; then
  found_count="$indexed_items"
  found_unit=records
fi
if [ "$setup_verified" = "1" ] && [ -n "$found_count" ]; then
  receipt_item "Found $(format_count "$found_count") $found_unit"
fi

indexing_continues=0
if [ "$setup_verified" = "1" ]; then
  case "$setup_mode" in
    ready) receipt_item "Index ready" ;;
    pending|stale)
      receipt_item "Indexing started"
      if [ "$daemon_enabled" = "1" ]; then indexing_continues=1; fi
      ;;
    unavailable)
      if [ "$daemon_configuration_disabled" = "1" ]; then
        receipt_item "Indexing deferred — daemon disabled"
      elif [ "$setup_no_daemon" = "1" ]; then
        receipt_item "Indexing deferred — daemon not started"
      fi
      ;;
  esac
fi

if [ "$run_setup" = "1" ] && [ "$setup_status" != "0" ] && [ "${managed_setup:-0}" != "1" ] && [ "$pro_status" != "setup_failed" ]; then
  receipt_warning "Setup failed. Retry: ctx setup"
fi
if [ "$skill_install_failed" = "1" ]; then
  receipt_warning "Agent skill setup failed. Retry: ctx integrations install skills"
fi
if [ "$indexing_continues" = "1" ]; then
  log ""
  log "Indexing will continue in the background."
fi
if [ "$run_setup" = "1" ] && [ "$setup_status" = "0" ] &&
   [ "$setup_verified" = "1" ] && [ "${managed_setup:-0}" = "1" ]; then
  log ""
  receipt_item "Setup complete"
fi
if [ -n "$path_result" ]; then
  log ""
  log "To use the newly installed ctx in this shell, run:"
  log "  $path_export_command"
  if [ "$path_profile_persisted" = "1" ] || [ "$path_profile_semantics" = "1" ]; then
    log ""
    log "New terminal sessions will include it automatically."
  else
    log ""
    log "To add it for future terminal sessions, add $path_display_dir to your shell profile."
  fi
fi

if [ "$setup_verified" = "1" ]; then
  log ""
  log '  Search:    ctx search "test failure"'
  if [ "${managed_setup:-0}" = "1" ]; then
    log "  Blame:     ctx blame file src/main.rs --lines 42"
  else
    log "  Progress:  ctx index watch"
  fi
  log "  Status:    ctx status"
fi
if [ "$setup_status" != "0" ]; then
  exit "$setup_status"
fi
report_install_stage "installer" "completed"
