set -euo pipefail
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
repository_root="$(cd "$script_dir/.." && pwd)"
project="$script_dir/AvenIOS.xcodeproj"
scheme="AvenIOS"
generated="$script_dir/Generated"
proof_root="$generated/HostProof"
simulator_derived_data="$proof_root/DerivedDataSimulator"
device_derived_data="$proof_root/DerivedDataDevice"
bundle_identifier="com.raine.aven.ios-proof"
aven_binary="$repository_root/target/debug/aven"
sync_auth_token="aven-ios-proof-authentication"
server_pid=""
server_url=""
require_package() {
if [[ ! -d "$generated/AvenUniFFI.xcframework" ]]; then
printf '%s\n' \
"error: run 'just ios-swiftpm-package' before the host proof" >&2
exit 1
fi
}
simulator_udid() {
xcrun simctl list devices available -j | jq -er '
[
.devices
| to_entries[]
| select(.key | contains("iOS-26-5"))
| .value[]
| select(.name == "iPhone 17 Pro" and .isAvailable == true)
][0].udid
'
}
build_simulator() {
local simulator_id="$1"
xcodebuild \
-project "$project" \
-scheme "$scheme" \
-configuration Release \
-sdk iphonesimulator \
-destination "platform=iOS Simulator,id=$simulator_id" \
-derivedDataPath "$simulator_derived_data" \
IPHONEOS_DEPLOYMENT_TARGET=17.0 \
ARCHS=arm64 ONLY_ACTIVE_ARCH=YES CODE_SIGNING_ALLOWED=NO \
build
}
test_simulator() {
local simulator_id="$1"
local result_bundle="$proof_root/AvenIOSHostTests.xcresult"
rm -rf "$result_bundle"
xcodebuild \
-project "$project" \
-scheme "$scheme" \
-configuration Debug \
-sdk iphonesimulator \
-destination "platform=iOS Simulator,id=$simulator_id" \
-derivedDataPath "$simulator_derived_data" \
-resultBundlePath "$result_bundle" \
IPHONEOS_DEPLOYMENT_TARGET=17.0 \
ARCHS=arm64 ONLY_ACTIVE_ARCH=YES \
test
}
build_server() {
cargo build --locked --bin aven
git -C "$repository_root" rev-parse HEAD > "$proof_root/server-revision.txt"
}
read_server_url() {
local line
local field
while IFS= read -r line; do
[[ "$line" == listening\ * ]] || continue
for field in $line; do
if [[ "$field" == url=* ]]; then
printf '%s\n' "${field#url=}"
return 0
fi
done
done < "$proof_root/server-stdout.log"
return 1
}
start_server() {
local bind_address="$1"
local advertised_host="${2:-}"
local server_root="$proof_root/SyncServer"
local config_directory="$server_root/config"
local parsed_url=""
local port
local -a arguments=(
server
--bind "$bind_address"
--data "$server_root/server.sqlite"
)
stop_server
rm -rf "$server_root"
mkdir -p "$config_directory"
chmod 700 "$server_root" "$config_directory"
printf "sync:\n auth_token: '%s'\n" "$sync_auth_token" \
> "$config_directory/config.yaml"
chmod 600 "$config_directory/config.yaml"
: > "$proof_root/server-stdout.log"
: > "$proof_root/server-stderr.log"
if [[ "$bind_address" == 0.0.0.0:* ]]; then
arguments+=(--unsafe-public-bind)
fi
AVEN_CONFIG_DIR="$config_directory" \
"$aven_binary" "${arguments[@]}" \
> "$proof_root/server-stdout.log" \
2> "$proof_root/server-stderr.log" &
server_pid=$!
for _ in {1..100}; do
if ! kill -0 "$server_pid" 2>/dev/null; then
wait "$server_pid" || true
server_pid=""
printf '%s\n' "error: proof server exited before readiness" >&2
return 1
fi
if parsed_url="$(read_server_url)"; then
break
fi
sleep 0.1
done
if [[ -z "$parsed_url" ]]; then
printf '%s\n' "error: proof server readiness timed out" >&2
return 1
fi
if [[ -n "$advertised_host" ]]; then
port="${parsed_url##*:}"
server_url="http://$advertised_host:$port"
else
server_url="$parsed_url"
fi
}
stop_server() {
if [[ -n "$server_pid" ]] && kill -0 "$server_pid" 2>/dev/null; then
kill -TERM "$server_pid"
wait "$server_pid" || true
fi
server_pid=""
server_url=""
}
configure_host_replica() {
local client_root="$proof_root/HostReplica"
rm -rf "$client_root"
mkdir -p "$client_root/config"
chmod 700 "$client_root" "$client_root/config"
printf "sync:\n server_url: '%s'\n auth_token: '%s'\n" \
"$server_url" "$sync_auth_token" \
> "$client_root/config/config.yaml"
chmod 600 "$client_root/config/config.yaml"
}
host_replica_command() {
local client_root="$proof_root/HostReplica"
env \
-u AVEN_SYNC_SERVER \
AVEN_CONFIG_DIR="$client_root/config" \
AVEN_DB="$client_root/host-replica.sqlite" \
"$aven_binary" "$@"
}
prepare_host_divergence() {
local list_json
local task_ref
configure_host_replica
host_replica_command sync > /dev/null
list_json="$(host_replica_command list --json)"
jq -e '
length == 1 and
.[0].title == "iOS sync proof seed" and
.[0].project == "ios-sync-proof" and
.[0].status == "todo" and
.[0].priority == "high" and
.[0].has_conflict == false
' > /dev/null <<< "$list_json"
task_ref="$(jq -er '.[0].ref' <<< "$list_json")"
host_replica_command edit "$task_ref" \
--title "iOS sync proof host variant" > /dev/null
host_replica_command sync > /dev/null
}
verify_host_convergence() {
local list_json
local conflicts_json
host_replica_command sync > /dev/null
list_json="$(host_replica_command list --json)"
conflicts_json="$(host_replica_command conflict list --json)"
jq -e '
length == 1 and
.[0].title == "iOS sync proof host variant" and
.[0].project == "ios-sync-proof" and
.[0].status == "todo" and
.[0].priority == "high" and
.[0].available_at == "" and
.[0].due_on == "" and
.[0].has_conflict == false
' > /dev/null <<< "$list_json"
jq -e 'length == 0' > /dev/null <<< "$conflicts_json"
printf '%s\n' \
"AVEN_IOS_HOST_REPLICA status=pass transport=rust_http convergence=equal unresolved=zero attachments=zero"
}
launch_simulator_phase() {
local simulator_id="$1"
local phase="$2"
local marker="$3"
local log_name="$4"
local launch_log="$proof_root/$log_name"
SIMCTL_CHILD_AVEN_IOS_SYNC_SERVER="$server_url" \
SIMCTL_CHILD_AVEN_IOS_SYNC_AUTH_TOKEN="$sync_auth_token" \
xcrun simctl launch --console --terminate-running-process \
"$simulator_id" "$bundle_identifier" "$phase" | tee "$launch_log"
grep -Fqx "$marker" "$launch_log"
}
launch_simulator_capture() {
local simulator_id="$1"
local phase="$2"
local log_name="$3"
local launch_log="$proof_root/$log_name"
SIMCTL_CHILD_AVEN_IOS_SYNC_SERVER="$server_url" \
SIMCTL_CHILD_AVEN_IOS_SYNC_AUTH_TOKEN="$sync_auth_token" \
xcrun simctl launch --console --terminate-running-process \
"$simulator_id" "$bundle_identifier" "$phase" | tee "$launch_log"
}
wait_for_marker() {
local marker="$1"
local log_path="$2"
for _ in {1..300}; do
if grep -Fqx "$marker" "$log_path" 2>/dev/null; then
return 0
fi
sleep 0.1
done
printf '%s\n' "error: lifecycle marker timed out" >&2
return 1
}
run_background_phase() {
local simulator_id="$1"
local phase="$2"
local ready_marker="$3"
local pass_prefix="$4"
local log_name="$5"
local launch_log="$proof_root/$log_name"
local launch_pid
: > "$launch_log"
SIMCTL_CHILD_AVEN_IOS_SYNC_SERVER="$server_url" \
SIMCTL_CHILD_AVEN_IOS_SYNC_AUTH_TOKEN="$sync_auth_token" \
xcrun simctl launch --console --terminate-running-process \
"$simulator_id" "$bundle_identifier" "$phase" \
> "$launch_log" 2>&1 &
launch_pid=$!
wait_for_marker "$ready_marker" "$launch_log"
xcrun simctl launch "$simulator_id" com.apple.Preferences > /dev/null
sleep 1
xcrun simctl launch "$simulator_id" "$bundle_identifier" > /dev/null
wait "$launch_pid"
grep -Fq "$pass_prefix" "$launch_log"
cat "$launch_log"
}
run_termination_phase() {
local simulator_id="$1"
local phase="$2"
local ready_marker="$3"
local log_name="$4"
local launch_log="$proof_root/$log_name"
local launch_pid
: > "$launch_log"
SIMCTL_CHILD_AVEN_IOS_SYNC_SERVER="$server_url" \
SIMCTL_CHILD_AVEN_IOS_SYNC_AUTH_TOKEN="$sync_auth_token" \
xcrun simctl launch --console --terminate-running-process \
"$simulator_id" "$bundle_identifier" "$phase" \
> "$launch_log" 2>&1 &
launch_pid=$!
wait_for_marker "$ready_marker" "$launch_log"
xcrun simctl terminate "$simulator_id" "$bundle_identifier"
wait "$launch_pid" || true
cat "$launch_log"
}
run_simulator_lifecycle() {
local simulator_id="$1"
local resource_log="$proof_root/simulator-resource.log"
start_server "127.0.0.1:0"
launch_simulator_capture \
"$simulator_id" --aven-resource-proof simulator-resource.log
grep -Fq \
"AVEN_IOS_RESOURCE_PROOF status=pass configuration=release" \
"$resource_log"
stop_server
run_background_phase \
"$simulator_id" \
--aven-background-local \
"AVEN_IOS_BACKGROUND_LOCAL status=ready" \
"AVEN_IOS_BACKGROUND_LOCAL status=pass behavior=" \
simulator-background-local.log
start_server "127.0.0.1:0"
run_background_phase \
"$simulator_id" \
--aven-background-network \
"AVEN_IOS_BACKGROUND_NETWORK status=ready" \
"AVEN_IOS_BACKGROUND_NETWORK status=pass transport=urlsession" \
simulator-background-network.log
stop_server
run_termination_phase \
"$simulator_id" \
--aven-termination-committed \
"AVEN_IOS_TERMINATION_COMMITTED status=ready" \
simulator-termination-committed.log
launch_simulator_capture \
"$simulator_id" \
--aven-termination-committed-verify \
simulator-termination-committed-verify.log
grep -Fqx \
"AVEN_IOS_TERMINATION_COMMITTED status=pass database=reopened mutation=observed" \
"$proof_root/simulator-termination-committed-verify.log"
start_server "127.0.0.1:0"
run_termination_phase \
"$simulator_id" \
--aven-termination-network \
"AVEN_IOS_TERMINATION_NETWORK status=ready" \
simulator-termination-network.log
launch_simulator_capture \
"$simulator_id" \
--aven-termination-network-recover \
simulator-termination-network-recover.log
grep -Fqx \
"AVEN_IOS_TERMINATION_NETWORK status=pass database=reopened session=fresh recovery=complete" \
"$proof_root/simulator-termination-network-recover.log"
stop_server
}
run_simulator_sync() {
local simulator_id="$1"
build_server
start_server "127.0.0.1:0"
launch_simulator_phase \
"$simulator_id" \
--aven-sync-seed \
"AVEN_IOS_SYNC_SEED status=pass transport=urlsession auth=accepted metadata=push malformed=atomic cancellation=single_fail recovery=fresh_session heartbeat=progressing attachments=zero" \
simulator-sync-seed.log
prepare_host_divergence
launch_simulator_phase \
"$simulator_id" \
--aven-sync-conflict \
"AVEN_IOS_SYNC_CONFLICT status=pass variants=typed resolution=remote metadata=push_pull convergence=local unresolved=zero attachments=zero" \
simulator-sync-conflict.log
verify_host_convergence
stop_server
}
run_simulator() {
local simulator_id
local app_path
local launch_log="$proof_root/simulator-launch.log"
simulator_id="$(simulator_udid)"
mkdir -p "$proof_root"
xcrun simctl bootstatus "$simulator_id" -b
build_simulator "$simulator_id"
test_simulator "$simulator_id"
app_path="$simulator_derived_data/Build/Products/Release-iphonesimulator/AvenIOS.app"
xcrun simctl install "$simulator_id" "$app_path"
xcrun simctl launch --console --terminate-running-process \
"$simulator_id" "$bundle_identifier" | tee "$launch_log"
grep -Fqx \
"AVEN_IOS_HOST_PROOF status=pass facade=typed worker=serial heartbeat=progressing persistence=reopen types=complete storage=application_support wal_shm=reopen protection=complete_until_first_authentication" \
"$launch_log"
inspect_simulator_sandbox "$simulator_id"
run_simulator_sync "$simulator_id"
run_simulator_lifecycle "$simulator_id"
}
inspect_simulator_sandbox() {
local simulator_id="$1"
local data_container
local persistence_root
local database
local storage_root
local inventory="$proof_root/simulator-sandbox-layout.txt"
data_container="$(
xcrun simctl get_app_container \
"$simulator_id" "$bundle_identifier" data
)"
persistence_root="$data_container/Library/Application Support/AvenHostProof/Persistence"
database="$persistence_root/persistence.sqlite"
storage_root="$database.blobs"
test -f "$database"
test -d "$storage_root/objects/sha256"
test -d "$storage_root/trash"
test -d "$storage_root/cache/previews"
test -z "$(find "$storage_root/objects/sha256" -type f -print -quit)"
find "$persistence_root" -mindepth 1 -maxdepth 5 -print \
| sed "s|$data_container|<app-data-container>|" \
| LC_ALL=C sort > "$inventory"
printf '%s\n' \
"AVEN_IOS_SANDBOX_PROOF status=pass location=application_support attachments=none"
}
build_device() {
mkdir -p "$proof_root"
xcodebuild \
-project "$project" \
-scheme "$scheme" \
-configuration Release \
-sdk iphoneos \
-destination "generic/platform=iOS" \
-derivedDataPath "$device_derived_data" \
IPHONEOS_DEPLOYMENT_TARGET=17.0 \
ARCHS=arm64 ONLY_ACTIVE_ARCH=YES CODE_SIGNING_ALLOWED=NO \
build
}
launch_device_phase() {
local device_id="$1"
local phase="$2"
local marker="$3"
local output_name="$4"
local launch_json="$proof_root/$output_name.json"
local launch_log="$proof_root/$output_name.log"
rm -f "$launch_json"
DEVICECTL_CHILD_AVEN_IOS_SYNC_SERVER="$server_url" \
DEVICECTL_CHILD_AVEN_IOS_SYNC_AUTH_TOKEN="$sync_auth_token" \
xcrun devicectl device process launch \
--device "$device_id" --terminate-existing --console \
--json-output "$launch_json" \
"$bundle_identifier" "$phase" | tee "$launch_log"
grep -Fqx "$marker" "$launch_log"
}
run_device() {
local device_id="${AVEN_IOS_DEVICE_ID:-}"
local development_team="${AVEN_IOS_DEVELOPMENT_TEAM:-}"
local server_host="${AVEN_IOS_DEVICE_SERVER_HOST:-}"
local app_path
local install_json="$proof_root/device-install.json"
if [[ -z "$device_id" || -z "$development_team" || -z "$server_host" ]]; then
printf '%s\n' \
"AVEN_IOS_DEVICE_PROOF status=unavailable condition=device_team_or_lan" \
>&2
return 2
fi
if [[ ! "$server_host" =~ ^[A-Za-z0-9.-]+$ ]]; then
printf '%s\n' "error: invalid device server host" >&2
return 64
fi
xcodebuild \
-project "$project" \
-scheme "$scheme" \
-configuration Release \
-sdk iphoneos \
-destination "platform=iOS,id=$device_id" \
-derivedDataPath "$device_derived_data" \
IPHONEOS_DEPLOYMENT_TARGET=17.0 \
ARCHS=arm64 ONLY_ACTIVE_ARCH=YES \
CODE_SIGN_STYLE=Automatic DEVELOPMENT_TEAM="$development_team" \
-allowProvisioningUpdates -allowProvisioningDeviceRegistration \
build
app_path="$device_derived_data/Build/Products/Release-iphoneos/AvenIOS.app"
rm -f "$install_json"
xcrun devicectl device install app \
--device "$device_id" "$app_path" \
--json-output "$install_json"
build_server
start_server "0.0.0.0:0" "$server_host"
launch_device_phase \
"$device_id" \
--aven-sync-seed \
"AVEN_IOS_SYNC_SEED status=pass transport=urlsession auth=accepted metadata=push malformed=atomic cancellation=single_fail recovery=fresh_session heartbeat=progressing attachments=zero" \
device-sync-seed
prepare_host_divergence
launch_device_phase \
"$device_id" \
--aven-sync-conflict \
"AVEN_IOS_SYNC_CONFLICT status=pass variants=typed resolution=remote metadata=push_pull convergence=local unresolved=zero attachments=zero" \
device-sync-conflict
verify_host_convergence
stop_server
printf '%s\n' \
"AVEN_IOS_DEVICE_PROOF status=pass transport=urlsession metadata=round_trip"
}
cleanup() {
stop_server
}
usage() {
printf 'usage: %s {all|simulator|device-build|device}\n' "$0" >&2
exit 64
}
trap cleanup EXIT INT TERM
require_package
case "${1:-}" in
all)
build_device
run_simulator
run_device || status=$?
if [[ "${status:-0}" -ne 0 && "${status:-0}" -ne 2 ]]; then
exit "$status"
fi
;;
simulator)
run_simulator
;;
device-build)
build_device
;;
device)
run_device
;;
*)
usage
;;
esac