#!/bin/bash

set -euo pipefail

ROOT_DIR="$(git rev-parse --show-toplevel)"
cd "$ROOT_DIR"

submission_mode=false
freeze_mode=false
archive_path=""
ipa_path=""
ipa_temp_dir=""

expected_team_id="ZX93RKSPHH"
expected_signing_authority="Apple Distribution: ZhiEra LLC (ZX93RKSPHH)"
expected_app_group="group.com.zhiera.kivo.shared"
expected_keychain_group="ZX93RKSPHH.com.zhiera.kivo"

# Archive compatibility exceptions below are conditional on these source/build
# checks. Initialize them explicitly so a failed xcodebuild inspection can
# never turn into an implicit allowlist under `set -u`.
app_store_lite_build_condition_is_present=false
signal_ui_lite_build_condition_is_present=false
signal_service_kit_lite_build_condition_is_present=false
main_financial_sources_are_excluded=false
signal_ui_payment_engine_is_excluded=false

while (($#)); do
  case "$1" in
    --submission)
      submission_mode=true
      shift
      ;;
    --freeze)
      freeze_mode=true
      shift
      ;;
    --archive)
      archive_path="${2:?--archive requires an .xcarchive path}"
      shift 2
      ;;
    --ipa)
      ipa_path="${2:?--ipa requires an .ipa path}"
      shift 2
      ;;
    *)
      echo "Unknown argument: $1" >&2
      exit 2
      ;;
  esac
done

failures=0
pass() { printf 'PASS  %s\n' "$1"; }
fail() { printf 'FAIL  %s\n' "$1" >&2; failures=$((failures + 1)); }
info() { printf 'INFO  %s\n' "$1"; }

plist_value() {
  local plist="$1"
  local key="$2"
  /usr/libexec/PlistBuddy -c "Print :$key" "$plist" 2>/dev/null || true
}

signed_entitlement() {
  local bundle="$1"
  local keypath="$2"
  codesign -d --entitlements :- "$bundle" 2>/dev/null \
    | plutil -extract "$keypath" raw -o - -- - 2>/dev/null \
    || true
}

profile_value() {
  local bundle="$1"
  local keypath="$2"
  security cms -D -i "$bundle/embedded.mobileprovision" 2>/dev/null \
    | plutil -extract "$keypath" raw -o - -- - 2>/dev/null \
    || true
}

verify_expected_value() {
  local label="$1"
  local actual="$2"
  local expected="$3"
  if [[ "$actual" == "$expected" ]]; then
    pass "$label is $expected"
  else
    fail "$label is '$actual' (expected '$expected')"
  fi
}

verify_distribution_bundle() {
  local bundle="$1"
  local label="$2"
  local expected_bundle_id="$3"
  local expected_executable="$4"
  local expected_profile="$5"
  local requires_push="$6"

  if [[ ! -d "$bundle" ]]; then
    fail "$label bundle is missing at $bundle"
    return
  fi

  local info_plist="$bundle/Info.plist"
  local executable="$bundle/$expected_executable"
  if [[ ! -f "$info_plist" || ! -x "$executable" ]]; then
    fail "$label is missing Info.plist or executable $expected_executable"
    return
  fi

  verify_expected_value "$label bundle identifier" \
    "$(plist_value "$info_plist" CFBundleIdentifier)" "$expected_bundle_id"
  verify_expected_value "$label version" \
    "$(plist_value "$info_plist" CFBundleShortVersionString)" "$release_version"
  verify_expected_value "$label build" \
    "$(plist_value "$info_plist" CFBundleVersion)" "$release_build"
  verify_expected_value "$label executable name" \
    "$(plist_value "$info_plist" CFBundleExecutable)" "$expected_executable"

  if codesign --verify --strict "$bundle" >/dev/null 2>&1; then
    pass "$label code signature is structurally valid"
  else
    fail "$label code signature is invalid"
  fi

  local signature_details
  signature_details="$(codesign -dv --verbose=4 "$bundle" 2>&1 || true)"
  if grep -F -q "TeamIdentifier=$expected_team_id" <<<"$signature_details"; then
    pass "$label signature uses team $expected_team_id"
  else
    fail "$label signature does not use team $expected_team_id"
  fi
  if grep -F -q "Authority=$expected_signing_authority" <<<"$signature_details"; then
    pass "$label signature uses the ZhiEra Apple Distribution identity"
  else
    fail "$label signature does not use '$expected_signing_authority'"
  fi

  if [[ ! -f "$bundle/embedded.mobileprovision" ]]; then
    fail "$label has no embedded distribution provisioning profile"
  else
    verify_expected_value "$label provisioning profile" \
      "$(profile_value "$bundle" Name)" "$expected_profile"
    verify_expected_value "$label profile team" \
      "$(profile_value "$bundle" TeamIdentifier.0)" "$expected_team_id"
    verify_expected_value "$label profile application identifier" \
      "$(profile_value "$bundle" Entitlements.application-identifier)" \
      "$expected_team_id.$expected_bundle_id"
    verify_expected_value "$label profile get-task-allow" \
      "$(profile_value "$bundle" Entitlements.get-task-allow)" "false"
    verify_expected_value "$label profile beta-reports-active" \
      "$(profile_value "$bundle" Entitlements.beta-reports-active)" "true"

    if [[ -n "$(profile_value "$bundle" ProvisionedDevices.0)" ]]; then
      fail "$label profile contains development/ad-hoc ProvisionedDevices"
    elif [[ "$(profile_value "$bundle" ProvisionsAllDevices)" == "true" ]]; then
      fail "$label profile is an enterprise profile"
    else
      pass "$label profile is an App Store distribution profile"
    fi
  fi

  verify_expected_value "$label signed application identifier" \
    "$(signed_entitlement "$bundle" application-identifier)" \
    "$expected_team_id.$expected_bundle_id"
  verify_expected_value "$label signed team entitlement" \
    "$(signed_entitlement "$bundle" 'com\.apple\.developer\.team-identifier')" \
    "$expected_team_id"
  verify_expected_value "$label signed get-task-allow" \
    "$(signed_entitlement "$bundle" get-task-allow)" "false"
  verify_expected_value "$label signed beta-reports-active" \
    "$(signed_entitlement "$bundle" beta-reports-active)" "true"
  verify_expected_value "$label signed App Group" \
    "$(signed_entitlement "$bundle" 'com\.apple\.security\.application-groups.0')" \
    "$expected_app_group"
  verify_expected_value "$label signed keychain group" \
    "$(signed_entitlement "$bundle" keychain-access-groups.0)" \
    "$expected_keychain_group"

  if [[ "$requires_push" == true ]]; then
    verify_expected_value "$label signed APNs environment" \
      "$(signed_entitlement "$bundle" aps-environment)" "production"
    verify_expected_value "$label profile APNs environment" \
      "$(profile_value "$bundle" Entitlements.aps-environment)" "production"
  fi
}

verify_privacy_manifests() {
  local app="$1"
  local label="$2"
  local required_manifests=(
    "$app/PrivacyInfo.xcprivacy"
    "$app/PlugIns/SignalNSE.appex/PrivacyInfo.xcprivacy"
    "$app/PlugIns/SignalShareExtension.appex/PrivacyInfo.xcprivacy"
  )
  local manifest
  for manifest in "${required_manifests[@]}"; do
    if [[ -f "$manifest" ]] && plutil -lint "$manifest" >/dev/null; then
      pass "$label contains valid ${manifest#$app/}"
    else
      fail "$label is missing valid required manifest ${manifest#$app/}"
    fi
  done

  local manifest_count=0
  while IFS= read -r -d '' manifest; do
    manifest_count=$((manifest_count + 1))
    plutil -lint "$manifest" >/dev/null \
      || fail "$label contains invalid privacy manifest ${manifest#$app/}"
  done < <(find "$app" -name PrivacyInfo.xcprivacy -type f -print0 2>/dev/null)
  if ((manifest_count >= ${#required_manifests[@]})); then
    pass "$label contains $manifest_count validatable privacy manifests"
  else
    fail "$label contains only $manifest_count privacy manifests"
  fi
}

format_artifact_matches() {
  local app="$1"
  sed "s#^$app/##" \
    | sed '/^$/d' \
    | LC_ALL=C sort -u \
    | head -n 12 \
    | tr '\n' ' '
}

report_layer_scan() {
  local app="$1"
  local label="$2"
  local matches="$3"
  local pattern="$4"
  if [[ -n "$matches" ]]; then
    local marker_inventory
    local matched_file
    marker_inventory="$(
      while IFS= read -r matched_file; do
        [[ -f "$matched_file" ]] || continue
        rg -a -i -o "$pattern" "$matched_file" 2>/dev/null || true
      done <<<"$matches" \
        | LC_ALL=C sort -fu \
        | head -n 12 \
        | tr '\n' ' '
    )"
    fail "$label contains forbidden marker(s) [$marker_inventory] in: $(format_artifact_matches "$app" <<<"$matches")"
  else
    pass "$label has no forbidden disabled-product or upstream runtime marker"
  fi
}

record_reviewed_binary_compatibility() {
  local binary="$1"
  local label="$2"
  local pattern="$3"
  local category="$4"
  [[ -f "$binary" ]] || return

  local marker_count
  local digest
  marker_count="$(rg -a -i -o "$pattern" "$binary" 2>/dev/null | wc -l | tr -d ' ' || true)"
  digest="$(shasum -a 256 "$binary" | awk '{print $1}')"
  if [[ "$marker_count" =~ ^[1-9][0-9]*$ ]]; then
    info "$label retains $marker_count reviewed $category compatibility marker(s); binary sha256=$digest"
  else
    pass "$label contains no reviewed $category compatibility markers (sha256=$digest)"
  fi
}

verify_sanitized_localizations() {
  local app="$1"
  local label="$2"
  local financial_keys_manifest="Scripts/app_store_lite_excluded_financial_localization_keys.txt"
  local paid_backup_keys_manifest="Scripts/app_store_lite_excluded_paid_backup_localization_keys.txt"
  local localization_count=0
  local leaked_files
  local missing_free_backup_files
  local strings_file

  while IFS= read -r -d '' strings_file; do
    localization_count=$((localization_count + 1))
  done < <(find "$app" -mindepth 2 -maxdepth 2 -type f -path '*.lproj/Localizable.strings' -print0 2>/dev/null)

  # Compare parsed plist keys against the exact reviewed manifest. This is
  # intentionally not a word/prefix scan: Kivo's local identity-wallet copy
  # and ordinary notification badges remain legitimate product resources.
  leaked_files="$(/usr/bin/ruby -rjson -ropen3 -rset -e '
    excluded = ARGV.shift(2).flat_map { |path| File.readlines(path, chomp: true) }.to_set
    ARGV.each do |path|
      json, status = Open3.capture2e("plutil", "-convert", "json", "-o", "-", path)
      unless status.success?
        puts "#{path}\tUNREADABLE"
        next
      end
      leaked = JSON.parse(json).keys.select { |key| excluded.include?(key) }.sort
      puts "#{path}\t#{leaked.first} (+#{leaked.length - 1} more)" unless leaked.empty?
    end
  ' "$financial_keys_manifest" "$paid_backup_keys_manifest" \
    "$app"/*.lproj/Localizable.strings 2>/dev/null || true)"

  # Free encrypted backup and restore remain part of App Store Lite. These
  # representative keys must survive the exact paid-product sanitization in
  # every locale, preventing a future broad backup-prefix filter.
  missing_free_backup_files="$(/usr/bin/ruby -rjson -ropen3 -e '
    required = %w[
      BACKUP_ONBOARDING_INTRO_TITLE
      BACKUP_RESTORE_MODAL_TITLE
      BACKUP_SETTINGS_MANUAL_BACKUP_BUTTON_TITLE
      CHOOSE_BACKUP_PLAN_FREE_PLAN_TITLE
      ONBOARDING_CHOOSE_RESTORE_METHOD_BACKUPS_TITLE
    ]
    ARGV.each do |path|
      json, status = Open3.capture2e("plutil", "-convert", "json", "-o", "-", path)
      unless status.success?
        puts "#{path}\tUNREADABLE"
        next
      end
      missing = required - JSON.parse(json).keys
      puts "#{path}\t#{missing.join(",")}" unless missing.empty?
    end
  ' "$app"/*.lproj/Localizable.strings 2>/dev/null || true)"

  if [[ "$localization_count" == 47 ]]; then
    pass "$label contains all 47 expected localized product catalogues"
  else
    fail "$label contains $localization_count localized product catalogues (expected 47)"
  fi
  if [[ -n "$leaked_files" ]]; then
    fail "$label still contains exact reviewed financial or paid-backup localization keys in: $(format_artifact_matches "$app" <<<"$leaked_files")"
  else
    pass "$label omits all 509 financial and 91 paid-backup localization keys"
  fi
  if [[ -n "$missing_free_backup_files" ]]; then
    fail "$label lost required free-backup or restore localization keys in: $(format_artifact_matches "$app" <<<"$missing_free_backup_files")"
  else
    pass "$label retains free-backup and restore localization keys in all 47 catalogues"
  fi
}

verify_deferred_product_localizations() {
  local app="$1"
  local label="$2"
  local deferred_keys_manifest="Scripts/app_store_lite_excluded_deferred_localization_keys.txt"
  local deferred_key
  local leaked_files

  if [[ ! -f "$deferred_keys_manifest" ]]; then
    fail "$label cannot verify deferred-product localizations because $deferred_keys_manifest is missing"
    return
  fi
  deferred_key="$(<"$deferred_keys_manifest")"

  leaked_files="$(/usr/bin/ruby -rjson -ropen3 -e '
    key = ARGV.shift
    ARGV.each do |path|
      json, status = Open3.capture2e("plutil", "-convert", "json", "-o", "-", path)
      unless status.success?
        puts "#{path}\tUNREADABLE"
        next
      end
      puts path if JSON.parse(json).key?(key)
    end
  ' "$deferred_key" "$app"/*.lproj/Localizable.strings 2>/dev/null || true)"

  if [[ -n "$leaked_files" ]]; then
    fail "$label contains deferred-product localization key $deferred_key in: $(format_artifact_matches "$app" <<<"$leaked_files")"
  else
    pass "$label omits deferred-product localization key $deferred_key"
  fi
}

verify_sanitized_plural_localizations() {
  local app="$1"
  local label="$2"
  local excluded_plural_keys_manifest="Scripts/app_store_lite_excluded_paid_backup_plural_keys.txt"
  local localization_count=0
  local leaked_files
  local strings_file

  while IFS= read -r -d '' strings_file; do
    localization_count=$((localization_count + 1))
  done < <(find "$app" -mindepth 2 -maxdepth 2 -type f -path '*.lproj/PluralAware.stringsdict' -print0 2>/dev/null)

  leaked_files="$(/usr/bin/ruby -rjson -ropen3 -rset -e '
    excluded = File.readlines(ARGV.shift, chomp: true).to_set
    ARGV.each do |path|
      json, status = Open3.capture2e("plutil", "-convert", "json", "-o", "-", path)
      unless status.success?
        puts "#{path}\tUNREADABLE"
        next
      end
      leaked = JSON.parse(json).keys.select { |key| excluded.include?(key) }.sort
      puts "#{path}\t#{leaked.join(",")}" unless leaked.empty?
    end
  ' "$excluded_plural_keys_manifest" "$app"/*.lproj/PluralAware.stringsdict 2>/dev/null || true)"

  if [[ "$localization_count" == 47 ]]; then
    pass "$label contains all 47 expected plural-aware catalogues"
  else
    fail "$label contains $localization_count plural-aware catalogues (expected 47)"
  fi
  if [[ -n "$leaked_files" ]]; then
    fail "$label still contains the paid-backup plural localization key in: $(format_artifact_matches "$app" <<<"$leaked_files")"
  else
    pass "$label omits the paid-backup plural localization key in all 47 catalogues"
  fi
}

verify_disabled_financial_resources() {
  local app="$1"
  local label="$2"
  local loose_matches
  local asset_catalog
  local asset_info
  local asset_names
  local asset_failures=""
  local forbidden_asset_name_pattern='"Name"[[:space:]]*:[[:space:]]*"(mobilecoin-24|payment|payment-28|payments-lock)"'

  loose_matches="$(find "$app" \
    \( -path "$app/Frameworks" -o -path '*/_CodeSignature' \) -prune -o \
    -type f \( \
      -name 'add-money.json' -o \
      -name 'cash-out.json' -o \
      -name 'mobilecoin-24.*' -o \
      -name 'payment.pdf' -o \
      -name 'payment-28.*' -o \
      -name 'payments-lock.*' \
    \) -print 2>/dev/null || true)"
  if [[ -n "$loose_matches" ]]; then
    fail "$label contains exact disabled financial resource files: $(format_artifact_matches "$app" <<<"$loose_matches")"
  else
    pass "$label contains no exact disabled financial resource files"
  fi

  while IFS= read -r -d '' asset_catalog; do
    asset_info="$(xcrun assetutil --info "$asset_catalog" 2>/dev/null || true)"
    if [[ -z "$asset_info" ]]; then
      asset_failures+="$asset_catalog"$'\t'"UNREADABLE"$'\n'
      continue
    fi
    asset_names="$(rg -o "$forbidden_asset_name_pattern" <<<"$asset_info" \
      | sed -E 's/^.*"(mobilecoin-24|payment|payment-28|payments-lock)"$/\1/' \
      | LC_ALL=C sort -u \
      | tr '\n' ',' \
      | sed 's/,$//' \
      || true)"
    if [[ -n "$asset_names" ]]; then
      asset_failures+="$asset_catalog"$'\t'"$asset_names"$'\n'
    fi
  done < <(find "$app" -type f -name Assets.car -print0 2>/dev/null)

  if [[ -n "$asset_failures" ]]; then
    fail "$label compiled asset catalogues contain exact disabled financial renditions: $(format_artifact_matches "$app" <<<"$asset_failures")"
  else
    pass "$label compiled asset catalogues omit mobilecoin-24, payment, payment-28, and payments-lock"
  fi
}

scan_release_artifact() {
  local app="$1"
  local label="$2"
  local main_executable="$app/Kivo"
  local forbidden_path_pattern='(^|/)(LibMobileCoin|MobileCoin)(\.framework|\.bundle|/|$)|(^|/)signal-messenger\.cer$|(^|/)(activate-payments|about-mobilecoin|paymentSpinner)\.json$|Kivo(DEX|AssetLeaderboard|PublicFeed|PublicProfile|MarketTokens|ExploreViewController|EVMChainRegistry|ExternalWallet)'
  # These are product routes/endpoints or exact product-surface assets. Generic
  # transaction/signing words are intentionally absent so WalletCore and
  # backup/protocol schemas cannot make the scan pass or fail by accident.
  local product_hard_content_pattern='https?://(support\.)?signal\.org|https?://signal\.me/|https?://signal\.group/|support@signal\.org|signal private messenger|Kivo(DEX|AssetLeaderboard|PublicFeed|PublicProfile|MarketTokens|ExploreViewController|EVMChainRegistry|ExternalWallet)|KIVO_(DEX|MARKET|SWAP|RWA|ASSET_LEADERBOARD|PUBLIC_FEED|EXTERNAL_WALLET)|tokenized[-_ ]?securit'
  local financial_endpoint_pattern='web3\.okx\.com|api\.okx\.com|api\.binance\.com'
  local hard_content_pattern="$product_hard_content_pattern|$financial_endpoint_pattern"
  local main_gated_compatibility_pattern='ATTACHMENT_KEYBOARD_PAYMENT|didTap(ActivatePayments|SendPayment)|CONTACT_SUPPORT_FILTER_(PAYMENTS|DONATIONS_AND_BADGES)(_SHORT)?|DONAT(E|ION)_ON_BEHALF_OF_A_FRIEND|SETTINGS_PAYMENTS_(LOCK_SWITCH_LABEL|SECURITY_[A-Z0-9_]+)'
  local signal_ui_engine_pattern='MobileCoinAPI|PaymentsProcessor|PaymentsReconciliation|getMobileCoinAPI|prepareOutgoingPayment|initiateOutgoingPayment|maximumPaymentAmount|updateCurrentPaymentBalanceIfNecessary'
  local signal_ui_stub_pattern='KivoDisabledPaymentReceiptAmount|PaymentActionSheets|PaymentOnboarding|PAYMENTS_LOCK_(FIRST_TIME|LOCAL_BIOMETRY)'
  local protocol_compatibility_pattern='LegacyPaymentReceiptUnavailableHelper|MobileCoinTxoIdentification|SSKProto(DataMessage|SyncMessage).*(Payment|GiftBadge)|BackupProto_(GiftBadge|PaymentNotification)|ChatItem_(GiftBadge|PaymentNotification)|ArchivedPayment|amountMob|feeMob|donationSubscriberData|payment_notification|gift_badge|DonationPermit(Response|Request)?|transactionDetails'
  local extension_compatibility_pattern='LegacyPaymentReceiptUnavailableHelper|mobileCoinHelper'
  local path_matches
  local content_matches
  local framework
  local framework_name
  local framework_matches=""
  local extension
  local extension_matches=""
  local dylib_matches=""
  local resources_matches

  path_matches="$(find "$app" -print 2>/dev/null \
    | sed "s#^$app/##" \
    | rg -i "$forbidden_path_pattern" \
    || true)"
  if [[ -n "$path_matches" ]]; then
    fail "$label contains forbidden financial/upstream paths: $(head -n 12 <<<"$path_matches" | tr '\n' ' ')"
  else
    pass "$label contains no forbidden financial/upstream paths"
  fi

  content_matches="$(rg -a -i -l "$hard_content_pattern" "$main_executable" 2>/dev/null || true)"
  report_layer_scan "$app" "$label main executable" "$content_matches" "$hard_content_pattern"

  if [[ -d "$app/PlugIns" ]]; then
    while IFS= read -r -d '' extension; do
      content_matches="$(rg -a -i -l "$hard_content_pattern" "$extension" 2>/dev/null || true)"
      [[ -z "$content_matches" ]] || extension_matches+="$content_matches"$'\n'

      local extension_executable
      extension_executable="$(plist_value "$extension/Info.plist" CFBundleExecutable)"
      if [[ "$signal_service_kit_lite_build_condition_is_present" == true &&
        "$payment_receive_gates_are_present" == true &&
        "$gift_receive_gates_are_present" == true ]]; then
        record_reviewed_binary_compatibility \
          "$extension/$extension_executable" \
          "$label $(basename "$extension")" \
          "$extension_compatibility_pattern" \
          "SignalServiceKit linkage"
      elif rg -a -i -q "$extension_compatibility_pattern" \
        "$extension/$extension_executable" 2>/dev/null; then
        fail "$label $(basename "$extension") retains legacy payment linkage without every reviewed Lite protocol gate"
      fi
    done < <(find "$app/PlugIns" -mindepth 1 -maxdepth 1 -type d -name '*.appex' -print0 2>/dev/null)
  fi
  report_layer_scan "$app" "$label app-extension layer" "$extension_matches" "$hard_content_pattern"

  if [[ -d "$app/Frameworks" ]]; then
    while IFS= read -r -d '' framework; do
      framework_name="$(basename "$framework")"
      local framework_hard_pattern="$hard_content_pattern"
      # WalletCore ships generic chain metadata and performs only local
      # address/key/transaction work for Kivo. Provider names and RPC registry
      # URLs inside that exact third-party library are not Kivo product routes.
      if [[ "$framework_name" == WalletCore.framework ||
        "$framework_name" == WalletCoreSwiftProtobuf.framework ]]; then
        framework_hard_pattern="$product_hard_content_pattern"
      fi
      content_matches="$(rg -a -i -l "$framework_hard_pattern" "$framework" 2>/dev/null || true)"
      [[ -z "$content_matches" ]] || framework_matches+="$content_matches"$'\n'

      local framework_executable
      framework_executable="$(plist_value "$framework/Info.plist" CFBundleExecutable)"
      case "$framework_name" in
        SignalServiceKit.framework)
          if [[ "$signal_service_kit_lite_build_condition_is_present" == true &&
            "$payment_receive_gates_are_present" == true &&
            "$gift_receive_gates_are_present" == true ]]; then
            record_reviewed_binary_compatibility \
              "$framework/$framework_executable" \
              "$label SignalServiceKit.framework" \
              "$protocol_compatibility_pattern" \
              "protocol/database/backup"
          elif rg -a -i -q "$protocol_compatibility_pattern" \
            "$framework/$framework_executable" 2>/dev/null; then
            fail "$label SignalServiceKit.framework retains legacy financial protocol symbols without every reviewed Lite receive gate"
          fi
          ;;
        LibSignalClient.framework)
          record_reviewed_binary_compatibility \
            "$framework/$framework_executable" \
            "$label LibSignalClient.framework" \
            "$protocol_compatibility_pattern" \
            "libsignal backup-schema"
          ;;
        SignalUI.framework)
          if rg -a -i -q "$signal_ui_engine_pattern" "$framework/$framework_executable" 2>/dev/null; then
            fail "$label SignalUI.framework still contains the legacy MobileCoin network/transaction engine"
          else
            pass "$label SignalUI.framework excludes the legacy MobileCoin network/transaction engine"
          fi
          if [[ "$signal_ui_lite_build_condition_is_present" == true &&
            "$signal_ui_payment_engine_is_excluded" == true ]]; then
            record_reviewed_binary_compatibility \
              "$framework/$framework_executable" \
              "$label SignalUI.framework" \
              "$signal_ui_stub_pattern" \
              "compile-time-disabled UI stub"
          elif rg -a -i -q "$signal_ui_stub_pattern" \
            "$framework/$framework_executable" 2>/dev/null; then
            fail "$label SignalUI.framework retains payment UI compatibility symbols without the reviewed Lite compile boundary"
          fi
          ;;
        WalletCore.framework|WalletCoreSwiftProtobuf.framework)
          record_reviewed_binary_compatibility \
            "$framework/$framework_executable" \
            "$label $framework_name" \
            'SigningInput|TransactionCompiler|CoinType|HDWallet|PrivateKey|PublicKey' \
            "generic offline wallet/signing-library"
          ;;
      esac
    done < <(find "$app/Frameworks" -mindepth 1 -maxdepth 1 -type d -name '*.framework' -print0 2>/dev/null)
  fi
  report_layer_scan "$app" "$label framework layer" "$framework_matches" "$hard_content_pattern"

  local dylib
  while IFS= read -r -d '' dylib; do
    content_matches="$(rg -a -i -l "$hard_content_pattern" "$dylib" 2>/dev/null || true)"
    [[ -z "$content_matches" ]] || dylib_matches+="$content_matches"$'\n'
  done < <(find "$app" -type f -name '*.dylib' -print0 2>/dev/null)
  report_layer_scan "$app" "$label standalone dylib layer" "$dylib_matches" "$hard_content_pattern"

  resources_matches="$(find "$app" \
    \( -path "$app/Frameworks" -o -path "$app/PlugIns" -o -path '*/_CodeSignature' \) -prune -o \
    -type f ! -path "$main_executable" ! -name '*.dylib' \
    -exec rg -a -i -l "$hard_content_pattern" {} + 2>/dev/null \
    || true)"
  report_layer_scan "$app" "$label first-party resource layer" "$resources_matches" "$hard_content_pattern"

  verify_sanitized_localizations "$app" "$label"
  verify_deferred_product_localizations "$app" "$label"
  verify_sanitized_plural_localizations "$app" "$label"
  verify_disabled_financial_resources "$app" "$label"

  if [[ "$app_store_lite_build_condition_is_present" == true &&
    "$main_financial_sources_are_excluded" == true &&
    "$signal_ui_payment_engine_is_excluded" == true &&
    "$payment_receive_gates_are_present" == true &&
    "$payment_ui_gates_are_present" == true &&
    "$gift_receive_gates_are_present" == true &&
    "$gift_ui_gates_are_present" == true ]]; then
    record_reviewed_binary_compatibility \
      "$main_executable" \
      "$label main executable" \
      "$main_gated_compatibility_pattern" \
      "compile-time/source-gated legacy message-schema"
  elif rg -a -i -q "$main_gated_compatibility_pattern" "$main_executable" 2>/dev/null; then
    fail "$label main executable retains legacy payment UI markers without every reviewed Lite source gate"
  fi
}

verify_release_app() {
  local app="$1"
  local label="$2"

  if [[ ! -d "$app" ]]; then
    fail "$label does not contain Kivo.app"
    return
  fi

  if codesign --verify --deep --strict "$app" >/dev/null 2>&1; then
    pass "$label passes codesign --verify --deep --strict"
  else
    fail "$label fails codesign --verify --deep --strict"
  fi

  verify_distribution_bundle "$app" "$label main app" \
    com.zhiera.kivo Kivo "Kivo App Store TF" true
  verify_distribution_bundle "$app/PlugIns/SignalNSE.appex" "$label notification extension" \
    com.zhiera.kivo.notification-service SignalNSE "Kivo NSE App Store TF" false
  verify_distribution_bundle "$app/PlugIns/SignalShareExtension.appex" "$label share extension" \
    com.zhiera.kivo.share-extension SignalShareExtension "Kivo Share App Store TF" false

  local extension_count
  extension_count="$(find "$app/PlugIns" -mindepth 1 -maxdepth 1 -type d -name '*.appex' 2>/dev/null \
    | wc -l \
    | tr -d ' ')"
  if [[ "$extension_count" == 2 ]]; then
    pass "$label contains exactly the reviewed NSE and Share extensions"
  else
    fail "$label contains $extension_count app extensions (expected exactly 2)"
  fi

  local expected_archive_commit
  local artifact_commit
  expected_archive_commit="$(git rev-parse --short=12 HEAD) (App Store Build $release_build)"
  artifact_commit="$(plist_value "$app/Info.plist" BuildDetails:KivoCommit)"
  verify_expected_value "$label BuildDetails source mapping" \
    "$artifact_commit" "$expected_archive_commit"

  verify_privacy_manifests "$app" "$label"
  scan_release_artifact "$app" "$label"
}

verify_frozen_source() {
  local source_status
  local release_tag
  source_status="$(git status --porcelain=v1 --untracked-files=all \
    --ignore-submodules=none -- . ':(exclude)Pods')"
  if [[ -z "$source_status" ]]; then
    pass "release source tree is clean apart from verified CocoaPods-generated files"
  else
    fail "release source tree contains uncommitted source changes: $source_status"
  fi

  release_tag="$(git tag --points-at HEAD | grep -E '^kivo-ios-[0-9]' | head -n 1 || true)"
  if [[ -z "$release_tag" ]]; then
    fail "HEAD has no exact kivo-ios-* release tag"
    return
  fi
  if [[ "$(git cat-file -t "refs/tags/$release_tag" 2>/dev/null || true)" == tag ]]; then
    pass "HEAD has annotated release tag $release_tag"
  else
    fail "$release_tag is not an annotated release tag"
  fi
}

verify_localization_manifest_contract() {
  local path="$1"
  local expected_count="$2"
  local expected_sha256="$3"
  local label="$4"

  if [[ ! -f "$path" ]]; then
    fail "$label manifest is missing at $path"
    return
  fi

  local line_count
  local unique_count
  local actual_sha256
  line_count="$(wc -l <"$path" | tr -d ' ')"
  unique_count="$(LC_ALL=C sort -u "$path" | wc -l | tr -d ' ')"
  actual_sha256="$(shasum -a 256 "$path" | awk '{print $1}')"

  if [[ "$line_count" == "$expected_count" ]] &&
    [[ "$unique_count" == "$expected_count" ]] &&
    LC_ALL=C sort -c "$path" >/dev/null 2>&1 &&
    [[ "$actual_sha256" == "$expected_sha256" ]]; then
    pass "$label manifest is the reviewed sorted unique $expected_count-key set (sha256=$actual_sha256)"
  else
    fail "$label manifest contract mismatch (lines=$line_count unique=$unique_count sha256=$actual_sha256)"
  fi
}

verify_app_store_resource_sanitizer_phase() {
  local project_file="Signal.xcodeproj/project.pbxproj"
  local signal_target_block
  local signal_app_store_configuration
  local sanitizer_block
  local resource_phase_line
  local sanitizer_phase_line

  signal_target_block="$(sed -n \
    '/D221A088169C9E5E00537ABF \/\* Signal \*\/ = {/,/^\t\t};/p' \
    "$project_file")"
  sanitizer_block="$(sed -n \
    '/A12B12C0FFEE120000000001 \/\* Run Script: sanitize App Store resources \*\/ = {/,/^\t\t};/p' \
    "$project_file")"
  signal_app_store_configuration="$(sed -n \
    '/D221A0BE169C9E5F00537ABF \/\* App Store Release \*\/ = {/,/^\t\t};/p' \
    "$project_file")"
  resource_phase_line="$(grep -n -F 'D221A087169C9E5E00537ABF /* Resources */' \
    <<<"$signal_target_block" | cut -d: -f1 || true)"
  sanitizer_phase_line="$(grep -n -F 'A12B12C0FFEE120000000001 /* Run Script: sanitize App Store resources */' \
    <<<"$signal_target_block" | cut -d: -f1 || true)"

  if [[ -n "$resource_phase_line" && -n "$sanitizer_phase_line" &&
    "$sanitizer_phase_line" -gt "$resource_phase_line" ]]; then
    pass "Signal target runs the App Store resource sanitizer after Copy Bundle Resources"
  else
    fail "Signal target does not run the App Store resource sanitizer after Copy Bundle Resources"
  fi

  if grep -F -q 'name = "Run Script: sanitize App Store resources";' <<<"$sanitizer_block" &&
    grep -F -q 'shellScript = "/usr/bin/ruby \"${PROJECT_DIR}/Scripts/sanitize_app_store_resources.rb\"\n";' \
      <<<"$sanitizer_block"; then
    pass "resource sanitizer build phase invokes the reviewed repository script"
  else
    fail "resource sanitizer build phase is missing or invokes an unreviewed command"
  fi

  if /usr/bin/ruby -c Scripts/sanitize_app_store_resources.rb >/dev/null 2>&1; then
    pass "App Store resource sanitizer has valid Ruby syntax"
  else
    fail "App Store resource sanitizer has invalid Ruby syntax"
  fi

  local expected_legacy_asset_paths=(
    Signal/LegacyPayments.xcassets/Contents.json
    Signal/LegacyPayments.xcassets/mobilecoin-24.imageset/Contents.json
    Signal/LegacyPayments.xcassets/mobilecoin-24.imageset/mobilecoin-24.pdf
    Signal/LegacyPayments.xcassets/payment.imageset/Contents.json
    Signal/LegacyPayments.xcassets/payment.imageset/payment.pdf
    Signal/LegacyPayments.xcassets/payment-28.imageset/Contents.json
    Signal/LegacyPayments.xcassets/payment-28.imageset/payment-28.pdf
    Signal/LegacyPayments.xcassets/payments-lock.imageset/Contents.json
    Signal/LegacyPayments.xcassets/payments-lock.imageset/payments-lock.svg
  )
  local missing_legacy_assets=""
  local legacy_asset_path
  for legacy_asset_path in "${expected_legacy_asset_paths[@]}"; do
    [[ -f "$legacy_asset_path" ]] || missing_legacy_assets+="$legacy_asset_path"$'\n'
  done

  if [[ -z "$missing_legacy_assets" ]] &&
    [[ ! -e Signal/Images.xcassets/mobilecoin-24.imageset ]] &&
    [[ ! -e Signal/Images.xcassets/payments-lock.imageset ]] &&
    [[ ! -e Signal/Symbols.xcassets/payment/payment.imageset ]] &&
    [[ ! -e Signal/Symbols.xcassets/payment/payment-28.imageset ]] &&
    grep -F -q 'LegacyPayments.xcassets in Resources' "$project_file"; then
    pass "legacy payment assets are retained in a dedicated non-Lite compatibility catalogue"
  else
    fail "legacy payment assets are missing, duplicated in shared catalogues, or not in the resource phase: $(tr '\n' ' ' <<<"$missing_legacy_assets")"
  fi

  if grep -F -q 'LegacyPayments.xcassets,' <<<"$signal_app_store_configuration" &&
    grep -E -q '"?add-money\.json"?,' <<<"$signal_app_store_configuration" &&
    grep -E -q '"?cash-out\.json"?,' <<<"$signal_app_store_configuration"; then
    pass "App Store Release excludes the dedicated payment catalogue and payment animations without breaking other configurations"
  else
    fail "App Store Release does not exclude every reviewed legacy payment resource"
  fi

  if grep -F -q 'case settingsWallet' SignalUI/Appearance/Theme+Icons.swift &&
    grep -F -A2 -q 'case .settingsWallet:' SignalUI/Appearance/Theme+Icons.swift &&
    ! grep -F -A2 'case .settingsWallet:' SignalUI/Appearance/Theme+Icons.swift | grep -F -q 'return "payment"' &&
    grep -F -q 'icon: .settingsWallet' Signal/src/ViewControllers/AppSettings/AppSettingsViewController.swift &&
    [[ "$(grep -F -c 'icon: .settingsWallet' Signal/Kivo/KivoWalletSettingsViewController.swift)" -eq 2 ]]; then
    pass "App Store Lite wallet surfaces use a non-legacy key icon retained in the shared asset catalogue"
  else
    fail "App Store Lite wallet surfaces still depend on the excluded legacy payment icon"
  fi

  if grep -F -A70 '34A954C4271A471300B05242 /* App Store Release */' "$project_file" | grep -F -q 'TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Kivo.app/Kivo";' &&
    grep -F -A70 'D221A0C1169C9E5F00537ABF /* App Store Release */' "$project_file" | grep -F -q 'BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/Kivo.app/Kivo";' &&
    grep -F -A70 'F9C5C8B1289451B900548EEE /* App Store Release */' "$project_file" | grep -F -q 'TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Kivo.app/Kivo";'; then
    pass "App Store Release test bundles target the Kivo executable"
  else
    fail "one or more App Store Release test bundles still target the obsolete Signal executable"
  fi

  lite_test_mobilecoin_block="$(sed -n '/^#if KIVO_APP_STORE_LITE$/,/^#endif$/p' \
    SignalServiceKit/TestUtils/MockSSKEnvironment.swift)"

  if [[ -x Scripts/test_kivo_app_store_lite_backup_settings ]] &&
    [[ -f Signal.xcodeproj/xcshareddata/xcschemes/Kivo-Backup-Tests.xcscheme ]] &&
    grep -F -q 'buildConfiguration = "Testable Release"' \
      Signal.xcodeproj/xcshareddata/xcschemes/Kivo-Backup-Tests.xcscheme &&
    grep -F -q -- '-configuration "App Store Release"' \
      Scripts/test_kivo_app_store_lite_backup_settings &&
    grep -F -q 'ENABLE_TESTABILITY=YES' \
      Scripts/test_kivo_app_store_lite_backup_settings &&
    grep -F -q 'EXCLUDED_SOURCE_FILE_NAMES=$(inherited) DonationReceiptCredentialRedemptionJobFinderTest.swift OWSRequestFactoryTest.swift PendingMonthlyIDEALDonationCodableTest.swift StripeTest.swift' \
      Scripts/test_kivo_app_store_lite_backup_settings &&
    grep -F -q 'OTHER_SWIFT_FLAGS=$(inherited) -DTESTABLE_BUILD' \
      Scripts/test_kivo_app_store_lite_backup_settings &&
    grep -F -q 'GCC_PREPROCESSOR_DEFINITIONS=$(inherited) TESTABLE_BUILD=1' \
      Scripts/test_kivo_app_store_lite_backup_settings &&
    grep -F -q 'only-testing:SignalServiceKitTests/BackupSettingsStoreTests' \
      Scripts/test_kivo_app_store_lite_backup_settings &&
    grep -F -q 'mobileCoinHelper = LegacyPaymentReceiptUnavailableHelper()' \
      <<<"$lite_test_mobilecoin_block" &&
    grep -F -q 'mobileCoinHelper = MobileCoinHelperMock()' \
      <<<"$lite_test_mobilecoin_block" &&
    grep -F -q 'SetCurrentAppContext(TestAppContext(), isRunningTests: true)' \
      SignalServiceKit/tests/MessageBackup/BackupSettingsStoreTests.swift; then
    pass "Lite backup tests use an invocation-only testability override without weakening the shipping configuration"
  else
    fail "Lite backup test harness is missing or can pollute the shipping App Store configuration"
  fi

  verify_localization_manifest_contract \
    "Scripts/app_store_lite_excluded_financial_localization_keys.txt" \
    509 \
    "127402bda7956488c6823dbcffa1199f3a0628f8a382f3a3bea50d8b73d279ab" \
    "financial localization"
  verify_localization_manifest_contract \
    "Scripts/app_store_lite_excluded_paid_backup_localization_keys.txt" \
    91 \
    "19c7c3b5950a29e4aac256a0e2288aa5402d5ad01b2e267e107d16722d57f562" \
    "paid-backup localization"
  verify_localization_manifest_contract \
    "Scripts/app_store_lite_excluded_paid_backup_plural_keys.txt" \
    1 \
    "9f96a9b97d6cf582972977d0687d7e0ca2b7b41d01086a87fb6d28c84c015b74" \
    "paid-backup plural localization"
  verify_localization_manifest_contract \
    "Scripts/app_store_lite_excluded_deferred_localization_keys.txt" \
    1 \
    "e1d3333f3be4e9a08514bc42de19bb83f9acf19a2c161ee74a3d73612dde8e21" \
    "deferred-product localization"

  if /usr/bin/ruby -e '
    source = File.read(ARGV.fetch(0))
    manifests = {
      "app_store_lite_excluded_financial_localization_keys.txt" => 509,
      "app_store_lite_excluded_paid_backup_localization_keys.txt" => 91,
      "app_store_lite_excluded_paid_backup_plural_keys.txt" => 1,
      "app_store_lite_excluded_deferred_localization_keys.txt" => 1,
    }
    manifests.each do |filename, expected_count|
      pattern = /filename:\s*"#{Regexp.escape(filename)}",\s*expected_count:\s*#{expected_count},/m
      abort "missing manifest contract for #{filename}" unless source.match?(pattern)
    end
    required_contracts = [
      /excluded_keys\s*=\s*financial_keys\s*\+\s*paid_backup_keys\s*\+\s*deferred_product_keys/,
      /App Store Lite Localizable\.strings manifests overlap/,
      /Dir\.glob\(File\.join\(resources_path, "\*\.lproj", "Localizable\.strings"\)\)\.sort/,
      /Dir\.glob\(File\.join\(resources_path, "\*\.lproj", "PluralAware\.stringsdict"\)\)\.sort/,
      /Expected paid-backup plural keys missing before sanitizing/,
    ]
    abort "sanitizer does not apply every reviewed manifest" unless required_contracts.all? { |pattern| source.match?(pattern) }
  ' Scripts/sanitize_app_store_resources.rb; then
    pass "resource sanitizer applies all four reviewed manifests to Localizable and PluralAware catalogues"
  else
    fail "resource sanitizer does not reference every reviewed manifest/count or both localization catalogue types"
  fi
}

verify_paid_backup_lite_source_boundary() {
  local paid_backup_keys_manifest="Scripts/app_store_lite_excluded_paid_backup_localization_keys.txt"
  local lite_source
  local paid_key_matches
  local paid_runtime_matches
  local required_pattern
  local missing_free_source=""
  local source_files=(
    Signal/Backups/BackupEnablingManager.swift
    Signal/Backups/BackupPlanOptionView.swift
    Signal/Backups/BackupPlanTermsAndConditionsView.swift
    Signal/Backups/BackupPlanUpsellConfiguration.swift
    Signal/Backups/BackupPlanUpsellViewController.swift
    Signal/Backups/BackupSettingsViewController.swift
    Signal/Backups/BackupSubscriptionAlreadyRedeemedSheet.swift
    Signal/Backups/ChooseBackupPlanViewController.swift
    Signal/Backups/LinkedDeviceBackupSettingsViewController.swift
    Signal/src/ViewControllers/AppSettings/AppSettingsViewController.swift
    SignalServiceKit/Backups/Settings/BackupPlanManager.swift
    SignalServiceKit/Backups/Settings/BackupSettingsStore.swift
    SignalServiceKit/Environment/AppSetup.swift
  )

  # clang's preprocessing pass evaluates these Swift #if blocks without
  # compiling dependencies. Scan the exact code that remains when the Lite
  # condition is true, rather than treating protocol/state compatibility names
  # such as BackupPlan.paid as reachable paid UI.
  if lite_source="$({
    local source_file
    for source_file in "${source_files[@]}"; do
      xcrun clang -E -P -x c -DKIVO_APP_STORE_LITE "$source_file" || exit 1
    done
  } 2>/dev/null)"; then
    pass "paid-backup sources preprocess successfully for KIVO_APP_STORE_LITE"
  else
    fail "paid-backup sources could not be preprocessed for the Lite boundary"
    return
  fi

  paid_key_matches="$(rg -n -F -f "$paid_backup_keys_manifest" <<<"$lite_source" || true)"
  if [[ -n "$paid_key_matches" ]]; then
    fail "Lite-active backup source still references paid-only localization keys: $(head -n 12 <<<"$paid_key_matches" | tr '\n' ' ')"
  else
    pass "Lite-active backup source references none of the 91 paid-only localization keys"
  fi

  local paid_runtime_pattern='import StoreKit|struct[[:space:]]+BackupPlanPaidOptionView|struct[[:space:]]+BackupPlanTermsAndConditionsView|class[[:space:]]+BackupPlanUpsellViewController|struct[[:space:]]+BackupPlanUpsellView|class[[:space:]]+BackupSubscriptionAlreadyRedeemedSheet|struct[[:space:]]+BackupSubscriptionLoadedView|struct[[:space:]]+PaidSubscription|func[[:space:]]+addPaidHeroContent|BackupPlanPaidOptionView[[:space:]]*\(|BackupPlanTermsAndConditionsView[[:space:]]*\(|(^|[^[:alnum:]_])BackupPlanUpsellViewController[[:space:]]*(\.|\()|(^|[^[:alnum:]_])BackupSubscriptionAlreadyRedeemedSheet[[:space:]]*\(|purchaseNewSubscription[[:space:]]*\(|redeemSubscriptionIfNecessary[[:space:]]*\(|subscriptionDisplayPrice[[:space:]]*\(|SubscriptionFetcher[[:space:]]*\(|AppStore[[:space:]]*\.[[:space:]]*showManageSubscriptions'
  paid_runtime_matches="$(rg -n "$paid_runtime_pattern" <<<"$lite_source" || true)"
  if [[ -n "$paid_runtime_matches" ]]; then
    fail "Lite-active backup source still contains paid StoreKit/subscription UI or network entrypoints: $(head -n 12 <<<"$paid_runtime_matches" | tr '\n' ' ')"
  else
    pass "Lite-active backup source excludes paid StoreKit/subscription UI and network entrypoints"
  fi

  for required_pattern in \
    'struct BackupPlanFreeOptionView' \
    'await setBackupPlan { _ in .free }' \
    'viewModel.performManualBackup()' \
    'backupsViewController = BackupOnboardingCoordinator(' \
    'backupType: .remote,'
  do
    if ! grep -F -q "$required_pattern" <<<"$lite_source"; then
      missing_free_source+="$required_pattern"$'\n'
    fi
  done
  if [[ -n "$missing_free_source" ]]; then
    fail "Lite source lost reviewed free-backup entrypoint(s): $(tr '\n' ' ' <<<"$missing_free_source")"
  else
    pass "Lite source retains free-plan selection, manual backup, and the Settings backup entrypoint"
  fi

  if rg -q 'case remote' Signal/Registration/UserInterface/RegistrationChooseRestoreMethodViewController.swift &&
    rg -q 'presenter\?\.didChooseRestoreMethod\(method: \.remote\)' \
      Signal/Registration/UserInterface/RegistrationChooseRestoreMethodViewController.swift &&
    rg -q 'RegistrationChooseRestoreMethodViewController\.swift in Sources' \
      Signal.xcodeproj/project.pbxproj; then
    pass "free remote-backup restore remains reachable during registration"
  else
    fail "free remote-backup restore source or App Store target membership is missing"
  fi

  if grep -F -q 'var appStoreLiteCompatiblePlan: BackupPlan {' \
    SignalServiceKit/Backups/Settings/BackupSettingsStore.swift &&
    grep -F -q 'case .paid, .paidExpiringSoon, .paidAsTester:' \
      SignalServiceKit/Backups/Settings/BackupSettingsStore.swift &&
    grep -F -q 'return persistedBackupPlan(tx: tx).appStoreLiteCompatiblePlan' \
      SignalServiceKit/Backups/Settings/BackupSettingsStore.swift &&
    grep -F -q 'let newBackupPlan = newBackupPlan.appStoreLiteCompatiblePlan' \
      SignalServiceKit/Backups/Settings/BackupSettingsStore.swift &&
    grep -F -q 'func migrateAppStoreLiteBackupPlanIfNeeded' \
      SignalServiceKit/Backups/Settings/BackupPlanManager.swift &&
    grep -F -q 'oldBackupPlan: persistedBackupPlan' \
      SignalServiceKit/Backups/Settings/BackupPlanManager.swift &&
    grep -F -q 'backupPlanManager.migrateAppStoreLiteBackupPlanIfNeeded' \
      SignalServiceKit/Environment/AppSetup.swift; then
    pass "Lite backup plans fail closed to free and legacy paid/offload state is migrated at launch"
  else
    fail "Lite backup plan normalization or launch migration is incomplete"
  fi

  if grep -F -q 'let backupPlan = restoredBackupPlan.appStoreLiteCompatiblePlan' \
      SignalServiceKit/Backups/Archiving/Archivers/AccountData/BackupArchiveAccountDataArchiver.swift &&
    grep -F -q 'context.backupPlan = backupPlan' \
      SignalServiceKit/Backups/Archiving/Archivers/AccountData/BackupArchiveAccountDataArchiver.swift; then
    pass "Lite backup restore uses one normalized plan for persistence and attachment eligibility"
  else
    fail "Lite backup restore can diverge between its persisted plan and attachment eligibility context"
  fi

  local lite_paid_identity_source
  local paid_identity_files=(
    SignalServiceKit/Backups/Archiving/Archivers/AccountData/BackupArchiveAccountDataArchiver.swift
    SignalServiceKit/StorageService/StorageServiceProto+Sync.swift
    Signal/AppLaunch/AppEnvironment.swift
    SignalServiceKit/ZeroKnowledge/BackupAuthCredentialManager.swift
  )
  if lite_paid_identity_source="$({
    local source_file
    for source_file in "${paid_identity_files[@]}"; do
      xcrun clang -E -P -x c -DKIVO_APP_STORE_LITE "$source_file" || exit 1
    done
  } 2>/dev/null)"; then
    local paid_identity_matches
    paid_identity_matches="$(rg -n \
      'backupSubscriptionManager\.(getIAPSubscriberData|restoreIAPSubscriberData|redeemSubscriptionIfNecessary)|waitForAuthCredentialDependency\(\.(renewBackupEntitlementForTestFlight|redeemBackupSubscriptionViaIAP)' \
      <<<"$lite_paid_identity_source" || true)"
    if [[ -n "$paid_identity_matches" ]]; then
      fail "Lite-active backup paths still archive, restore, sync, or redeem paid subscriber identity: $(head -n 12 <<<"$paid_identity_matches" | tr '\n' ' ')"
    else
      pass "Lite-active backup paths exclude paid subscriber identity and entitlement redemption"
    fi
  else
    fail "paid-backup identity paths could not be preprocessed for the Lite boundary"
  fi

  local lite_subscription_manager_source
  if lite_subscription_manager_source="$(
    xcrun clang -E -P -x c -DKIVO_APP_STORE_LITE \
      SignalServiceKit/Subscriptions/Backups/BackupSubscriptionManager.swift 2>/dev/null
  )" &&
    grep -F -q 'func getIAPSubscriberData(tx: DBReadTransaction) -> IAPSubscriberData? { nil }' \
      <<<"$lite_subscription_manager_source" &&
    grep -F -q 'func restoreIAPSubscriberData(_ iapSubscriberData: IAPSubscriberData, tx: DBWriteTransaction) {}' \
      <<<"$lite_subscription_manager_source" &&
    ! grep -F -q 'KeyValueStore(collection: "BackupSubscriptionManager")' \
      <<<"$lite_subscription_manager_source"; then
    pass "Lite subscription compatibility manager ignores legacy and restored IAP identity"
  else
    fail "Lite subscription compatibility manager can still read or persist IAP identity"
  fi
}

if [[ "$submission_mode" == true && -z "$archive_path" ]]; then
  fail "--submission requires --archive /absolute/path/to/Kivo.xcarchive"
fi

ringrtc_setup_line="$(
  grep -n -F 'Pods/SignalRingRTC/bin/set-up-for-cocoapods' \
    Scripts/bootstrap_kivo_open_source_dependencies |
    head -n 1 |
    cut -d: -f1 || true
)"
pod_install_line="$(
  grep -n -F 'bundle exec pod install' Scripts/bootstrap_kivo_open_source_dependencies |
    head -n 1 |
    cut -d: -f1 || true
)"

if grep -q "LIBSIGNAL_COMMIT=\"ed3a21d54b0caf1684eb1bf1dba62fff64c784ef\"" Scripts/bootstrap_kivo_open_source_dependencies &&
  grep -q "support_files_group.source_tree = 'SOURCE_ROOT'" Podfile &&
  [[ "$ringrtc_setup_line" =~ ^[0-9]+$ ]] &&
  [[ "$pod_install_line" =~ ^[0-9]+$ ]] &&
  ((ringrtc_setup_line < pod_install_line)); then
  pass "open-source bootstrap prepares RingRTC before CocoaPods and keeps LibSignalClient paths relocatable"
else
  fail "open-source dependency order or relocatable LibSignalClient path fix is missing"
fi

if Scripts/verify_kivo_pods_install; then
  pass "Pods matches the frozen gitlink, lockfile, generated paths, and canonical digest"
else
  fail "Pods installation does not match the frozen Build 12 dependency state"
fi

expected_locales="en-US ms th zh-Hans zh-Hant"
actual_locales="$(find fastlane/metadata -mindepth 1 -maxdepth 1 -type d -not -empty -exec basename {} \; | LC_ALL=C sort | tr '\n' ' ' | sed 's/ $//')"
if [[ "$actual_locales" == "$expected_locales" ]]; then
  pass "App Store metadata contains only reviewed locales"
else
  fail "metadata locales are '$actual_locales' (expected '$expected_locales')"
fi

required_metadata="description.txt keywords.txt name.txt privacy_url.txt release_notes.txt subtitle.txt support_url.txt"
for locale in $expected_locales; do
  for file in $required_metadata; do
    [[ -s "fastlane/metadata/$locale/$file" ]] || fail "missing fastlane/metadata/$locale/$file"
  done

  name="$(<"fastlane/metadata/$locale/name.txt")"
  subtitle="$(<"fastlane/metadata/$locale/subtitle.txt")"
  keywords="$(<"fastlane/metadata/$locale/keywords.txt")"
  description="$(<"fastlane/metadata/$locale/description.txt")"
  release_notes="$(<"fastlane/metadata/$locale/release_notes.txt")"
  ((${#name} <= 30)) || fail "$locale app name exceeds 30 characters"
  ((${#subtitle} <= 30)) || fail "$locale subtitle exceeds 30 characters"
  ((${#keywords} <= 100)) || fail "$locale keywords exceed 100 characters"
  ((${#description} <= 4000)) || fail "$locale description exceeds 4000 characters"
  ((${#release_notes} <= 4000)) || fail "$locale release notes exceed 4000 characters"
done
pass "metadata field lengths fit App Store limits"

if grep -R -E -i -q 'support\.signal\.org|github\.com/signalapp|\.invalid([/:]|$)|signal private messenger' fastlane/metadata; then
  fail "metadata still contains an upstream or placeholder destination"
else
  pass "metadata has no upstream support or placeholder destinations"
fi

for plist in \
  Signal/Signal-Info.plist \
  Signal/PrivacyInfo.xcprivacy \
  SignalNSE/Info.plist \
  SignalNSE/PrivacyInfo.xcprivacy \
  SignalShareExtension/Info.plist \
  SignalShareExtension/PrivacyInfo.xcprivacy
do
  if plutil -lint "$plist" >/dev/null; then
    pass "$plist is valid"
  else
    fail "$plist is invalid"
  fi
done

if rg -q '<string>NSPrivacyCollectedDataTypePreciseLocation</string>' Signal/PrivacyInfo.xcprivacy; then
  pass "main privacy manifest declares user-selected precise-location sharing"
else
  fail "location sharing is reachable but the main privacy manifest omits Precise Location"
fi

invalid_strings=0
while IFS= read -r strings_file; do
  if ! plutil -lint "$strings_file" >/dev/null; then
    printf 'Invalid strings file: %s\n' "$strings_file" >&2
    invalid_strings=$((invalid_strings + 1))
  fi
done < <(find Signal/translations -name '*.strings' -type f -print)
if ((invalid_strings == 0)); then
  pass "all localized .strings files are valid"
else
  fail "$invalid_strings localized .strings files are invalid"
fi

camera_qr_localization_count=0
while IFS= read -r camera_strings_file; do
  camera_description="$(
    /usr/libexec/PlistBuddy -c 'Print :NSCameraUsageDescription' \
      "$camera_strings_file" 2>/dev/null || true
  )"
  case "$camera_description" in
    *QR* | *คิวอาร์โค้ด* | *二維碼* | *二维码*)
      camera_qr_localization_count=$((camera_qr_localization_count + 1))
      ;;
  esac
done < <(find Signal/translations -name InfoPlist.strings -type f -print | LC_ALL=C sort)
if [[ "$camera_qr_localization_count" == "47" ]]; then
  pass "camera permission describes QR scanning in all 47 localizations"
else
  fail "camera permission describes QR scanning in $camera_qr_localization_count of 47 localizations"
fi

for key in \
  KIVO_DELETE_ACCOUNT_PASSWORD_PLACEHOLDER \
  KIVO_DELETE_ACCOUNT_TITLE \
  KIVO_DELETE_ACCOUNT_DESCRIPTION \
  KIVO_DELETE_ACCOUNT_CONFIRM_TITLE
do
  count="$(rg -l "^\"${key}\" =" Signal/translations --glob 'Localizable.strings' | wc -l | tr -d ' ')"
  if [[ "$count" == "47" ]]; then
    pass "$key exists in all 47 localizations"
  else
    fail "$key exists in $count of 47 localizations"
  fi
done

open_source_locales=(en ms th zh_CN zh_HK zh_TW)
open_source_localization_count=0
for locale in "${open_source_locales[@]}"; do
  if rg -q '^"KIVO_OPEN_SOURCE_CELL" =' "Signal/translations/$locale.lproj/Localizable.strings"; then
    open_source_localization_count=$((open_source_localization_count + 1))
  fi
done
if ((open_source_localization_count == ${#open_source_locales[@]})) &&
  rg -q 'KivoURLs\.sourceCode' Signal/src/ViewControllers/AppSettings/HelpViewController.swift; then
  pass "About exposes the Kivo source page in primary release languages"
else
  fail "About source-page link or a primary release localization is missing"
fi

if rg -q 'static let newDeviceServiceIdentifier = "kivo-new-device"' \
    Signal/DeviceTransfer/DeviceTransfer.swift &&
  /usr/libexec/PlistBuddy -c 'Print :NSBonjourServices:0' Signal/Signal-Info.plist 2>/dev/null \
    | grep -Fxq '_kivo-new-device._tcp'; then
  pass "device-transfer Bonjour runtime and Info.plist identifiers match Kivo"
else
  fail "device-transfer Bonjour runtime and Info.plist identifiers do not match"
fi

if rg -q 'UIApplication\.shared\.open\(KivoURLs\.support\)' \
    Signal/QuickRestore/OutgoingDeviceRestoreInitialViewController.swift &&
  ! rg -q 'TODO: link to documentation' Signal; then
  pass "Quick Restore help resolves to Kivo support with no placeholder URL"
else
  fail "Quick Restore still has a missing or placeholder help URL"
fi

if rg -q 'Sticker-pack links are not available' release/app-store/public/addstickers/index.html &&
  ! rg -q 'data-open-kivo|preview the pack before installing|Discover and install' \
    release/app-store/public/addstickers/index.html &&
  ! rg -q '"/addstickers/"[[:space:]]*:' release/app-store/public/assets/open-kivo.js; then
  pass "local public site matches Lite external sticker-pack boundary"
else
  fail "local public site still advertises external sticker-pack installation"
fi

if ! rg -q 'github\.com/sowhat1010/Signal-iOS/issues' \
    release/app-store/public/support/index.html; then
  pass "public support page does not send users to an upstream-branded issue tracker"
else
  fail "public support page still links the upstream-branded issue tracker"
fi

for url in \
  https://kivo.it389.com/healthz \
  https://kivo.it389.com/privacy \
  https://kivo.it389.com/terms \
  https://kivo.it389.com/support \
  https://kivo.it389.com/source \
  https://kivo.it389.com/u/ \
  https://kivo.it389.com/group/ \
  https://kivo.it389.com/call/ \
  https://kivo.it389.com/addstickers/
do
  if curl --fail --location --silent --show-error --max-time 15 --output /dev/null "$url"; then
    pass "$url is reachable"
  else
    fail "$url is not reachable"
  fi
done

for page in privacy support; do
  if curl --fail --location --silent --show-error --max-time 15 \
    "https://kivo.it389.com/$page/" \
    | grep -F -q 'forgotten wallet password does not block'; then
    pass "public $page page matches the password-free account deletion flow"
  else
    fail "public $page page still contradicts the password-free account deletion flow"
  fi
done

build_settings="$(mktemp)"
signal_ui_build_settings="$(mktemp)"
signal_service_kit_build_settings="$(mktemp)"
cleanup() {
  rm -f "$build_settings" "$signal_ui_build_settings" "$signal_service_kit_build_settings"
  if [[ -n "$ipa_temp_dir" && -d "$ipa_temp_dir" ]]; then
    rm -rf "$ipa_temp_dir"
  fi
}
trap cleanup EXIT
if xcodebuild \
  -workspace Signal.xcworkspace \
  -scheme Signal \
  -configuration 'App Store Release' \
  -destination 'generic/platform=iOS' \
  -showBuildSettings >"$build_settings"; then
  grep -Eq '^ *PRODUCT_BUNDLE_IDENTIFIER = com\.zhiera\.kivo$' "$build_settings" \
    && pass "Release bundle identifier is com.zhiera.kivo" \
    || fail "Release bundle identifier is not com.zhiera.kivo"
  grep -Eq '^ *FULL_PRODUCT_NAME = Kivo\.app$' "$build_settings" \
    && pass "Release wrapper is Kivo.app" \
    || fail "Release wrapper is not Kivo.app"
  grep -Eq '^ *EXECUTABLE_NAME = Kivo$' "$build_settings" \
    && pass "Release executable is Kivo" \
    || fail "Release executable is not Kivo"

  if grep -E -q '^ *SWIFT_ACTIVE_COMPILATION_CONDITIONS = .*KIVO_APP_STORE_LITE' "$build_settings"; then
    app_store_lite_build_condition_is_present=true
    pass "main App Store target compiles with KIVO_APP_STORE_LITE"
  else
    fail "main App Store target is missing KIVO_APP_STORE_LITE"
  fi

  main_exclusions_line="$(grep -E '^ *EXCLUDED_SOURCE_FILE_NAMES =' "$build_settings" | head -n 1 || true)"
  main_financial_sources_are_excluded=true
  for exclusion in \
    'BadgeGifting*.swift' \
    'Donate*.swift' \
    'Donation*.swift' \
    'Payments*.swift' \
    'SendPayment*.swift'
  do
    if ! grep -F -q "$exclusion" <<<"$main_exclusions_line"; then
      main_financial_sources_are_excluded=false
    fi
  done
  if [[ "$main_financial_sources_are_excluded" == true ]]; then
    pass "main App Store target excludes the reviewed legacy financial UI source families"
  else
    fail "main App Store target does not exclude every reviewed legacy financial UI source family"
  fi
else
  fail "could not read App Store Release build settings"
fi

if xcodebuild \
  -project Signal.xcodeproj \
  -target SignalUI \
  -configuration 'App Store Release' \
  -showBuildSettings >"$signal_ui_build_settings"; then
  if grep -E -q '^ *SWIFT_ACTIVE_COMPILATION_CONDITIONS = .*KIVO_APP_STORE_LITE' \
    "$signal_ui_build_settings"; then
    signal_ui_lite_build_condition_is_present=true
    pass "SignalUI App Store target compiles with KIVO_APP_STORE_LITE"
  else
    fail "SignalUI App Store target is missing KIVO_APP_STORE_LITE"
  fi

  signal_ui_exclusions_line="$(grep -E '^ *EXCLUDED_SOURCE_FILE_NAMES =' \
    "$signal_ui_build_settings" | head -n 1 || true)"
  signal_ui_payment_engine_is_excluded=true
  for exclusion in \
    'MobileCoinAPI+Configuration.swift' \
    'MobileCoinAPI.swift' \
    'MobileCoinHelperSDK.swift' \
    'PaymentsFormat+MobileCoin.swift' \
    'PaymentsImpl.swift' \
    'PaymentsProcessor.swift' \
    'PaymentsReconciliation.swift'
  do
    if ! grep -F -q "$exclusion" <<<"$signal_ui_exclusions_line"; then
      signal_ui_payment_engine_is_excluded=false
    fi
  done
  if [[ "$signal_ui_payment_engine_is_excluded" == true ]]; then
    pass "SignalUI App Store target excludes the reviewed MobileCoin network/transaction engine"
  else
    fail "SignalUI App Store target does not exclude every reviewed MobileCoin engine source"
  fi
else
  fail "could not read SignalUI App Store Release build settings"
fi

if xcodebuild \
  -project Signal.xcodeproj \
  -target SignalServiceKit \
  -configuration 'App Store Release' \
  -showBuildSettings >"$signal_service_kit_build_settings"; then
  if grep -E -q '^ *SWIFT_ACTIVE_COMPILATION_CONDITIONS = .*KIVO_APP_STORE_LITE' \
    "$signal_service_kit_build_settings"; then
    signal_service_kit_lite_build_condition_is_present=true
    pass "SignalServiceKit App Store target compiles with KIVO_APP_STORE_LITE"
  else
    fail "SignalServiceKit App Store target is missing KIVO_APP_STORE_LITE"
  fi
else
  fail "could not read SignalServiceKit App Store Release build settings"
fi

release_version="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' Signal/Signal-Info.plist)"
if [[ -n "$release_version" ]]; then
  pass "main app uses version $release_version"
else
  fail "main app version is empty"
fi

release_build="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleVersion' Signal/Signal-Info.plist)"
if [[ "$release_build" =~ ^[1-9][0-9]*$ ]]; then
  pass "main app uses valid build $release_build"
else
  fail "main app build '$release_build' is not a positive integer"
fi

for plist in SignalNSE/Info.plist SignalShareExtension/Info.plist; do
  build="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleVersion' "$plist")"
  [[ "$build" == "$release_build" ]] \
    && pass "$plist matches main app build $release_build" \
    || fail "$plist uses build $build, expected $release_build to match the main app"
done

if [[ "$submission_mode" == false ]]; then
  if /usr/libexec/PlistBuddy -c 'Print :ITSAppUsesNonExemptEncryption' Signal/Signal-Info.plist >/dev/null 2>&1 ||
    /usr/libexec/PlistBuddy -c 'Print :ITSAppUsesNonExemptEncryption' SignalShareExtension/Info.plist >/dev/null 2>&1; then
    fail "an export-compliance plist answer is present without a completed decision record"
  else
    pass "export-compliance plist answer is withheld pending authorized determination"
  fi
fi

if rg -q 'signal-messenger\.cer in Resources' Signal.xcodeproj/project.pbxproj; then
  fail "upstream private CA certificate is still packaged"
else
  pass "upstream private CA certificate is not packaged"
fi

verify_app_store_resource_sanitizer_phase
verify_paid_backup_lite_source_boundary

deferred_sources=(
  Signal/Kivo/Platform
  Signal/Kivo/KivoEVMChainRegistry.swift
)
for deferred_source in "${deferred_sources[@]}"; do
  if [[ -e "$deferred_source" ]]; then
    fail "App Store Lite source still contains deferred feature path $deferred_source"
  else
    pass "deferred feature path is absent: $deferred_source"
  fi
done

if rg -q \
  'Kivo(DEX|AssetLeaderboard|PublicFeed|PublicProfile|MarketTokens|ExploreViewController|EVMChainRegistry)' \
  Signal.xcodeproj/project.pbxproj; then
  fail "App Store target still references a deferred finance, public-feed, or leaderboard type"
else
  pass "App Store target has no deferred finance, public-feed, or leaderboard references"
fi

if rg -q \
  'KivoExternalWallet|WalletConnect|externalWallet|外部钱包' \
  Signal/Kivo Signal/AppLaunch Signal.xcodeproj/project.pbxproj; then
  fail "App Store target still contains the deferred external-wallet flow"
else
  pass "App Store target has no external-wallet flow"
fi

if rg -q 'KivoProductFeatures\.signalDonationsEnabled' Signal/URLs/UrlOpener.swift; then
  pass "legacy donation callbacks are gated off for Kivo"
else
  fail "legacy donation callback parsing is not gated by the Kivo product flag"
fi

if rg -q 'KivoProductFeatures\.proxyAndCensorshipEnabled' Signal/URLs/UrlOpener.swift; then
  pass "legacy proxy links are gated off for Kivo"
else
  fail "legacy proxy links are not gated by the Kivo product flag"
fi

payment_receive_files=(
  SignalServiceKit/Messages/MessageReceiver.swift
  SignalServiceKit/Messages/DeviceSyncing/OWSIncomingSentMessageTranscript.swift
  SignalServiceKit/Payments/PaymentsHelperImpl.swift
)
payment_receive_gates_are_present=true
for source in "${payment_receive_files[@]}"; do
  if ! rg -q 'KivoProductFeatures\.legacyMobileCoinPaymentsEnabled' "$source"; then
    payment_receive_gates_are_present=false
  fi
done
if [[ "$payment_receive_gates_are_present" == true ]]; then
  pass "legacy MobileCoin receive and sync paths are gated off for Kivo"
else
  fail "a legacy MobileCoin receive or sync path is missing its Kivo product gate"
fi

payment_ui_files=(
  Signal/ConversationView/Components/CVComponentState.swift
  Signal/ConversationView/Components/CVComponentSystemMessage.swift
  Signal/ConversationView/ConversationViewController+CVComponentDelegate.swift
  Signal/ConversationView/ConversationViewController+MessageActionsDelegate.swift
  Signal/ConversationView/MessageActions.swift
  Signal/ConversationView/ConversationViewController+ConversationInputToolbarDelegate.swift
  Signal/src/ViewControllers/Payments/SendPaymentViewController.swift
)
payment_ui_gates_are_present=true
for source in "${payment_ui_files[@]}"; do
  if ! rg -q 'KivoProductFeatures\.legacyMobileCoinPaymentsEnabled' "$source"; then
    payment_ui_gates_are_present=false
  fi
done
if [[ "$payment_ui_gates_are_present" == true ]]; then
  pass "legacy MobileCoin rendering and actions are gated off for Kivo"
else
  fail "a legacy MobileCoin rendering or action path is missing its Kivo product gate"
fi

gift_receive_files=(
  SignalServiceKit/Messages/MessageReceiver.swift
  SignalServiceKit/Messages/DeviceSyncing/OWSIncomingSentMessageTranscript.swift
)
gift_receive_gates_are_present=true
for source in "${gift_receive_files[@]}"; do
  if ! rg -q 'KivoProductFeatures\.signalDonationsEnabled' "$source"; then
    gift_receive_gates_are_present=false
  fi
done
if [[ "$gift_receive_gates_are_present" == true ]]; then
  pass "legacy gift badge receive and sync paths are gated off for Kivo"
else
  fail "a legacy gift badge receive or sync path is missing its Kivo product gate"
fi

gift_ui_files=(
  Signal/ConversationView/Components/CVComponentState.swift
  Signal/ConversationView/Components/CVComponentGiftBadge.swift
  Signal/ConversationView/ConversationViewController+GiftBadges.swift
)
gift_ui_gates_are_present=true
for source in "${gift_ui_files[@]}"; do
  if ! rg -q 'KivoProductFeatures\.signalDonationsEnabled' "$source"; then
    gift_ui_gates_are_present=false
  fi
done
if [[ "$gift_ui_gates_are_present" == true ]]; then
  pass "legacy gift badge rendering and actions are gated off for Kivo"
else
  fail "a legacy gift badge rendering or action path is missing its Kivo product gate"
fi

profile_badge_lite_source="$({
  for source in \
    Signal/src/ViewControllers/AppSettings/Profile/ProfileSettingsViewController.swift \
    'Signal/src/ViewControllers/ThreadSettings/ConversationSettingsViewController+Contents.swift' \
    Signal/src/ViewControllers/ThreadSettings/ConversationSettingsViewController.swift \
    Signal/src/ViewControllers/ThreadSettings/MemberActionSheet.swift \
    SignalUI/Views/ConversationAvatarView.swift
  do
    xcrun clang -E -P -x c -DKIVO_APP_STORE_LITE "$source" || exit 1
  done
} 2>/dev/null || true)"
if [[ -z "$profile_badge_lite_source" ]]; then
  fail "profile-badge sources could not be preprocessed for the Lite boundary"
elif rg -q \
  'BADGE_CONFIGURATION_TITLE|CONVERSATION_SETTINGS_BADGES_(HEADER|FOOTER)|BadgeDetailsSheet[[:space:]]*\(' \
  <<<"$profile_badge_lite_source"; then
  fail "Lite-active profile/contact source still exposes a removed badge UI or localization key"
elif rg -U -q \
  '(?s)#if KIVO_APP_STORE_LITE[[:space:]]+badgeView\.image = nil.*?#else' \
  SignalUI/Views/ConversationAvatarView.swift &&
  rg -U -q \
  '(?s)fileprivate func fetchBadge.*?#if KIVO_APP_STORE_LITE[[:space:]]+return nil.*?#else' \
  SignalUI/Views/ConversationAvatarView.swift; then
  pass "Lite profile/contact badge entrypoints and rendering are compile-time disabled"
else
  fail "Lite avatar rendering lacks the reviewed fail-closed badge boundary"
fi

if rg -q 'if TSConstants\.isSystemContactsEnabled' \
  Signal/src/ViewControllers/AppSettings/Notifications/NotificationSettingsViewController.swift; then
  pass "contact-joined notification setting follows the system contacts boundary"
else
  fail "contact-joined notification setting is exposed while system contacts are disabled"
fi

support_source="Signal/src/ViewControllers/AppSettings/ContactSupportViewController.swift"
support_financial_switch_boundaries="$(rg -U -o \
  '(?s)#if !KIVO_APP_STORE_LITE\n[[:space:]]*case \.payments:.*?case \.donationsAndBadges:.*?#endif' \
  "$support_source" | grep -c '^#if !KIVO_APP_STORE_LITE' || true)"
if rg -U -q \
  '#if !KIVO_APP_STORE_LITE\n[[:space:]]*case payments\n[[:space:]]*case donationsAndBadges\n[[:space:]]*#endif\n[[:space:]]*case safetyAndAbuse' \
  "$support_source" &&
  [[ "$support_financial_switch_boundaries" == 3 ]] &&
  [[ "$(rg -c 'case \.payments:' "$support_source")" == 3 ]] &&
  [[ "$(rg -c 'case \.donationsAndBadges:' "$support_source")" == 3 ]] &&
  rg -U -q \
    '(?s)#if KIVO_APP_STORE_LITE\n[[:space:]]*let visibleFilters = Filter\.allCases\n#else\n[[:space:]]*let visibleFilters = Filter\.allCases\.filter.*?return filter != \.safetyAndAbuse.*?#endif' \
    "$support_source" &&
  rg -q 'KIVO_CONTACT_SUPPORT_FILTER_SAFETY_AND_ABUSE' Signal/translations/en.lproj/Localizable.strings; then
  pass "Lite support enum and menu compile out payments/donations and retain safety/abuse"
else
  fail "Lite support enum/filter boundary does not match the communications-only scope"
fi

if Scripts/verify_kivo_app_store_lite_stories; then
  pass "App Store Lite Stories compile-time boundary is intact"
else
  fail "App Store Lite Stories compile-time boundary is incomplete"
fi

for key in \
  KivoPlatformAllowedAssetHosts \
  KivoPlatformAllowedUploadHosts \
  KivoPlatformAllowedWebHosts \
  KivoPlatformCDNBaseURL
do
  if /usr/libexec/PlistBuddy -c "Print :$key" Signal/Signal-Info.plist >/dev/null 2>&1; then
    fail "Signal-Info.plist still contains deferred platform key $key"
  else
    pass "Signal-Info.plist omits deferred platform key $key"
  fi
done

if /usr/libexec/PlistBuddy -c 'Print :NSContactsUsageDescription' Signal/Signal-Info.plist >/dev/null 2>&1; then
  fail "App Store Lite must not request Contacts permission"
elif rg -q '^\s*NSContactsUsageDescription\s*=' Signal/translations/*/InfoPlist.strings; then
  fail "a localized InfoPlist.strings still declares Contacts permission"
else
  pass "App Store Lite has no base or localized Contacts permission purpose string"
fi

if rg -i -q 'support\.signal\.org|signal private messenger' \
  Signal/Backups Signal/translations/*/InfoPlist.strings; then
  fail "reachable backup or permission copy still contains upstream Signal branding"
else
  pass "backup and permission copy has no upstream Signal branding"
fi

ug_product_brand_leaks="$(
  rg -n 'سىگنال' Signal/translations/ug.lproj/Localizable.strings \
    | rg -v 'ONBOARDING_VERIFICATION_HELP_BULLET_1' || true
)"
if rg -q 'Signal을' Signal/translations/ko.lproj/PluralAware.stringsdict ||
  [[ -n "$ug_product_brand_leaks" ]]; then
  fail "known long-tail localizations still expose the upstream product name"
else
  pass "known long-tail localization brand leaks are removed"
fi

if [[ "$freeze_mode" == true || "$submission_mode" == true ]]; then
  verify_frozen_source
fi

if [[ "$submission_mode" == true ]]; then
  main_export_answer="$(/usr/libexec/PlistBuddy -c 'Print :ITSAppUsesNonExemptEncryption' Signal/Signal-Info.plist 2>/dev/null || true)"
  share_export_answer="$(/usr/libexec/PlistBuddy -c 'Print :ITSAppUsesNonExemptEncryption' SignalShareExtension/Info.plist 2>/dev/null || true)"
  if [[ "$main_export_answer" =~ ^(true|false)$ && "$share_export_answer" == "$main_export_answer" ]]; then
    pass "submission plists contain a consistent explicit export-compliance answer"
  else
    fail "submission plists have no consistent authorized export-compliance answer"
  fi

  if rg -q '\{\{REQUIRED_[A-Z0-9_]+\}\}' release/app-store/app-review-notes.md; then
    fail "App Review Notes still contain REQUIRED placeholders"
  else
    pass "App Review Notes contain no REQUIRED placeholders"
  fi

  deployed_addstickers="$(curl --fail --location --silent --show-error --max-time 15 \
    https://kivo.it389.com/addstickers/ || true)"
  if grep -F -q 'Sticker-pack links are not available' <<<"$deployed_addstickers" &&
    ! grep -E -q 'data-open-kivo|preview the pack before installing|Discover and install' \
      <<<"$deployed_addstickers"; then
    pass "deployed sticker page matches the submitted Lite client"
  else
    fail "deployed sticker page still advertises a flow absent from the submitted Lite client"
  fi

  exact_tag="$(git tag --points-at HEAD | grep -E '^kivo-ios-[0-9]' | head -n 1 || true)"
  if [[ -n "$exact_tag" ]]; then
    if git verify-tag "$exact_tag" >/dev/null 2>&1; then
      pass "submission release tag $exact_tag has a valid cryptographic signature"
    else
      fail "submission release tag $exact_tag is unsigned or its signature cannot be verified"
    fi
    if curl --fail --location --silent --show-error --max-time 15 https://kivo.it389.com/source/ | grep -F -q "$exact_tag"; then
      pass "public source page lists $exact_tag"
    else
      fail "public source page does not list $exact_tag"
    fi
  else
    fail "HEAD has no exact kivo-ios-* release tag"
  fi

  if git branch -r --contains HEAD | grep -Eq '^ *origin/'; then
    pass "HEAD is present on an origin branch"
  else
    fail "HEAD has not been pushed to an origin branch"
  fi

  if command -v dig >/dev/null && [[ -n "$(dig +short MX it389.com)" ]]; then
    pass "it389.com publishes an MX record for support email"
  else
    fail "it389.com has no verified MX record for support@it389.com"
  fi
fi

if [[ -n "$archive_path" ]]; then
  if [[ -d "$archive_path" ]]; then
    verify_release_app "$archive_path/Products/Applications/Kivo.app" "archive"
  else
    fail "archive path does not exist: $archive_path"
  fi
fi

if [[ -n "$ipa_path" ]]; then
  if [[ ! -f "$ipa_path" ]]; then
    fail "IPA path does not exist: $ipa_path"
  else
    ipa_temp_dir="$(mktemp -d "${TMPDIR:-/tmp}/kivo-ipa-verify.XXXXXX")"
    mkdir -p "$ipa_temp_dir/extracted"
    if unzip -q "$ipa_path" -d "$ipa_temp_dir/extracted"; then
      ipa_app_count="$(find "$ipa_temp_dir/extracted/Payload" \
        -mindepth 1 -maxdepth 1 -type d -name '*.app' 2>/dev/null \
        | wc -l \
        | tr -d ' ')"
      if [[ "$ipa_app_count" == 1 ]]; then
        ipa_app="$(find "$ipa_temp_dir/extracted/Payload" \
          -mindepth 1 -maxdepth 1 -type d -name '*.app' \
          | head -n 1)"
        verify_release_app "$ipa_app" "IPA"
      else
        fail "IPA contains $ipa_app_count top-level app bundles (expected exactly 1)"
      fi
    else
      fail "could not extract IPA: $ipa_path"
    fi
  fi
fi

if ((failures)); then
  printf '\nKivo App Store preflight failed with %d issue(s).\n' "$failures" >&2
  exit 1
fi

printf '\nKivo App Store preflight passed.\n'
