#!/usr/bin/env bash
set -Eeuo pipefail

serving_root="${OPENCLAW_TEAM_RELEASE_ROOT:-/opt/openclaw-team}"
releases_root="$serving_root/releases"
current_link="$serving_root/current"
previous_link="$serving_root/previous"
journal_file="$serving_root/journal/activation.json"
build_home="${OPENCLAW_TEAM_BUILD_HOME:-/var/lib/openclaw-team-deploy}"
mirror="$build_home/mirror/mirror.git"
work_root="$build_home/work"
runtime_home="${OPENCLAW_TEAM_RUNTIME_HOME:-/home/openclaw}"
legacy_current="${OPENCLAW_TEAM_LEGACY_CURRENT:-$runtime_home/current}"
config_file="${OPENCLAW_TEAM_CONFIG_FILE:-$runtime_home/.openclaw/openclaw.json}"
state_database="${OPENCLAW_TEAM_STATE_DATABASE:-$runtime_home/.openclaw/state/openclaw.sqlite}"
node_bin="${OPENCLAW_TEAM_NODE_BIN:-/usr/bin/node}"
gateway_runtime_bin=/usr/bin/node
gateway_runtime_snapshot=""
runtime_target=""
runtime_kind=""
runtime_path=""
runtime_hash=""
runuser_bin="${OPENCLAW_TEAM_RUNUSER:-/usr/sbin/runuser}"
timeout_bin="${OPENCLAW_TEAM_TIMEOUT:-/usr/bin/timeout}"
systemctl_bin="${OPENCLAW_TEAM_SYSTEMCTL:-systemctl}"
busctl_bin="${OPENCLAW_TEAM_BUSCTL:-/usr/bin/busctl}"
curl_bin="${OPENCLAW_TEAM_CURL:-/usr/bin/curl}"
ss_bin="${OPENCLAW_TEAM_SS:-/usr/bin/ss}"
proc_root="${OPENCLAW_TEAM_PROC_ROOT:-/proc}"
gateway_port="${OPENCLAW_TEAM_GATEWAY_PORT:-18789}"
lock_file="${OPENCLAW_TEAM_LOCK_FILE:-/run/openclaw-release-deploy.lock}"
root_uid="${OPENCLAW_TEAM_ROOT_UID:-0}"
root_gid="${OPENCLAW_TEAM_ROOT_GID:-0}"
ancestor_boundary="${OPENCLAW_TEAM_ANCESTOR_BOUNDARY:-/}"
fetch_budget="${OPENCLAW_TEAM_FETCH_BUDGET:-180}"
# Candidate checkout has its own budget, independent of the upstream fetch.
clone_budget="${OPENCLAW_TEAM_CLONE_BUDGET:-600}"
# Mirror operations must never run a foreground auto-gc inside a bounded step.
mirror_git_config=(-c gc.auto=0 -c maintenance.auto=false)
build_budget="${OPENCLAW_TEAM_BUILD_BUDGET:-2400}"
verification_budget="${OPENCLAW_TEAM_VERIFICATION_BUDGET:-600}"
drain_budget="${OPENCLAW_TEAM_DRAIN_BUDGET:-30}"
drain_poll_interval="${OPENCLAW_TEAM_DRAIN_POLL_INTERVAL:-5}"
official_origin=https://github.com/openclaw/openclaw.git
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
release_lib="${OPENCLAW_TEAM_RELEASE_LIB:-$script_dir/release-lib.mjs}"
if [[ ! -f "$release_lib" && -z "${OPENCLAW_TEAM_RELEASE_LIB:-}" ]]; then
	release_lib=/usr/local/lib/openclaw-team/release-lib.mjs
fi

runtime_uid="${OPENCLAW_TEAM_RUNTIME_UID:-$(id -u openclaw)}"
deploy_uid="${OPENCLAW_TEAM_DEPLOY_UID:-$(id -u openclaw-deploy)}"
deploy_gid="${OPENCLAW_TEAM_DEPLOY_GID:-$(id -g openclaw-deploy)}"
deploy_file_uid="${OPENCLAW_TEAM_DEPLOY_FILE_UID:-$deploy_uid}"
runtime_dir="${OPENCLAW_TEAM_RUNTIME_DIR:-/run/user/$runtime_uid}"
bus_address="unix:path=$runtime_dir/bus"
mode=deploy
declare +x allow_cpu_degraded=0
interrupt_after_drain=0
offline_doctor_repair=0
capture_startup_profile=0
arm_steady_profile=0
profile_keep_arm=0
profile_operation=""
profile_inspection=""
profile_finished=0
profile_closed=0
profile_owner_unsafe=0
profile_convergence=""
profile_result=""
drain_budget_elapsed=0
requested_sha=""
frozen_target_sha=""
frozen_manifest_sha=""
frozen_predecessor_sha=""
frozen_predecessor_pid=""
frozen_predecessor_start=""
frozen_process_instance=""
frozen_release_binding=""
handoff_peer=""
handoff_target_sha=""
handoff_stop_only=0
handoff_manifest=""
handoff_manifest_hash=""
handoff_config_hash=""
rollback_armed=0
predecessor_stopped=0
restart_failed=0
held_suspension=""
suspension_cleanup_only=0
suspension_handoff_started=0
suspension_expiry=""
suspension_state=""
suspension_active_count=0
suspension_retry_after_ms=0
suspension_scope=""
suspension_release=""
suspension_pid=""
suspension_generation=""
suspension_instance=""
suspension_blockers=""
suspension_write_custody=unknown
custody_wait_budget=5
# explicit operator waiver for terminal-persistence holders that never settle (dead runs).
waive_terminal_persistence=0
terminal_persistence_waiver_armed=0
custody_wait_interval=2
previous_release=""
previous_info=""
candidate_release=""
candidate_info=""
protected_identity=""
policy_identity=""
session_witness=""
session_witness_record=""
session_proof=fresh-current
service_pid=""
service_generation=""
verification_deadline=0
verification_owned_pid=""
verification_owned_generation=""
verification_owned_scope=""
drain_deadline=0
operator_expected_sha=""
operator_expected_pid=""
operator_expected_generation=""
operator_receipt_open=0
operator_restart_verified=0
start_stopped_current=0
stopped_expected_boot=""
stopped_expected_invocation=""
stopped_admitted=0
stopped_start_attempted=0
stopped_timer_attempted=0
stopped_verified=0
stopped_receipt_open=0
native_update_root=""
migration_guard=""
worker_config_recovered=0
worker_config_token=""
worker_expected_config_hash=""
worker_expected_setup_hash=""
reconcile_config_hash=""
reconcile_journal_hash=""
reconcile_request=""
reconcile_position=restored
reconcile_evidence=""
reconcile_stopped=0

fail() {
	printf 'openclaw-release-deploy: %s\n' "$*" >&2
	return 1
}

proof() {
	"$node_bin" --no-warnings "$release_lib" "$@"
}

proof_json() {
	local command="$1" payload="$2"
	shift 2
	# Suspension task diagnostics are private payloads, not process-list arguments.
	printf '%s' "$payload" | proof "$command" @stdin "$@"
}

profile_command() {
	local operation="$1" payload="$2" command_exit output mutating=0
	((profile_owner_unsafe == 0)) || return "$profile_owner_unsafe"
	# A pipe-backed command substitution can wait on an escaped writer after timeout dies.
	output="$(mktemp "${TMPDIR:-/tmp}/openclaw-profile-output.XXXXXXXX" 8>&- 9>&-)" || return 1
	case "$operation" in
	prepare | admit | disarm | cleanup | retire | collect | close-arm | strict-observation | terminalize-arm) mutating=1 ;;
	esac
	if printf '%s' "$payload" | {
		if ((mutating)); then
			# This trusted Node keeps the flock and marks it close-on-exec before child commands.
			env -u NODE_OPTIONS "$timeout_bin" --signal=TERM --kill-after=10s 45s \
				"$node_bin" --no-warnings "$release_lib" startup-profile "$operation" 8>&-
		else
			bounded 45 "$node_bin" --no-warnings "$release_lib" startup-profile "$operation"
		fi
	} >"$output"; then
		if ! profile_result="$(<"$output")"; then
			rm -- "$output" 8>&- 9>&- || :
			return 1
		fi
		rm -- "$output" 8>&- 9>&- || return 1
		printf '%s\n' "$profile_result"
		return $?
	else
		command_exit=$?
	fi
	case "$command_exit" in
	1 | 2 | 124)
		rm -- "$output" 8>&- 9>&- || return 1
		return "$command_exit"
		;;
	*)
		if ((mutating == 0)); then
			printf 'PROFILE_READ_INCOMPLETE phase=%s exit=%s stdout=%s\n' "$operation" "$command_exit" "$output" >&2
			return "$command_exit"
		fi
		profile_owner_unsafe=76
		printf 'PROFILE_OWNER_UNJOINED phase=%s supervisor_exit=%s further-mutation=refused stdout=%s\n' \
			"$operation" "$command_exit" "$output" >&2
		return 76
		;;
	esac
}

profile_warning() {
	((profile_owner_unsafe == 0)) || return "$profile_owner_unsafe"
	local label=STARTUP_PROFILE_INCOMPLETE
	((arm_steady_profile == 0)) || label=STEADY_PROFILE_ARM_INCOMPLETE
	printf '%s phase=%s deployment-outcome=separate\n' "$label" "$1" >&2
}

profile_maintenance() {
	[[ -d /var/lib/openclaw-team-diagnostics ]] || return 0
	local receipt payload='{}'
	if [[ -n "${1:-}" ]]; then printf -v payload '{"retainArm":"%s"}' "$1"; fi
	profile_command cleanup "$payload" >/dev/null || { local code=$?; profile_warning cleanup; return "$code"; }
	receipt="$profile_result"
	if [[ "$receipt" == *'"reload":true'* ]]; then
		system_systemctl daemon-reload || { profile_warning reload; return 1; }
	fi
	profile_command retire '{}' >/dev/null || { local code=$?; profile_warning retirement; return "$code"; }
}

profile_inspect() {
	local payload
	profile_operation="$("$node_bin" -p 'require("node:crypto").randomUUID()')" || return 1
	payload="$("$node_bin" -e '
const [operation,release,identity,journal,activation,pid,start,ownerPid,ownerStart,uid,gid,steady]=process.argv.slice(1);
process.stdout.write(JSON.stringify({operation,release,releaseIdentity:JSON.parse(identity),journal,activation,predecessor:{pid:Number(pid),start},owner:{pid:Number(ownerPid),start:ownerStart},uid:Number(uid),gid:Number(gid),...(steady==="1"?{mode:"steady"}:{})}));
' "$profile_operation" "$candidate_release" "$candidate_info" "${journal_file%/*}" "$journal_file" \
		"$suspension_pid" "$suspension_generation" "$$" "$(process_generation "$$")" "$runtime_uid" "$(id -g openclaw)" "$arm_steady_profile")" || return 1
	profile_command inspect "$payload" >/dev/null || return $?
	profile_inspection="$profile_result"
}

profile_skip_no_effect() {
	profile_command no-effects "{\"operation\":\"$profile_operation\"}" >/dev/null || return $?
	# Inspection is read-only and may refuse before returning a cleanup baseline.
	[[ "$(proof journal-field "$journal_file" phase)" == B_PREVIOUS_PUBLISHED &&
		"$(read_pointer "$current_link")" == "$previous_release" &&
		"$(read_pointer "$previous_link")" == "$previous_release" ]] || return 1
	assert_exclusive_owner system && assert_protected_identity && assert_policy && check_suspension || return 1
	migration_check_existing_guard || return 1
	profile_closed=1
	profile_finished=1
	printf 'STEADY_PROFILE_SKIPPED operation=%s outcome=skipped-no-effect phase=inspection\n' "$profile_operation"
}

profile_close() {
	((profile_owner_unsafe == 0)) || return "$profile_owner_unsafe"
	((arm_steady_profile && !profile_closed)) && [[ -n "$profile_operation" ]] || return 0
	local request receipt verification
	request="$profile_inspection"
	[[ -n "$request" ]] || printf -v request '{"operation":"%s"}' "$profile_operation"
	if profile_command close-arm "$request" >/dev/null &&
		receipt="$profile_result" &&
		printf -v verification '{"request":%s,"receipt":%s}' "$request" "$receipt" &&
		profile_command verify-closed "$verification" >/dev/null; then
		profile_closed=1
		profile_finished=1
		profile_keep_arm=0
		printf 'STEADY_PROFILE_SKIPPED operation=%s outcome=closed phase=%s\n' "$profile_operation" "$1"
	else
		((profile_owner_unsafe != 0)) || profile_owner_unsafe=1
		fail 'optional profile closure is unverified; journal and owner evidence retained'
		return "$profile_owner_unsafe"
	fi
}

profile_prepare() {
	if profile_command prepare "$profile_inspection" >/dev/null &&
		system_systemctl daemon-reload &&
		profile_command verify-environment "{\"operation\":\"$profile_operation\",\"armed\":true}" >/dev/null; then
		return 0
	fi
	((profile_owner_unsafe == 0)) || return "$profile_owner_unsafe"
	((arm_steady_profile)) || return 1
	profile_close preparation
}

profile_admit() {
	[[ -n "$profile_operation" ]] && ((profile_finished == 0)) || return 0
	if ((arm_steady_profile)); then
		if profile_command admit "{\"operation\":\"$profile_operation\",\"pid\":$service_pid,\"start\":\"$service_generation\"}" &&
			profile_command disarm "{\"operation\":\"$profile_operation\"}" >/dev/null &&
			system_systemctl daemon-reload &&
			profile_command verify-environment "{\"operation\":\"$profile_operation\",\"armed\":false}" >/dev/null; then
			return 0
		fi
		profile_close admission
		return $?
	fi
	profile_command admit "{\"operation\":\"$profile_operation\",\"pid\":$service_pid,\"start\":\"$service_generation\"}" || { profile_warning admission || return $?; }
	profile_command disarm "{\"operation\":\"$profile_operation\"}" >/dev/null || { profile_warning disarm || return $?; }
	system_systemctl daemon-reload || profile_warning reload
}

profile_finish() {
	[[ -n "$profile_operation" ]] && ((profile_finished == 0)) || return 0
	if ((arm_steady_profile)); then
		[[ "$1" == true && -n "$profile_convergence" ]] || { profile_close deployment-exit; return $?; }
		local observation
		if profile_command strict-observation "$profile_convergence" >/dev/null &&
			observation="$profile_result" &&
			printf '%s' "$observation" | "$node_bin" -e '
const operation=process.argv[1];let input="";
process.stdin.on("data",chunk=>input+=chunk).on("end",()=>{
const receipt=JSON.parse(input);if(receipt.operation!==operation||receipt.outcome!=="strict-observed")process.exit(1);
});' "$profile_operation"; then
			profile_finished=1
			profile_keep_arm=1
			return 0
		fi
		profile_close ineligible-or-unregistered
		return $?
	fi
	profile_finished=1
	profile_command disarm "{\"operation\":\"$profile_operation\"}" >/dev/null || { profile_warning disarm || return $?; }
	system_systemctl daemon-reload || profile_warning reload
	profile_command verify-environment "{\"operation\":\"$profile_operation\",\"armed\":false}" >/dev/null || { profile_warning environment || return $?; }
	profile_command collect "{\"operation\":\"$profile_operation\",\"wait\":$1}" || { profile_warning collection || return $?; }
}

bounded() {
	local seconds="$1"
	shift
	# Close owner capabilities on exec, not on function calls that can leave restoration aliases.
	"$timeout_bin" --signal=TERM --kill-after=10s "${seconds}s" "$@" 8>&- 9>&-
}

runtime_command=(
	"$runuser_bin" -u openclaw -- env
	-u OPENCLAW_SERVICE_MARKER -u OPENCLAW_SERVICE_KIND -u OPENCLAW_GATEWAY_SERVICE_PID
	-u INVOCATION_ID -u SYSTEMD_EXEC_PID -u JOURNAL_STREAM
	HOME="$runtime_home" XDG_RUNTIME_DIR="$runtime_dir" DBUS_SESSION_BUS_ADDRESS="$bus_address"
	OPENCLAW_SUPERVISOR_MODE=external OPENCLAW_SYSTEMD_UNIT=openclaw-gateway.service
)

run_runtime() {
	"${runtime_command[@]}" "$@" 8>&- 9>&-
}

run_build() {
	"$runuser_bin" -u openclaw-deploy -- env -i \
		HOME="$build_home/home" \
		PATH="${OPENCLAW_TEAM_BUILD_PATH:-/usr/bin:/bin:/usr/local/bin}" \
		XDG_CONFIG_HOME="$build_home/config" \
		XDG_CACHE_HOME="$build_home/cache" \
		GIT_CONFIG_GLOBAL=/dev/null \
		GIT_CONFIG_SYSTEM=/dev/null \
		GIT_CONFIG_NOSYSTEM=1 \
		GIT_TERMINAL_PROMPT=0 \
		npm_config_userconfig="$build_home/config/npmrc" \
		PNPM_HOME="$build_home/pnpm" \
		"$@" 8>&- 9>&-
}

bounded_build() {
	local seconds="$1"
	shift
	bounded "$seconds" "$runuser_bin" -u openclaw-deploy -- env -i \
		HOME="$build_home/home" \
		PATH="${OPENCLAW_TEAM_BUILD_PATH:-/usr/bin:/bin:/usr/local/bin}" \
		XDG_CONFIG_HOME="$build_home/config" \
		XDG_CACHE_HOME="$build_home/cache" \
		GIT_CONFIG_GLOBAL=/dev/null \
		GIT_CONFIG_SYSTEM=/dev/null \
		GIT_CONFIG_NOSYSTEM=1 \
		GIT_TERMINAL_PROMPT=0 \
		npm_config_userconfig="$build_home/config/npmrc" \
		PNPM_HOME="$build_home/pnpm" \
		"$@"
}

user_systemctl() {
	run_runtime "$systemctl_bin" --user "$@"
}

user_file_systemctl() {
	env HOME="$runtime_home" XDG_CONFIG_HOME="$runtime_home/.config" \
		XDG_RUNTIME_DIR="$runtime_dir" DBUS_SESSION_BUS_ADDRESS="$bus_address" \
		"$systemctl_bin" --user --root=/ "$@" openclaw-gateway.service
}

user_unit_file() {
	user_file_systemctl "$1" || return 1
	user_systemctl daemon-reload
}

system_systemctl() {
	"$systemctl_bin" "$@"
}

run_gateway_node() {
	local rpc_budget="${OPENCLAW_TEAM_RPC_BUDGET:-45}" remaining
	if ((verification_deadline > 0)); then
		rpc_budget=$((verification_deadline - SECONDS))
		((rpc_budget > 0)) || return 1
	fi
	if ((drain_deadline > 0)); then
		remaining=$((drain_deadline - SECONDS))
		# Expiry before dispatch is the same budget exhaustion as bounded's timeout.
		((remaining > 0)) || return 124
		if ((remaining < rpc_budget)); then
			rpc_budget="$remaining"
		fi
	fi
	bounded "$rpc_budget" "$runuser_bin" -u openclaw -- env \
		HOME="$runtime_home" OPENCLAW_HOME="$runtime_home" \
		OPENCLAW_CONFIG_PATH="$config_file" OPENCLAW_STATE_DIR="$(dirname "$(dirname "$state_database")")" \
		XDG_RUNTIME_DIR="$runtime_dir" \
		DBUS_SESSION_BUS_ADDRESS="$bus_address" \
		OPENCLAW_SUPERVISOR_MODE=external \
		OPENCLAW_SYSTEMD_UNIT=openclaw-gateway.service \
		"$node_bin" "$@"
}

run_gateway() {
	local release="$1"
	shift
	run_gateway_node "$release/dist/index.js" "$@"
}

run_marker_rpc() {
	run_gateway_node --input-type=module -e '
import { pathToFileURL } from "node:url";
const [release, method, payload, timeout] = process.argv.slice(1);
const { callGatewayFromCli, isGatewayTransportError } = await import(pathToFileURL(`${release}/dist/plugin-sdk/gateway-runtime.js`).href);
try {
  const result = await callGatewayFromCli(method, { json: true, timeout }, JSON.parse(payload), {
    clientName: "cli", mode: "cli", scopes: ["operator.admin"], expectFinal: false, progress: false, sharedStateMode: "read-only",
  });
  process.stdout.write(JSON.stringify(result));
} catch (error) {
  // A startup admission refusal is pre-dispatch: nothing ran, so the caller may re-send the same payload.
  if (method === "agent" && !isGatewayTransportError(error) && error?.code === "UNAVAILABLE" &&
    error?.details?.code === "agent-database-inspection-pending") {
    process.stderr.write(`MARKER_ADMISSION_PENDING agent=${String(error.details.agentId)}\n`);
    process.stdout.write("RETRYABLE_ADMISSION_PENDING");
    process.exitCode = 76;
  } else if (method === "agent" && isGatewayTransportError(error) && error.kind === "timeout") {
    process.stderr.write("MARKER_DISPATCH_TIMEOUT reconciliation=same-idempotency-key\n");
    process.stdout.write("RETRYABLE_DISPATCH_TIMEOUT");
    process.exitCode = 77;
  } else if (method === "system.info" && isGatewayTransportError(error) &&
    (error.kind === "timeout" || [undefined, 1006, 1012].includes(error.code))) {
    process.stdout.write("RETRYABLE_READINESS_TRANSPORT");
    process.exitCode = 78;
  } else {
    // Only a read of an acknowledged run may recover transport uncertainty.
    if (method !== "agent.wait" || !isGatewayTransportError(error) ||
      !(error.kind === "timeout" || [undefined, 1006, 1012].includes(error.code))) throw error;
    process.stderr.write(`MARKER_OBSERVATION_TRANSPORT_UNAVAILABLE kind=${error.kind}\n`);
    process.stdout.write("RETRYABLE_OBSERVATION_TRANSPORT");
    process.exitCode = 75;
  }
}
' "$@"
}

read_process_instance() {
	local response
	# JSON CLI failures live on stdout; report the exit without exposing the private response.
	response="$(run_gateway "$1" gateway call system.info --params '{}' --json --timeout 30000)" || {
		printf 'GATEWAY_RPC_FAILED method=system.info exit=%s\n' "$?" >&2
		return 1
	}
	proof_json suspension-process-instance "$response" "$2"
}

system_state() {
	system_systemctl is-active openclaw-gateway.service 2>/dev/null || true
}

user_manager_stopped() {
	local manager manager_after
	local -a properties=(--property=LoadState --property=ActiveState --property=SubState
		--property=MainPID --property=ControlPID --property=Job --property=ControlGroup)
	# A retired user manager has no bus. Prove its native process/job/cgroup state;
	# a transport error alone never means that the legacy Gateway is inactive.
	manager="$(system_systemctl show "user@$runtime_uid.service" "${properties[@]}")" || return 1
	printf '%s' "$manager" | proof user-manager-stopped "$ancestor_boundary" "$runtime_uid" >/dev/null || return 1
	manager_after="$(system_systemctl show "user@$runtime_uid.service" "${properties[@]}")" || return 1
	[[ "$manager_after" == "$manager" ]] || return 1
}

user_state() {
	local observed exit_code=0
	observed="$(user_systemctl is-active openclaw-gateway.service 2>/dev/null)" || exit_code=$?
	case "$exit_code:$observed" in
	0:active | 3:inactive | 3:failed) printf '%s\n' "$observed"; return 0 ;;
	esac
	[[ -z "$observed" && "$exit_code" != 0 ]] || return 1
	user_manager_stopped || return 1
	printf 'inactive\n'
}

system_unit_state() {
	system_systemctl is-enabled openclaw-gateway.service 2>/dev/null || true
}

user_unit_state() {
	user_file_systemctl is-enabled 2>/dev/null || true
}

assert_exclusive_owner() {
	local expected="$1" observed_system observed_user observed_user_enabled
	observed_system="$(system_state)"
	observed_user="$(user_state)" || return 1
	if [[ "$observed_system" == active && "$observed_user" == active ]]; then
		fail 'user and system Gateway units are simultaneously active'
		return 1
	fi
	# Conditional callers disable errexit; one predicate must return every ownership failure.
	case "$expected" in
	system)
		[[ "$observed_system" == active && "$observed_user" == inactive &&
			"$(system_unit_state)" == enabled && "$(user_unit_state)" == disabled ]] ||
			fail 'system Gateway active ownership or durable enablement is not exclusive'
		;;
	migration-user)
		observed_user_enabled="$(user_unit_state)"
		[[ "$observed_user" == active && "$observed_system" == inactive &&
			"$(system_unit_state)" == disabled &&
			("$observed_user_enabled" == enabled || "$observed_user_enabled" == disabled) ]] ||
			fail 'legacy user/system Gateway active ownership or durable enablement is unsafe'
		;;
	migration-switching)
		[[ "$observed_user" == active && "$observed_system" == inactive &&
			"$(system_unit_state)" == enabled && "$(user_unit_state)" == disabled ]] ||
			fail 'migration handoff lost its active user owner or exact durable enablement'
		;;
	quiescent)
		[[ "$observed_user" == inactive && "$observed_system" == inactive ]] ||
			fail 'Gateway ownership is not quiescent during migration'
		;;
	*) fail 'unknown Gateway ownership topology' ;;
	esac
}

load_gateway_runtime() {
	local operation=current
	if [[ "$mode" == runtime ]]; then operation=capture; fi
	gateway_runtime_snapshot="$(proof gateway-runtime "$operation" "$serving_root")" || return 1
	gateway_runtime_bin="$(printf '%s' "$gateway_runtime_snapshot" | "$node_bin" -e 'let s="";process.stdin.on("data",x=>s+=x);process.stdin.on("end",()=>process.stdout.write(JSON.parse(s).executable));')" || return 1
}

read_service() {
	local scope="$1" output key value active_state="" sub_state="" exec_start="" user="" group="" environment=""
	if [[ "$scope" == system ]]; then
		proof gateway-runtime check "$serving_root" "$gateway_runtime_snapshot" >/dev/null || return 1
		output="$(system_systemctl show openclaw-gateway.service \
			--property=ActiveState --property=SubState --property=MainPID \
			--property=ExecStart --property=User --property=Group --property=Environment)" || return 1
	else
		output="$(user_systemctl show openclaw-gateway.service \
			--property=ActiveState --property=SubState --property=MainPID \
			--property=ExecStart)" || return 1
	fi
	service_pid=""
	while IFS='=' read -r key value; do
		case "$key" in
		ActiveState) active_state="$value" ;;
		SubState) sub_state="$value" ;;
		MainPID) service_pid="$value" ;;
		ExecStart) exec_start="$value" ;;
		User) user="$value" ;;
		Group) group="$value" ;;
		Environment) environment="$value" ;;
		esac
	done <<<"$output"
	[[ "$active_state" == active && "$sub_state" == running && "$service_pid" =~ ^[1-9][0-9]*$ ]] ||
		return 1
	if [[ "$scope" == system ]]; then
		[[ "$user" == openclaw && "$group" == openclaw &&
			" $environment " == *" HOME=$runtime_home "* &&
			" $environment " == *' OPENCLAW_SUPERVISOR_MODE=external '* &&
			"$exec_start" == *"argv[]=$gateway_runtime_bin $serving_root/current/dist/index.js gateway --port $gateway_port ;"* ]] ||
			return 1
	else
		[[ "$exec_start" == *"argv[]=/usr/bin/node $legacy_current/dist/index.js gateway --port $gateway_port ;"* ]] ||
			return 1
	fi
}

process_generation() {
	local process_stat remainder
	local -a fields
	IFS= read -r process_stat <"$proc_root/$1/stat" || return 1
	[[ "$process_stat" == *') '* ]] || return 1
	remainder="${process_stat##*) }"
	read -r -a fields <<<"$remainder"
	[[ "${fields[19]:-}" =~ ^[1-9][0-9]*$ ]] || return 1
	printf '%s' "${fields[19]}"
}

assert_single_listener() {
	local expected_pid="$1" listing
	listing="$("$ss_bin" -H -ltnp "sport = :$gateway_port")" || return 1
	LISTENER_SNAPSHOT="$listing" EXPECTED_PID="$expected_pid" EXPECTED_PORT="$gateway_port" \
		"$node_bin" -e '
const lines = (process.env.LISTENER_SNAPSHOT ?? "").split("\n").filter(Boolean);
if (!lines.length) process.exit(75);
if (lines.length > 2) process.exit(2);
const families = new Set();
for (const line of lines) {
  const address = line.match(new RegExp("(?:^|\\s)(127\\.0\\.0\\.1|\\[::1\\]):" + process.env.EXPECTED_PORT + "(?:\\s|$)"))?.[1];
  if (!address || families.has(address)) process.exit(2);
  families.add(address);
  const matches = [...line.matchAll(/pid=(\d+)/g)].map((match) => match[1]);
  if (matches.length !== 1 || matches[0] !== process.env.EXPECTED_PID) process.exit(2);
}
'
}

capture_service_generation() {
	local scope="$1"
	assert_exclusive_owner "$scope" || return 1
	if [[ "$scope" == migration-user || "$scope" == migration-switching ]]; then
		read_service user || {
			fail 'legacy user Gateway ownership or entrypoint drifted'
			return 1
		}
	else
		read_service system || {
			fail 'root-owned system Gateway ownership or entrypoint drifted'
			return 1
		}
	fi
	service_generation="$(process_generation "$service_pid")" || {
		fail 'Gateway process generation is unavailable'
		return 1
	}
}

capture_generation() {
	capture_service_generation "$1" || return 1
	assert_single_listener "$service_pid" || {
		fail 'Gateway does not own one exact loopback listener'
		return 1
	}
}

recheck_generation() {
	local scope="$1" expected_pid="$2" expected_generation="$3"
	capture_generation "$scope" || return 1
	[[ "$service_pid" == "$expected_pid" && "$service_generation" == "$expected_generation" ]] ||
		fail 'Gateway process generation changed during deployment'
}

predecessor_is_stopped() {
	local old_pid="$1"
	# Reused or reappearing old PID paths remain ambiguous, even with stopped units.
	[[ -d "$proc_root" && ! -e "$proc_root/$old_pid" && ! -L "$proc_root/$old_pid" ]] || return 1
	stopped_system_owners || return 1
	[[ ! -e "$proc_root/$old_pid" && ! -L "$proc_root/$old_pid" ]]
}

assert_stop_start_contract() {
	local properties
	properties="$(system_systemctl show openclaw-gateway.service --all \
		--property=KillSignal --property=RestartKillSignal --property=FileDescriptorStoreMax \
		--property=NFileDescriptorStore --property=RuntimeDirectory \
		--property=RequiredBy --property=RequisiteOf --property=PartOf --property=ConsistsOf \
		--property=BindsTo --property=BoundBy --property=PropagatesStopTo --property=StopPropagatedFrom \
		--property=Upholds --property=UpheldBy)" || return 1
	printf '%s' "$properties" | proof gateway-stop-start-contract >/dev/null
}

audit_root() {
	proof audit-root "$serving_root" "$root_uid" "$runtime_uid" "$deploy_uid" "$ancestor_boundary" >/dev/null
	proof audit-build "$build_home" "$root_uid" "$deploy_uid" "$deploy_gid" \
		"$runtime_home" "$deploy_file_uid" >/dev/null
}

read_pointer() {
	local pointer="$1" target sha
	[[ -L "$pointer" ]] || return 1
	target="$(realpath "$pointer")" || return 1
	[[ "$(dirname "$target")" == "$releases_root" ]] || return 1
	sha="$(basename "$target")"
	[[ "$sha" =~ ^[a-f0-9]{40}$ ]] || return 1
	proof validate-release "$target" "$sha" "$root_uid" sealed >/dev/null || return 1
	printf '%s\n' "$target"
}

atomic_pointer() {
	local target="$1" pointer="$2" temporary="${2}.next.$$"
	[[ "$(dirname "$target")" == "$releases_root" && "$(dirname "$pointer")" == "$serving_root" ]] ||
		{ fail 'pointer target is outside the sealed serving namespace'; return 1; }
	ln -s "$target" "$temporary" || return 1
	mv -Tf "$temporary" "$pointer" || return 1
	proof sync-directory "$serving_root"
}

assert_protected_identity() {
	local current_identity
	if [[ -n "$reconcile_evidence" ]]; then
		proof recovery-check "$journal_file" "$reconcile_evidence" "$serving_root" "${reconcile_position:-restored}" >/dev/null || return 1
	fi
	current_identity="$(proof fingerprint "$config_file" "$state_database")" || return 1
	if [[ -n "$protected_identity" && "$protected_identity" != "$current_identity" ]]; then
		if [[ "${candidate_config_verification:-0}" == 1 && -z "$reconcile_evidence" ]]; then
			[[ "$verified_pid" == "$candidate_config_pid" && "$verified_generation" == "$candidate_config_generation" ]] || return 1
			recheck_generation system "$verified_pid" "$verified_generation" || return 1
			proof candidate-config-accept "$journal_file" "$serving_root" "$protected_identity" \
				"$verified_pid" "$verified_generation" "$runtime_uid" "$policy_identity" || return 1
			protected_identity="$(proof journal-field "$journal_file" protectedPaths)" || return 1
			current_identity="$(proof fingerprint "$config_file" "$state_database")" || return 1
		fi
	fi
	if [[ -n "$protected_identity" && "$protected_identity" != "$current_identity" ]]; then
		fail 'protected configuration or SQLite device/inode identity changed'
		return 1
	fi
	protected_identity="$current_identity"
}

assert_policy() {
	local observed
	observed="$(proof policy "$config_file")" || return 1
	if [[ -n "$policy_identity" && "$observed" != "$policy_identity" ]]; then
		fail 'configured deployment policy or model drifted'
		return 1
	fi
	policy_identity="$observed"
}

assert_scheduler_owner() {
	local release="$1" scheduler_status
	scheduler_status="$(run_gateway "$release" gateway call update.status --params '{}' --json --timeout 30000)" || {
		printf 'GATEWAY_RPC_FAILED method=update.status exit=%s\n' "$?" >&2
		return 1
	}
	printf '%s' "$scheduler_status" | proof scheduler @stdin >/dev/null
}

retain_suspension_cleanup_offer() {
	local offered offered_lease offered_expiry
	if offered="$(proof_json suspension-offer "$1" 2>/dev/null)"; then
		read -r offered_lease offered_expiry <<<"$offered"
		if [[ -n "$held_suspension" && "$offered_lease" != "$held_suspension" ]]; then
			printf 'SUSPENSION_REPLACEMENT_OFFER expected=%s offered=%s cleanup=offered-only\n' "$held_suspension" "$offered_lease" >&2
		fi
		held_suspension="$offered_lease"; suspension_expiry="$offered_expiry"
		suspension_cleanup_only=1
	fi
}

request_suspension() {
	local release="$1" request="$2" response parsed offered_lease offered_expiry _terminal_blocked
	local failure_code failure_category
	[[ -z "$held_suspension" ]] || { fail 'cannot replace an owned suspension'; return 1; }
	if response="$(run_gateway "$release" gateway call gateway.suspend.prepare \
		--params "$request" --json --timeout 30000)"; then
		:
	else
		failure_code=$?
		failure_category="$(proof_json suspension-prepare-failure "$response" "$failure_code" 2>/dev/null)" || failure_category=unclassified
		printf 'SUSPENSION_PREPARE_FAILED stage=initial exit=%s category=%s\n' \
			"$failure_code" "$failure_category" >&2
		printf 'DEFERRED preserve-only drain preparation unavailable or conflicting\n'
		return 75
	fi
	# An offered lease remains cleanup-only until its complete response passes.
	retain_suspension_cleanup_offer "$response"
	if ! parsed="$(proof_json suspension-prepare "$response")"; then
		printf 'DEFERRED preserve-only drain lease malformed or expired\n'
		return 75
	fi
	read -r suspension_state offered_lease offered_expiry suspension_active_count suspension_retry_after_ms _terminal_blocked suspension_blockers suspension_write_custody <<<"$parsed"
	if [[ "$suspension_state" == BUSY ]]; then
		printf 'DEFERRED Gateway restart drain or another suspension blocks preservation blockers=%s\n' \
			"$suspension_blockers"
		return 75
	fi
	held_suspension="$offered_lease"
	suspension_expiry="$offered_expiry"
	suspension_cleanup_only=0
}

poll_suspension() {
	local release="$1" response params parsed observed_expiry _terminal_blocked rpc_exit category
	local timeout_ms=5000 rpc_budget=5
	params="$(proof suspension-lease-params "$held_suspension")" || {
		printf 'SUSPENSION_POLL_FAILED stage=params method=gateway.suspend.status exit=%s\n' "$?" >&2
		return 1
	}
	response="$(OPENCLAW_TEAM_RPC_BUDGET="$rpc_budget" run_gateway "$release" gateway call gateway.suspend.status \
		--params "$params" --json --timeout "$timeout_ms")" || {
		rpc_exit=$?
		category="$(proof_json suspension-poll-failure "$response" "$rpc_exit" 2>/dev/null)" || category=fatal
		printf 'SUSPENSION_POLL_FAILED stage=rpc method=gateway.suspend.status exit=%s\n' "$rpc_exit" >&2
		[[ "$category" != retry ]] || return 76
		return 1
	}
	parsed="$(proof_json suspension-status "$response" "$held_suspension" "$suspension_expiry")" || {
		printf 'SUSPENSION_POLL_FAILED stage=proof method=gateway.suspend.status exit=%s\n' "$?" >&2
		return 1
	}
	read -r suspension_state observed_expiry suspension_active_count suspension_retry_after_ms _terminal_blocked suspension_blockers suspension_write_custody <<<"$parsed"
	[[ "$observed_expiry" == "$suspension_expiry" ]] || {
		printf 'SUSPENSION_POLL_FAILED stage=expiry method=gateway.suspend.status exit=1\n' >&2
		return 1
	}
}

prepare_suspension() {
	local release="$1" request request_id now_ms remaining_ms
	local remaining_budget sleep_seconds retry_seconds poll_exit
	if [[ ! "$drain_budget" =~ ^[1-9][0-9]*$ ]] || ((drain_budget > 3600)); then
		fail 'preserve-only drain budget must be between 1 and 3600 seconds'
		return 1
	fi
	if ((drain_budget > 30)); then
		printf 'DRAIN_BUDGET_CAPPED requested=%s effective=30\n' "$drain_budget" >&2
		drain_budget=30
	fi
	if [[ ! "$drain_poll_interval" =~ ^[1-9][0-9]*$ ]] || ((drain_poll_interval > 300)); then
		fail 'preserve-only drain status poll interval must be between 1 and 300 seconds'
		return 1
	fi
	suspension_release="$release"
	((suspension_handoff_started == 0)) || [[ -z "$held_suspension" ]] || {
		fail 'cannot prepare during an attempted suspension handoff'; return 1;
	}
	if [[ -z "$held_suspension" ]]; then
		suspension_handoff_started=0; suspension_cleanup_only=0
	fi
	drain_budget_elapsed=0
	request_id="team-release-$$-$(date -u +%Y%m%dT%H%M%SZ)"
	request="$(proof suspension-prepare-params "$request_id")" || return 1
	drain_deadline=$((SECONDS + drain_budget))
	if ! request_suspension "$release" "$request"; then
		return 75
	fi
	while [[ "$suspension_state" == DRAINING ]]; do
		remaining_budget=$((drain_deadline - SECONDS))
		if ((remaining_budget <= 0)); then
			if ((interrupt_after_drain)); then
				drain_budget_elapsed=1
				break
			fi
			printf 'DEFERRED preserve-only Gateway drain exceeded its %s-second budget blockers=%s\n' \
				"$drain_budget" "$suspension_blockers"
			return 75
		fi
		printf 'DRAINING activeCount=%s lease=%s expiresAtMs=%s blockers=%s custody=%s\n' \
			"$suspension_active_count" "$held_suspension" "$suspension_expiry" "$suspension_blockers" "$suspension_write_custody"
		now_ms="$(proof suspension-now)" || return 1
		remaining_ms=$((suspension_expiry - now_ms))
		if ((remaining_ms <= 5000)); then
			printf 'DEFERRED preserve-only Gateway drain lease expired before cutover blockers=%s\n' \
				"$suspension_blockers"
			return 75
		fi
		retry_seconds=$(((suspension_retry_after_ms + 999) / 1000))
		sleep_seconds="$drain_poll_interval"
		if ((retry_seconds > sleep_seconds)); then
			sleep_seconds="$retry_seconds"
		fi
		if ((sleep_seconds > remaining_budget)); then
			sleep_seconds="$remaining_budget"
		fi
		if ((sleep_seconds > 0)); then
			sleep "$sleep_seconds"
		fi
		if ((SECONDS >= drain_deadline)); then
			continue
		fi
		retry_seconds=1
		while true; do
			if poll_suspension "$release"; then break; else poll_exit=$?; fi
			if ((poll_exit != 76)); then
				printf 'DEFERRED preserve-only Gateway drain status changed, expired, or became unavailable blockers=%s\n' \
					"$suspension_blockers"
				return 75
			fi
			# Failed reads grant no authority. Deadline expiry still needs fresh lease,
			# generation, and write-custody proof before any authorized interruption.
			remaining_budget=$((drain_deadline - SECONDS))
			((remaining_budget > 0)) || break
			sleep_seconds="$retry_seconds"
			if ((sleep_seconds > remaining_budget)); then sleep_seconds="$remaining_budget"; fi
			printf 'SUSPENSION_POLL_RETRY backoffSeconds=%s remainingSeconds=%s\n' "$sleep_seconds" "$remaining_budget" >&2
			sleep "$sleep_seconds"
			((SECONDS < drain_deadline)) || break
			now_ms="$(proof suspension-now)" || return 1
			# Leave time for exact-lease cleanup; the drain lease cannot be renewed.
			((suspension_expiry - now_ms > 5000)) || break
			if ((retry_seconds < 4)); then retry_seconds=$((retry_seconds * 2)); fi
		done
	done
	custody_wait_deadline=$((drain_deadline + custody_wait_budget))
	drain_deadline="$custody_wait_deadline"
	if [[ "$suspension_state" != READY ]]; then
		# Write custody is transient (a protected final chat write in flight), so a single
		# refusal on custody alone wastes the whole prepared release. Re-poll for a bounded
		# window while the only refusal is held custody; every other refusal stays final.
		until check_suspension; do
			if [[ "$suspension_state" == DRAINING && "$suspension_write_custody" == held ]] &&
				{ [[ ",$suspension_blockers," != *,terminal-persistence:* ]] || ((${waive_terminal_persistence:-0})); } &&
				((interrupt_after_drain && drain_budget_elapsed && SECONDS < custody_wait_deadline)); then
				now_ms="$(proof suspension-now)" || return 1
				if ((suspension_expiry - now_ms <= 5000)); then
					printf 'DEFERRED bounded interruption lease expired while write custody was held blockers=%s\n' \
						"$suspension_blockers"
					return 75
				fi
				printf 'WAITING transient write custody activeCount=%s blockers=%s\n' \
					"$suspension_active_count" "$suspension_blockers"
				sleep "$custody_wait_interval"
				if ((SECONDS >= custody_wait_deadline)); then
					printf 'DEFERRED bounded interruption write custody wait expired activeCount=%s blockers=%s\n' \
						"$suspension_active_count" "$suspension_blockers"
					return 75
				fi
				continue
			fi
			if ((${waive_terminal_persistence:-0} && !${terminal_persistence_waiver_armed:-0})) &&
				[[ "$suspension_state" == DRAINING ]] && ((interrupt_after_drain && drain_budget_elapsed)); then
				# Operator-explicit: final writes that did not settle within the custody budget are abandoned.
				terminal_persistence_waiver_armed=1
				printf 'TERMINAL_PERSISTENCE_WAIVED activeCount=%s blockers=%s custody=%s\n' \
					"$suspension_active_count" "$suspension_blockers" "$suspension_write_custody"
				continue
			fi
			printf 'DEFERRED bounded interruption lacks a valid owned lease or has pending terminal persistence\n'
			return 75
		done
	fi
	drain_deadline=0
	if [[ "$suspension_state" == DRAINING ]]; then
		printf 'INTERRUPTING authorized after %s-second graceful drain activeCount=%s blockers=%s custody=%s\n' \
			"$drain_budget" "$suspension_active_count" "$suspension_blockers" "$suspension_write_custody"
	fi
}

check_suspension() {
	((suspension_cleanup_only == 0)) || { fail 'suspension offer is cleanup-only, not continued authority'; return 1; }
	poll_suspension "$suspension_release" || {
		printf 'SUSPENSION_CHECK_FAILED stage=status exit=%s\n' "$?" >&2
		return 1
	}
	recheck_generation "${1:-$suspension_scope}" "$suspension_pid" "$suspension_generation" || {
		printf 'SUSPENSION_CHECK_FAILED stage=generation exit=%s\n' "$?" >&2
		return 1
	}
	# Reported writes fence interruption; absent legacy custody is unknown, not clear.
	# Keep the independent terminal-persistence fence for older residents as well.
	{ [[ "$suspension_state" == READY ]] ||
		{ ((interrupt_after_drain && drain_budget_elapsed)) &&
			[[ "$suspension_state" == DRAINING ]] &&
			{ [[ ",$suspension_blockers," != *,terminal-persistence:* ]] || ((${waive_terminal_persistence:-0})); }; }; } &&
		{ [[ "$suspension_write_custody" == clear || "$suspension_write_custody" == unknown ]] ||
			((${waive_terminal_persistence:-0} && ${terminal_persistence_waiver_armed:-0})); } || {
		# Only the native status proof's fixed state and blocker enums/counts reach this receipt.
		printf 'SUSPENSION_CHECK_FAILED stage=policy exit=1 state=%s activeCount=%s blockers=%s custody=%s interruptAfterDrain=%s drainBudgetElapsed=%s\n' \
			"$suspension_state" "$suspension_active_count" "$suspension_blockers" "$suspension_write_custody" "$interrupt_after_drain" "$drain_budget_elapsed" >&2
		return 1
	}
}

arm_suspension_handoff() {
	local params response
	((suspension_cleanup_only == 0)) || { fail 'suspension offer is cleanup-only, not continued authority'; return 1; }
	((interrupt_after_drain && drain_budget_elapsed)) && [[ "$suspension_state" == DRAINING ]] || return 0
	params="$(proof suspension-handoff-params "$held_suspension" "$suspension_pid" "$suspension_instance")" || return 1
	# An uncertain RPC may already have armed natively; never renew after sending it.
	suspension_handoff_started=1
	response="$(run_gateway "$suspension_release" gateway call gateway.suspend.handoff \
		--params "$params" --json --timeout 30000)" || return 1
	proof_json suspension-handoff "$response" "$held_suspension" "$suspension_expiry" >/dev/null || return 1
	# Arming does not renew the lease. A refused or uncertain arm is resumed by the existing exit owner.
	assert_protected_identity && assert_policy && check_suspension || return 1
	printf 'HANDOFF_ARMED lease=%s expiresAtMs=%s pid=%s\n' "$held_suspension" "$suspension_expiry" "$suspension_pid"
}

resume_suspension() {
	local params response saved_deadline saved_drain_deadline lease
	[[ -n "$held_suspension" ]] || return 0
	lease="$held_suspension"
	held_suspension=""
	recheck_generation "$suspension_scope" "$suspension_pid" "$suspension_generation" || {
		fail 'cannot resume suspension on a changed or unavailable lease owner'
		return 1
	}
	saved_deadline="$verification_deadline"
	saved_drain_deadline="$drain_deadline"
	verification_deadline=0
	drain_deadline=0
	params="$(proof suspension-lease-params "$lease")" || return 1
	if ! response="$(run_gateway "$suspension_release" gateway call gateway.suspend.resume \
		--params "$params" --json --timeout 30000)" ||
		! proof_json suspension-resume "$response" >/dev/null; then
		fail 'exact owned suspension resume was not acknowledged; automation stopped'
		return 1
	fi
	verification_deadline="$saved_deadline"
	drain_deadline="$saved_drain_deadline"
	suspension_cleanup_only=0
}

record_failed() {
	local sha="$1"
	[[ "$sha" =~ ^[a-f0-9]{40}$ ]] || return 1
	printf '%s\n' "$sha" >"$serving_root/failed/$sha"
	chmod 0600 "$serving_root/failed/$sha"
	proof sync-directory "$serving_root/failed"
}

verified_markers() {
	# Keep filesystem ownership checks and durable marker I/O in the existing Node runtime.
	"$node_bin" -e '
const fs = require("node:fs"), path = require("node:path");
const [root, rawUid, rawGid, operation, sha] = process.argv.slice(1);
const uid = Number(rawUid), gid = Number(rawGid), directory = path.join(root, "verified");
const validSha = value => /^[a-f0-9]{40}$/.test(value);
const check = (entry, mode, isDirectory) => {
  if (!entry || entry.uid !== uid || entry.gid !== gid || (entry.mode & 0o7777) !== mode ||
      (isDirectory ? !entry.isDirectory() : !entry.isFile() || entry.nlink !== 1))
    throw new Error("verified marker ownership, type, or permissions are unsafe");
};
const sync = pathname => { const fd = fs.openSync(pathname, "r"); try { fs.fsyncSync(fd); } finally { fs.closeSync(fd); } };
if (!["record", "list", "check"].includes(operation) || (operation !== "list" && !validSha(sha)))
  throw new Error("invalid verified marker operation or SHA");
let entry = fs.lstatSync(directory, { throwIfNoEntry: false });
if (!entry && operation === "record") {
  fs.mkdirSync(directory, { mode: 0o700 });
  fs.chownSync(directory, uid, gid);
  sync(root);
  entry = fs.lstatSync(directory);
}
if (!entry) { if (operation === "check") process.exit(1); process.exit(0); }
check(entry, 0o700, true);
const marker = name => {
  const pathname = path.join(directory, name), stat = fs.lstatSync(pathname, { throwIfNoEntry: false });
  if (stat) {
    check(stat, 0o600, false);
    if (fs.readFileSync(pathname, "utf8") !== name + "\n") throw new Error("verified marker content is invalid");
  }
  return stat;
};
if (operation === "record") {
  marker(sha);
  const temporary = path.join(directory, "." + sha + "." + process.pid);
  const fd = fs.openSync(temporary, "wx", 0o600);
  try { fs.fchownSync(fd, uid, gid); fs.writeFileSync(fd, sha + "\n"); fs.fsyncSync(fd); }
  finally { fs.closeSync(fd); }
  fs.renameSync(temporary, path.join(directory, sha));
  sync(directory);
} else if (operation === "check") {
  if (!marker(sha)) process.exit(1);
} else {
  const retained = [];
  for (const name of fs.readdirSync(directory).filter(validSha)) {
    const stat = marker(name), release = path.join(root, "releases", name);
    if (!fs.lstatSync(release, { throwIfNoEntry: false })) {
      fs.unlinkSync(path.join(directory, name));
      sync(directory);
    } else retained.push({ name, modified: stat.mtimeMs });
  }
  retained.sort((a, b) => b.modified - a.modified || a.name.localeCompare(b.name));
  for (const item of retained) process.stdout.write(item.name + "\n");
}
' "$serving_root" "$root_uid" "$root_gid" "$@"
}

refuse_verified_recovery() {
	local candidate_sha="$1" missing
	if [[ -z "$requested_sha" || ! "$requested_sha" =~ ^[a-f0-9]{40}$ ]]; then
		missing='--sha must name one exact verified release SHA'
	elif [[ "$requested_sha" == "$candidate_sha" ]]; then
		missing='requested SHA must be distinct from the journal candidate'
	elif [[ ! -d "$releases_root/$requested_sha" || -L "$releases_root/$requested_sha" ]]; then
		missing='requested release must exist on disk in the sealed serving namespace'
	elif ! verified_markers check "$requested_sha"; then
		missing='requested release must carry a valid root-owned verified/ marker'
	elif ! proof validate-release "$releases_root/$requested_sha" "$requested_sha" "$root_uid" sealed >/dev/null; then
		missing='requested release must be sealed-valid'
	else
		missing='owned restart requires a live generation-bound suspension or proven stopped owners; third-release recovery requires a durable journal transition supported by the admission library'
	fi
	fail "failed rollback requires explicit recovery of a distinct system release; evidence retained; missing precondition: $missing"
}

read_probe() {
	local endpoint="$1" remaining response
	remaining=$((verification_deadline - SECONDS))
	((remaining > 0)) || return 1
	if [[ -n "${2:-}" ]] && ((remaining > $2)); then remaining="$2"; fi
	response="$(bounded "$remaining" "$curl_bin" --silent --show-error \
		--connect-timeout "$remaining" --max-time "$remaining" --write-out $'\n%{http_code}' \
		"http://127.0.0.1:$gateway_port/$endpoint")" || return "$?"
	[[ "$response" == *$'\n'* ]] || return 1
	probe_body="${response%$'\n'*}"
	probe_status="${response##*$'\n'}"
	[[ "$probe_status" =~ ^[0-9]{3}$ && -n "$probe_body" ]]
}

verification_failure() {
	# Only call-site labels and numeric return/timing facts enter these diagnostics.
	printf 'VERIFICATION_FAILED stage=%s exit=%s elapsed_seconds=%s outcome=%s\n' \
		"$1" "$2" "$((SECONDS - $3))" "$4" >&2 || :
}

verification_step() {
	local verification_label="$1" verification_started="$SECONDS" verification_exit
	shift
	if [[ -n "$verification_owned_pid" ]]; then
		if ! capture_service_generation "$verification_owned_scope" ||
			[[ "$service_pid" != "$verification_owned_pid" || "$service_generation" != "$verification_owned_generation" ]]; then
			verification_failure "$verification_label" 1 "$verification_started" generation-changed
			return 1
		fi
	fi
	# Keep function mutations in this shell; callers retain their existing OR-list policy.
	if "$@"; then return 0; else verification_exit=$?; fi
	verification_failure "$verification_label" "$verification_exit" "$verification_started" nonzero-exit
	return "$verification_exit"
}

verify_convergence() {
	local release="$1" expected_info="$2" scope="${3:-system}" expected_build channels gateway_json
	local observed_process health_json health_status startup_json startup_status ready_json ready_status
	local convergence_state channel_state timeout_ms verification_result_started listener_exit
	local -a probe_args channel_args
	cpu_degraded_waived=0
	profile_convergence=""
	expected_build="$(INFO_JSON="$expected_info" verification_step convergence.expected-build "$node_bin" -e 'process.stdout.write(JSON.parse(process.env.INFO_JSON).buildId)')" || return 1
	verification_step convergence.release proof validate-release "$release" "$(basename "$release")" "$root_uid" sealed >/dev/null || return 1
	verification_step convergence.generation capture_service_generation "$scope" || return 1
	if assert_single_listener "$service_pid"; then :; else
		listener_exit=$?
		# An empty socket table is normal while this bound generation starts.
		((listener_exit == 75)) && return 75
		verification_failure convergence.generation "$listener_exit" "$SECONDS" invalid-listener
		fail 'Gateway does not own one exact loopback listener'
		return 1
	fi
	verified_pid="$service_pid"
	verified_generation="$service_generation"
	if [[ "$scope" == system ]]; then
		verification_step convergence.runtime proof gateway-runtime process "$serving_root" "$gateway_runtime_snapshot" "$proc_root" "$verified_pid" >/dev/null || return 1
	fi
	timeout_ms=$(((verification_deadline - SECONDS) * 1000))
	verification_step convergence.process-budget test "$timeout_ms" -gt 0 || return 1
	observed_process="$(verification_step convergence.process-rpc run_gateway "$release" gateway call system.info --params '{}' --json --timeout "$timeout_ms")" || return 1
	verified_instance="$(PROCESS_JSON="$observed_process" EXPECTED_PID="$verified_pid" verification_step convergence.process-identity "$node_bin" -e '
const processInfo=JSON.parse(process.env.PROCESS_JSON);
if(processInfo.pid!==Number(process.env.EXPECTED_PID)||typeof processInfo.processInstanceId!=="string"||!processInfo.processInstanceId)process.exit(2);
process.stdout.write(processInfo.processInstanceId);
')" || return 1
	verification_step convergence.healthz read_probe healthz || return 1
	health_json="$probe_body"
	health_status="$probe_status"
	verification_step convergence.startupz read_probe startupz || return 1
	startup_json="$probe_body"
	startup_status="$probe_status"
	timeout_ms=$(((verification_deadline - SECONDS) * 1000))
	verification_step convergence.status-budget test "$timeout_ms" -gt 0 || return 1
	gateway_json="$(verification_step convergence.gateway-status run_gateway "$release" gateway status --json --timeout "$timeout_ms")" || return 1
	verification_step convergence.gateway-status-check proof_json gateway-status "$gateway_json" "$expected_build" >/dev/null || return 1
	verification_step convergence.readyz read_probe readyz || return 1
	ready_json="$probe_body"
	ready_status="$probe_status"
	timeout_ms=$(((verification_deadline - SECONDS) * 1000))
	verification_step convergence.channels-budget test "$timeout_ms" -gt 0 || return 1
	channels="$(verification_step convergence.channels run_gateway "$release" gateway call channels.status \
		--params "{\"probe\":true,\"timeoutMs\":$timeout_ms}" --json --timeout "$timeout_ms")" || return 1
	# Full argv keeps the strict path safe under nounset without an empty optional array.
	probe_args=(probes "$health_json" "$health_status" "$startup_json" "$startup_status" "$ready_json" "$ready_status" @stdin)
	channel_args=(channels @stdin)
	if ((allow_cpu_degraded == 1)); then
		probe_args+=(--allow-cpu-degraded)
		channel_args+=(--allow-cpu-degraded)
	fi
	verification_result_started="$SECONDS"
	convergence_state="$(printf '%s' "$channels" | verification_step convergence.probes proof "${probe_args[@]}")" || return 1
	case "$convergence_state" in
	READY) ;;
	READY_CPU_DEGRADED_OPERATOR_WAIVED)
		((allow_cpu_degraded == 1)) || { verification_failure convergence.probe-result 0 "$verification_result_started" waiver-not-authorized; return 1; }
		;;
	RECOVERING) verification_failure convergence.probe-result 0 "$verification_result_started" recovering; return 1 ;;
	*) verification_failure convergence.probe-result 0 "$verification_result_started" unexpected-result; return 1 ;;
	esac
	verification_result_started="$SECONDS"
	channel_state="$(printf '%s' "$channels" | verification_step convergence.channel-check proof "${channel_args[@]}")" || return 1
	case "$channel_state" in
	CHANNELS_OK) ;;
	CHANNELS_CPU_DEGRADED_OPERATOR_WAIVED)
		((allow_cpu_degraded == 1)) || { verification_failure convergence.channel-result 0 "$verification_result_started" waiver-not-authorized; return 1; }
		;;
	*) verification_failure convergence.channel-result 0 "$verification_result_started" unexpected-result; return 1 ;;
	esac
	timeout_ms=$(((verification_deadline - SECONDS) * 1000))
	verification_step convergence.sessions-budget test "$timeout_ms" -gt 0 || return 1
	verification_step convergence.sessions-list run_gateway "$release" gateway call sessions.list \
		--params '{"limit":1}' --json --timeout "$timeout_ms" >/dev/null || return 1
	verification_step convergence.protected-state assert_protected_identity || return 1
	verification_step convergence.policy assert_policy || return 1
	verification_step convergence.scheduler assert_scheduler_owner "$release" || return 1
	if [[ "$convergence_state" == READY_CPU_DEGRADED_OPERATOR_WAIVED ||
		"$channel_state" == CHANNELS_CPU_DEGRADED_OPERATOR_WAIVED ]]; then cpu_degraded_waived=1; fi
	if ((arm_steady_profile && !profile_finished)) && [[ -n "$profile_operation" ]]; then
		printf -v profile_convergence '{"operation":"%s","readiness":%s,"channels":%s,"convergenceState":"%s","channelState":"%s"}' \
			"$profile_operation" "$ready_json" "$channels" "$convergence_state" "$channel_state"
	fi
	return 0
}

wait_for_marker_readiness() {
	local release="$1" scope="$2" startup_json startup_status readiness_state
	local remaining timeout_ms observed_process pause probe_exit
	while :; do
		verification_step marker.readiness-generation recheck_generation "$scope" "$verified_pid" "$verified_generation" || return 1
		remaining=$((verification_deadline - SECONDS))
		verification_step marker.readiness-budget test "$remaining" -gt 0 || return 1
		readiness_state=RECOVERING
		probe_exit=0
		verification_step marker.startupz read_probe startupz 10 || probe_exit=$?
		if ((probe_exit == 0)); then
			startup_json="$probe_body"; startup_status="$probe_status"
			verification_step marker.readyz read_probe readyz 10 || probe_exit=$?
			if ((probe_exit == 0)); then
				readiness_state="$(verification_step marker.readiness proof marker-readiness "$startup_json" "$startup_status" "$probe_body" "$probe_status")" || return 1
			fi
		fi
		# curl connection/timeout and the outer process deadline report no snapshot.
		if ((probe_exit != 0)); then
			[[ "$probe_exit" == 7 || "$probe_exit" == 28 || "$probe_exit" == 124 ]] || return "$probe_exit"
		fi
		case "$readiness_state" in
		READY)
			timeout_ms=$(((verification_deadline - SECONDS) * 1000)); ((timeout_ms <= 10000)) || timeout_ms=10000
			verification_step marker.readiness-rpc-budget test "$timeout_ms" -gt 0 || return 1
			probe_exit=0
			observed_process="$(verification_step marker.readiness-rpc run_marker_rpc "$release" system.info '{}' "$timeout_ms")" || probe_exit=$?
			if ((probe_exit == 0)); then
				PROCESS_JSON="$observed_process" EXPECTED_PID="$verified_pid" EXPECTED_INSTANCE="$verified_instance" verification_step marker.readiness-process "$node_bin" -e '
const p=JSON.parse(process.env.PROCESS_JSON);
if(p.pid!==Number(process.env.EXPECTED_PID)||p.processInstanceId!==process.env.EXPECTED_INSTANCE)process.exit(2);
' || return 1
				verification_step marker.readiness-generation-after recheck_generation "$scope" "$verified_pid" "$verified_generation" || return 1
				return 0
			fi
			[[ "$probe_exit" == 78 && "$observed_process" == RETRYABLE_READINESS_TRANSPORT ]] || return "$probe_exit"
			;;
		RECOVERING) ;;
		*) return 1 ;;
		esac
		remaining=$((verification_deadline - SECONDS))
		verification_step marker.readiness-pause-budget test "$remaining" -gt 0 || return 1
		pause=3; ((pause <= remaining)) || pause="$remaining"
		verification_step marker.readiness-pause bounded "$remaining" sleep "$pause" || return 1
	done
}

verify_marker() {
	local release="$1" scope="${2:-system}"
	local marker session approvals_before approvals_after reply payload run_id terminal_state marker_policy
	local observed_process timeout_ms remaining observation_pause observation_pause_ms observation_exit
	# Logical entries may retire while their exact transcript generation stays preserved.
	printf '%s' "$session_witness" | verification_step marker.session-preservation proof session-preservation-verify "$release" "$release" "$state_database" >/dev/null || return 1
	marker="TEAM_RELEASE_$(date -u +%Y%m%dT%H%M%SZ)_$$"
	session="team-release-$marker"
	approvals_before="$(verification_step marker.approvals-before proof approvals "$state_database" "$session")" || return 1
	verification_step marker.approvals-empty test "$approvals_before" = 0 || {
		fail 'verification session already has approval history'
		return 1
	}
	marker_policy="$(verification_step marker.model-policy run_gateway_node --input-type=module -e '
import { pathToFileURL } from "node:url";
const [release, raw] = process.argv.slice(1), policy = JSON.parse(raw);
const { splitTrailingAuthProfile } = await import(pathToFileURL(`${release}/dist/plugin-sdk/model-ref-parse.js`).href);
process.stdout.write(JSON.stringify({model:splitTrailingAuthProfile(policy.model).model,
  fallbacks:policy.fallbacks.map(value=>splitTrailingAuthProfile(value).model)}));
' "$release" "$policy_identity")" || return 1
	wait_for_marker_readiness "$release" "$scope" || return 1
	remaining=$((verification_deadline - SECONDS))
	verification_step marker.run-budget test "$remaining" -gt 0 || return 1
	# Native ACK plus same-run observations keep proof independent of one long-lived
	# response socket; the native idempotency key also reconciles a lost ACK.
	payload="$(MARKER="$marker" SESSION="$session" REMAINING="$remaining" "$node_bin" -e '
const session=process.env.SESSION,agentId=JSON.parse(process.env.OPENCLAW_TEAM_OPERATOR_PROFILE).modelAgent;
process.stdout.write(JSON.stringify({agentId,sessionId:session,
  sessionKey:`agent:${agentId}:explicit:${session.toLowerCase()}`,deliver:false,
  message:`Output only this exact string, byte-for-byte, with no prefix or suffix: ${process.env.MARKER}`,
  timeout:Number(process.env.REMAINING),idempotencyKey:require("node:crypto").randomUUID()}));
')" || return 1
	# Native idempotency owns uncertain dispatch: reconcile only with the identical
	# payload and live readiness, never mint another run or renew this deadline.
	while :; do
		timeout_ms=$((remaining * 1000)); ((timeout_ms <= 60000)) || timeout_ms=60000
		observation_exit=0
		reply="$(verification_step marker.run run_marker_rpc "$release" agent "$payload" "$timeout_ms")" || observation_exit=$?
		((observation_exit == 0)) && break
		{ [[ "$observation_exit" == 76 && "$reply" == RETRYABLE_ADMISSION_PENDING ]] ||
			[[ "$observation_exit" == 77 && "$reply" == RETRYABLE_DISPATCH_TIMEOUT ]]; } || return "$observation_exit"
		verification_step marker.admission-generation recheck_generation "$scope" "$verified_pid" "$verified_generation" || return 1
		printf 'MARKER_ADMISSION_RETRY same-payload=required same-generation=required exit=%s\n' "$observation_exit" >&2
		remaining=$((verification_deadline - SECONDS))
		verification_step marker.admission-budget test "$remaining" -gt 3 || return 1
		verification_step marker.admission-pause bounded "$remaining" sleep 3 || return 1
		wait_for_marker_readiness "$release" "$scope" || return 1
		remaining=$((verification_deadline - SECONDS))
	done
	run_id="$(REPLY_JSON="$reply" REQUEST_JSON="$payload" SESSION="$session" verification_step marker.accepted "$node_bin" -e '
const reply=JSON.parse(process.env.REPLY_JSON),agentId=JSON.parse(process.env.OPENCLAW_TEAM_OPERATOR_PROFILE).modelAgent;
if(reply.runId!==JSON.parse(process.env.REQUEST_JSON).idempotencyKey)process.exit(2);
// Native replay can acknowledge an in-flight reservation or return its terminal cache.
if(!["accepted","in_flight","ok"].includes(reply.status)||
  (reply.agentId!==undefined&&reply.agentId!==agentId)||
  (reply.sessionKey!==undefined&&reply.sessionKey!==`agent:${agentId}:explicit:${process.env.SESSION.toLowerCase()}`)||
  (reply.status==="accepted"&&(reply.agentId!==agentId||reply.sessionKey===undefined)))process.exit(2);
process.stdout.write(reply.runId);
')" || return 1
	while :; do
		verification_step marker.wait-generation recheck_generation "$scope" "$verified_pid" "$verified_generation" || return 1
		remaining=$((verification_deadline - SECONDS))
		verification_step marker.wait-budget test "$remaining" -gt 0 || return 1
		timeout_ms=$((remaining * 1000)); ((timeout_ms <= 60000)) || timeout_ms=60000
		# Keep the RPC budget available for connection and response handling. Pace
		# pending snapshots here instead of spending that budget in a server wait.
		payload="$(RUN_ID="$run_id" "$node_bin" -e 'process.stdout.write(JSON.stringify({runId:process.env.RUN_ID,timeoutMs:0}))')" || return 1
		observation_exit=0
		reply="$(verification_step marker.wait run_marker_rpc "$release" agent.wait "$payload" "$timeout_ms")" || observation_exit=$?
		verification_step marker.wait-generation-after recheck_generation "$scope" "$verified_pid" "$verified_generation" || return 1
		if ((observation_exit != 0)); then
			# The runner emits this pair only for a typed transient read failure.
			[[ "$observation_exit" == 75 && "$reply" == RETRYABLE_OBSERVATION_TRANSPORT ]] || return "$observation_exit"
			printf 'MARKER_OBSERVATION_RETRY same-run=required same-generation=required\n' >&2
		else
			terminal_state="$(REPLY_JSON="$reply" RUN_ID="$run_id" SESSION="$session" EXPECTED_REPLY="$marker" POLICY_JSON="$marker_policy" verification_step marker.reply-check "$node_bin" -e '
const r=JSON.parse(process.env.REPLY_JSON), receipt=r.terminalReceipt, policy=JSON.parse(process.env.POLICY_JSON);
// A length stop can omit the visible truncation notice from terminalReply.
if(r.runId!==process.env.RUN_ID||r.error||r.pendingError===true||
  (r.stopReason!==undefined&&r.stopReason!=="stop"))process.exit(2);
if((r.status==="pending"||r.status==="timeout")&&!r.endedAt&&!receipt&&!r.terminalReply&&!r.stopReason) {
  process.stdout.write("PENDING");
} else {
  if(r.status!=="ok"||r.pendingError===true||r.terminalReply?.disposition!=="visible"||
    r.terminalReply.text!==process.env.EXPECTED_REPLY||receipt?.runId!==process.env.RUN_ID||
    receipt.sessionId!==process.env.SESSION||typeof receipt.turnId!=="string"||!receipt.turnId.trim()||
    receipt.terminalDisposition!=="visible"||receipt.requested?.provider+"/"+receipt.requested?.model!==policy.model||
    ![policy.model,...policy.fallbacks].includes(receipt.effective?.provider+"/"+receipt.effective?.model))process.exit(2);
  process.stdout.write("VERIFIED");
}
')" || return 1
			[[ "$terminal_state" == VERIFIED ]] && break
		fi
		remaining=$((verification_deadline - SECONDS))
		verification_step marker.pending-budget test "$remaining" -gt 0 || return 1
		# Keep half the remaining budget for another snapshot near the deadline.
		observation_pause_ms=$((remaining * 500))
		((observation_pause_ms <= 10000)) || observation_pause_ms=10000
		printf -v observation_pause '%d.%03d' "$((observation_pause_ms / 1000))" "$((observation_pause_ms % 1000))"
		verification_step marker.pending-pause bounded "$remaining" sleep "$observation_pause" || return 1
	done
	printf 'MARKER_VERIFIED run=%s observation=agent.wait same-generation=required\n' "$run_id"
	approvals_after="$(verification_step marker.approvals-after proof approvals "$state_database" "$session")" || return 1
	verification_step marker.approvals-unchanged test "$approvals_before" = "$approvals_after" || {
		fail 'verification session created an approval'
		return 1
	}
	verification_step marker.owner assert_exclusive_owner "$scope" || return 1
	verification_step marker.generation recheck_generation "$scope" "$verified_pid" "$verified_generation" || return 1
	timeout_ms=$(((verification_deadline - SECONDS) * 1000))
	verification_step marker.process-budget test "$timeout_ms" -gt 0 || return 1
	observed_process="$(verification_step marker.process-rpc run_gateway "$release" gateway call system.info --params '{}' --json --timeout "$timeout_ms")" || return 1
	PROCESS_JSON="$observed_process" EXPECTED_PID="$verified_pid" EXPECTED_INSTANCE="$verified_instance" verification_step marker.process-identity "$node_bin" -e '
const processInfo=JSON.parse(process.env.PROCESS_JSON);
if(processInfo.pid!==Number(process.env.EXPECTED_PID)||processInfo.processInstanceId!==process.env.EXPECTED_INSTANCE)process.exit(2);
' || return 1
	verification_step marker.protected-state assert_protected_identity || return 1
	verification_step marker.policy assert_policy || return 1
}

load_journal_witness() {
	local original
	[[ -n "$session_witness_record" ]] || session_witness_record="$(proof journal-read "$journal_file")" || return 1
	original="$(proof journal-original-witness "$journal_file" "$session_witness_record")" || return 1
	if [[ -n "$original" ]]; then
		session_witness="$original"
		session_proof="original-pre-cutover"
	fi
}

wait_for_live() {
	local release="$1" expected_info="$2" scope="${3:-system}" sleep_seconds verification_deadline convergence_exit listener_wait_logged=0
	local verification_owned_pid="${4:-}" verification_owned_generation="${5:-}" verification_owned_scope="$scope"
	local +x cpu_degraded_waived=0
	# Descriptor-less legacy recovery and no-journal checks can prove only fresh current state.
	[[ -n "$session_witness" ]] || session_witness="$(verification_step wait-for-live.session-witness proof session-preservation-witness "$release" "$release" "$state_database")" || return 1
	verification_deadline=$((SECONDS + verification_budget))
	while ((SECONDS < verification_deadline)); do
		if [[ -n "$verification_owned_pid" ]]; then
			capture_service_generation "$scope" || return 1
			[[ "$service_pid" == "$verification_owned_pid" && "$service_generation" == "$verification_owned_generation" ]] || {
				fail 'started recovery Gateway generation changed; journal retained'
				return 1
			}
		fi
		if verify_convergence "$release" "$expected_info" "$scope"; then
			verify_marker "$release" "$scope" || return $?
			if ((cpu_degraded_waived == 1)); then
				printf 'CPU_DEGRADED_OPERATOR_WAIVED scope=wait_for_live exception=performance-only sha=%s pid=%s generation=%s owner=%s final-deployment-acceptance=not-asserted\n' \
					"$(basename "$release")" "$verified_pid" "$verified_generation" "$scope"
				printf 'WARNING performance-only CPU/operator waiver used for sha=%s pid=%s generation=%s owner=%s; native degradation unchanged; wait_for_live checks passed, final deployment acceptance remains with caller\n' \
					"$(basename "$release")" "$verified_pid" "$verified_generation" "$scope" >&2
			fi
			return 0
		else
			convergence_exit=$?
			if ((convergence_exit == 75 && listener_wait_logged == 0)); then
				printf 'VERIFICATION_WAITING stage=convergence.listener outcome=not-bound\n' >&2
				listener_wait_logged=1
			fi
		fi
		sleep_seconds="${OPENCLAW_TEAM_RETRY_SECONDS:-2}"
		((SECONDS < verification_deadline)) || break
		if ((SECONDS + sleep_seconds > verification_deadline)); then
			sleep_seconds=$((verification_deadline - SECONDS))
		fi
		sleep "$sleep_seconds"
	done
	verification_failure wait-for-live.budget 1 "$((verification_deadline - verification_budget))" retry-budget-ended
	return 1
}

migration_native_guard() {
	local unit_path response conditions executable
	response="$(bounded 15 "$busctl_bin" --system --json=short call org.freedesktop.systemd1 \
		/org/freedesktop/systemd1 org.freedesktop.systemd1.Manager GetUnit s openclaw-gateway.service)" || return 1
	unit_path="$(proof migration-unit-path "$response")" || return 1
	conditions="$(bounded 15 "$busctl_bin" --system --json=short get-property org.freedesktop.systemd1 \
		"$unit_path" org.freedesktop.systemd1.Unit Conditions)" || return 1
	executable="$(bounded 15 "$busctl_bin" --system --json=short get-property org.freedesktop.systemd1 \
		"$unit_path" org.freedesktop.systemd1.Service ExecConditionEx)" || return 1
	proof migration-guard-verify "$migration_guard" "$conditions" "$executable" "$serving_root" >/dev/null
}

migration_install_guard() {
	local fragment
	fragment="$(system_systemctl show openclaw-gateway.service --property=FragmentPath --value)" || return 1
	migration_guard="$(proof migration-guard-install "$fragment" "$serving_root" "$root_uid" "$ancestor_boundary")" || return 1
	system_systemctl daemon-reload || return 1
	migration_native_guard
}

migration_check_existing_guard() {
	local asset="$ancestor_boundary/etc/systemd/system/openclaw-gateway.service.d/zz-openclaw-agent-schema-migration.conf"
	if [[ -e "$asset" || -L "$asset" ]]; then
		migration_guard="$(proof migration-guard-descriptor "$serving_root")" || return 1
		migration_native_guard || return 1
		if [[ ! -e "$journal_file" ]] || ! proof journal-field "$journal_file" nativeUpdate.backupRoot >/dev/null 2>&1; then
			proof migration-permit-present "$serving_root" >/dev/null || return 1
		fi
	fi
}

stopped_system_owners() {
	local snapshot key value active="" sub="" main="" control="" job="unknown" listeners
	snapshot="$(system_systemctl show openclaw-gateway.service --property=ActiveState --property=SubState \
		--property=MainPID --property=ControlPID --property=Job)" || return 1
	while IFS='=' read -r key value; do
		case "$key" in
		ActiveState) active="$value" ;;
		SubState) sub="$value" ;;
		MainPID) main="$value" ;;
		ControlPID) control="$value" ;;
		Job) job="$value" ;;
		esac
	done <<<"$snapshot"
	[[ ("$active" == inactive && "$sub" == dead || "$active" == failed && "$sub" == failed) && "$main" == 0 && "$control" == 0 &&
		("$job" == "" || "$job" == 0) && "$(user_state)" == inactive &&
		"$(user_unit_state)" == disabled && "$(system_unit_state)" == enabled ]] || {
		fail 'stopped Gateway proof requires system/user owners without queued start/control jobs'
		return 1
	}
	if ! snapshot="$(user_systemctl show openclaw-gateway.service --property=MainPID --property=ControlPID --property=Job)"; then
		user_manager_stopped || return 1
	else
		main=unknown; control=unknown; job=unknown
		while IFS='=' read -r key value; do
			case "$key" in MainPID) main="$value" ;; ControlPID) control="$value" ;; Job) job="$value" ;; esac
		done <<<"$snapshot"
		[[ "$main" == 0 && "$control" == 0 && ("$job" == "" || "$job" == 0) ]] || {
			fail 'stopped Gateway proof refuses a legacy user start job or control process'
			return 1
		}
	fi
	listeners="$("$ss_bin" -H -ltnp "sport = :$gateway_port")" || return 1
	[[ -z "$listeners" ]] || { fail 'stopped Gateway proof still has a Gateway listener'; return 1; }
}

stopped_restored_snapshot() {
	local original_pid="$1" system_snapshot user_snapshot listeners
	local -a properties=(ActiveState SubState MainPID ControlPID Job UnitFileState ExecMainPID InvocationID ExecMainStartTimestampMonotonic ControlGroup)
	local -a arguments=()
	local property
	stopped_system_owners || return 1
	for property in "${properties[@]}"; do arguments+=("--property=$property"); done
	system_snapshot="$(system_systemctl show openclaw-gateway.service "${arguments[@]}" \
		--property=ExecStart --property=User --property=Group --property=Environment --property=Slice)" || return 1
	user_snapshot="$(user_systemctl show openclaw-gateway.service "${arguments[@]}")" || return 1
	listeners="$("$ss_bin" -H -ltnp "sport = :$gateway_port")" || return 1
	[[ -z "$listeners" ]] || return 1
	SYSTEM_SNAPSHOT="$system_snapshot" USER_SNAPSHOT="$user_snapshot" GATEWAY_RUNTIME_BIN="$gateway_runtime_bin" "$node_bin" -e '
const fs = require("node:fs"), path = require("node:path"), crypto = require("node:crypto");
const [boundary, procRoot, originalPid, servingRoot, runtimeHome, port, rawUid, rawGid] = process.argv.slice(1);
const requireFact = (ok, message) => { if (!ok) throw new Error(message); };
const uid = Number(rawUid), gid = Number(rawGid);
requireFact([rawUid, rawGid].every(value => /^[0-9]+$/.test(value)) &&
  [uid, gid].every(value => Number.isSafeInteger(value) && value >= 0), "invalid cgroup owner identity");
const fields = ["ActiveState", "SubState", "MainPID", "ControlPID", "Job", "UnitFileState",
  "ExecMainPID", "InvocationID", "ExecMainStartTimestampMonotonic", "ControlGroup"];
function parse(raw, extra = []) {
  const result = {};
  for (const line of raw.split("\n")) {
    const equal = line.indexOf("=");
    requireFact(equal > 0 && !Object.hasOwn(result, line.slice(0, equal)), "ambiguous stopped unit snapshot");
    result[line.slice(0, equal)] = line.slice(equal + 1);
  }
  requireFact(Object.keys(result).length === fields.length + extra.length &&
    [...fields, ...extra].every(name => Object.hasOwn(result, name)), "incomplete stopped unit snapshot");
  return Object.fromEntries([...fields, ...extra].map(name => [name, result[name]]));
}
const system = parse(process.env.SYSTEM_SNAPSHOT, ["ExecStart", "User", "Group", "Environment", "Slice"]);
const user = parse(process.env.USER_SNAPSHOT);
const absent = pathname => !fs.lstatSync(pathname, { throwIfNoEntry: false });
requireFact(fs.lstatSync(procRoot).isDirectory() && fs.realpathSync(procRoot) === procRoot, "proc root is not canonical");
requireFact(/^[1-9][0-9]*$/.test(originalPid), "invalid stopped process binding");
for (const [unit, enabled, allowFailed] of [[system, "enabled", true], [user, "disabled", false]]) {
  requireFact((unit.ActiveState === "inactive" && unit.SubState === "dead" ||
    allowFailed && unit.ActiveState === "failed" && unit.SubState === "failed") &&
    unit.MainPID === "0" && unit.ControlPID === "0" && ["", "0"].includes(unit.Job) &&
    unit.UnitFileState === enabled, "Gateway owner or job is not stopped");
  requireFact(/^(0|[1-9][0-9]*)$/.test(unit.ExecMainPID) &&
    /^[0-9]+$/.test(unit.ExecMainStartTimestampMonotonic) &&
    (unit.InvocationID === "" || /^[a-f0-9]{32}$/.test(unit.InvocationID)), "last execution binding is malformed");
  if (unit.ExecMainPID === "0") {
    requireFact(unit.ExecMainStartTimestampMonotonic === "0" && unit.InvocationID === "" &&
      unit.ControlGroup === "", "zero Gateway PID has inconsistent execution history");
  } else {
    requireFact(/^[a-f0-9]{32}$/.test(unit.InvocationID) && BigInt(unit.ExecMainStartTimestampMonotonic) > 0n &&
      absent(path.join(procRoot, unit.ExecMainPID)), "last Gateway process is not absent");
  }
}
requireFact(system.ExecMainPID !== "0" && absent(path.join(procRoot, originalPid)), "stopped predecessor history is unavailable");
requireFact(user.ExecMainPID === "0", "stopped recovery requires a never-started user Gateway");
requireFact(system.Slice === "system.slice", "stopped system Gateway slice is not canonical");
requireFact(system.User === "openclaw" && system.Group === "openclaw" &&
  (" " + system.Environment + " ").includes(" HOME=" + runtimeHome + " ") &&
  (" " + system.Environment + " ").includes(" OPENCLAW_SUPERVISOR_MODE=external ") &&
  system.ExecStart.includes("argv[]=" + process.env.GATEWAY_RUNTIME_BIN + " " + servingRoot + "/current/dist/index.js gateway --port " + port + " ;"),
  "stopped Gateway execution configuration changed");
const root = path.join(boundary, "sys/fs/cgroup");
const identity = entry => ({ dev: entry.dev, ino: entry.ino, uid: entry.uid, gid: entry.gid, mode: entry.mode & 0o7777 });
const owned = entry => entry.uid === uid && entry.gid === gid && !(entry.mode & 0o022);
const equal = (left, right) => JSON.stringify(left) === JSON.stringify(right);
function directoryIdentity(directory) {
  const entry = fs.lstatSync(directory);
  requireFact(entry.isDirectory() && !entry.isSymbolicLink() && owned(entry), "cgroup directory is delegated or unsafe");
  return identity(entry);
}
requireFact(path.resolve(boundary) === boundary && fs.realpathSync(root) === root, "cgroup root is not canonical");
const ancestors = [boundary, path.join(boundary, "sys"), path.join(boundary, "sys/fs")]
  .map(directory => ({ path: directory, identity: directoryIdentity(directory) }));
function controlFile(file, read = false) {
  const before = fs.lstatSync(file);
  requireFact(before.isFile() && owned(before), "cgroup control is delegated or unsafe");
  const fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
  try {
    const opened = fs.fstatSync(fd);
    requireFact(opened.isFile() && owned(opened) && equal(identity(before), identity(opened)), "cgroup control changed at open");
    let contents;
    if (read) {
      const bytes = Buffer.alloc(257), length = fs.readSync(fd, bytes, 0, bytes.length, 0);
      requireFact(length <= 256, "Gateway cgroup events are oversized");
      contents = bytes.subarray(0, length).toString("utf8");
    }
    requireFact(equal(identity(opened), identity(fs.fstatSync(fd))) &&
      equal(identity(opened), identity(fs.lstatSync(file))), "cgroup control changed during inspection");
    return { identity: identity(opened), ...(read ? { contents } : {}) };
  } finally { fs.closeSync(fd); }
}
function group(unit, name) {
  requireFact(unit.ControlGroup === "" || unit.ControlGroup === name, "Gateway cgroup is not canonical");
  const hierarchy = [];
  const inspect = directory => {
    const entry = directoryIdentity(directory);
    const controls = ["cgroup.procs", "cgroup.threads", "cgroup.subtree_control"]
      .map(control => ({ name: control, ...controlFile(path.join(directory, control)) }));
    requireFact(equal(entry, directoryIdentity(directory)), "cgroup directory changed during inspection");
    hierarchy.push({ path: directory, directory: entry, controls });
  };
  inspect(root);
  let current = root;
  for (const part of name.split("/").filter(Boolean)) {
    current = path.join(current, part);
    const entry = fs.lstatSync(current, { throwIfNoEntry: false });
    if (!entry) return { path: name, present: false, hierarchy };
    inspect(current);
  }
  const directory = directoryIdentity(current);
  for (const child of fs.readdirSync(current)) {
    const entry = fs.lstatSync(path.join(current, child));
    requireFact(!entry.isDirectory() && !entry.isSymbolicLink(), "stopped Gateway cgroup has an unchecked subgroup or symlink");
  }
  const eventFile = controlFile(path.join(current, "cgroup.events"), true), events = eventFile.contents;
  const values = new Map();
  for (const line of events.trim().split("\n")) {
    const match = /^([a-z_]+) ([0-9]+)$/.exec(line);
    requireFact(match && !values.has(match[1]), "Gateway cgroup population is ambiguous");
    values.set(match[1], match[2]);
  }
  requireFact(values.get("populated") === "0", "Gateway cgroup still contains processes");
  requireFact(equal(directory, directoryIdentity(current)), "Gateway cgroup changed during inspection");
  return { path: name, present: true, directory, events, eventIdentity: eventFile.identity, hierarchy };
}
const groups = [group(system, "/system.slice/openclaw-gateway.service")];
for (const pid of [originalPid, system.ExecMainPID])
  requireFact(absent(path.join(procRoot, pid)), "stopped Gateway process reappeared");
const config = crypto.createHash("sha256").update(JSON.stringify([
  system.ExecStart, system.User, system.Group, system.Environment])).digest("hex");
for (const name of ["ExecStart", "User", "Group", "Environment"]) delete system[name];
process.stdout.write(JSON.stringify({ system, user, ancestors, groups, config }));
' "$ancestor_boundary" "$proc_root" "$original_pid" "$serving_root" "$runtime_home" "$gateway_port" "$root_uid" "$root_gid"
}

install_migration_guard_current() {
	[[ ! -e "$journal_file" && ! -L "$journal_file" ]] || { fail 'native guard bootstrap refuses a pending journal'; return 1; }
	local release
	release="$(read_pointer "$current_link")" || return 1
	[[ "$(basename "$release")" == "$operator_expected_sha" ]] || return 1
	recheck_generation system "$operator_expected_pid" "$operator_expected_generation" || return 1
	[[ "$(realpath "$proc_root/$service_pid/cwd")" == "$release" ]] || return 1
	assert_protected_identity || return 1
	assert_policy || return 1
	proof compatibility "$release" "$release" "$state_database" >/dev/null || return 1
	assert_frozen_predecessor || return 1
	migration_install_guard || return 1
	recheck_generation system "$operator_expected_pid" "$operator_expected_generation" || return 1
	[[ "$(read_pointer "$current_link")" == "$release" ]] || return 1
	assert_protected_identity || return 1
	assert_policy || return 1
	proof migration-permit-present "$serving_root" >/dev/null || return 1
	printf 'MIGRATION_GUARD_INSTALLED sha=%s restart=0 native-condition=positive-path-exists\n' "$operator_expected_sha"
}

bind_successor_generation() {
	# Type=simple acknowledges fork, not exec or listener readiness.
	# Bind the OS generation now; wait_for_live owns listener/RPC convergence.
	capture_service_generation system || return 1
	suspension_scope=system
	suspension_release="$candidate_release"
	suspension_pid="$service_pid"
	suspension_generation="$service_generation"
	suspension_instance=""
}

prepare_rollback_suspension() {
	# Bind at successful start or explicit recovery, not after failed verification:
	# a replacement must never gain rollback authority by answering a fresh lease RPC.
	[[ "$suspension_scope" == system && "$suspension_release" == "$candidate_release" ]] || return 1
	recheck_generation system "$suspension_pid" "$suspension_generation" || return 1
	if ((interrupt_after_drain)); then
		# system.info is fenced after preparation; bind the already-owned successor before its lease.
		suspension_instance="$(read_process_instance "$candidate_release" "$suspension_pid")" || return 1
		recheck_generation system "$suspension_pid" "$suspension_generation" || return 1
	fi
	if prepare_suspension "$candidate_release" && check_suspension; then
		return 0
	fi
	# A running but unfenceable successor stays in place, even when verification is unhealthy.
	fail 'rollback successor could not be safely fenced; refusing restart and retaining evidence'
}

native_update_permit() {
	stopped_system_owners || return 1
	session_witness_record="$(proof journal-read "$journal_file")" || return 1
	proof journal-native-permit "$journal_file" "$session_witness_record" "$1" >/dev/null
}

finish_native_update() {
	[[ -n "$native_update_root" ]] || return 0
	run_gateway "$candidate_release" update finish --backup-root "$native_update_root" ||
		printf 'WARNING native update backup cleanup remains pending: %s\n' "$native_update_root" >&2
}

native_update() {
	local action="$1" migration_budget
	# A 37 GiB crossing can take 39 minutes before its backup; keep slow disks in budget.
	migration_budget=$((build_budget < 7200 ? 7200 : build_budget))
	session_witness_record="$(proof journal-read "$journal_file")" || return 1
	stopped_system_owners || return 1
	if [[ "$action" == restore ]]; then
		proof journal-native-permit "$journal_file" "$session_witness_record" revoke >/dev/null || return 1
	fi
	bounded "$migration_budget" "$runuser_bin" -u openclaw -- env \
		HOME="$runtime_home" OPENCLAW_HOME="$runtime_home" \
		OPENCLAW_CONFIG_PATH="$config_file" OPENCLAW_STATE_DIR="$(dirname "$(dirname "$state_database")")" \
		OPENCLAW_SUPERVISOR_MODE=external OPENCLAW_SERVICE_REPAIR_POLICY=external \
		OPENCLAW_UPDATE_IN_PROGRESS=1 OPENCLAW_DOCTOR_SQLITE_NOCOW_REPAIR="$offline_doctor_repair" \
		OPENCLAW_SYSTEMD_UNIT=openclaw-gateway.service \
		"$node_bin" "$candidate_release/dist/index.js" update "$action" --backup-root "$native_update_root" || return 1
	stopped_system_owners || return 1
	session_witness_record="$(proof journal-native-state "$journal_file" "$session_witness_record" "$config_file" "$state_database")" || return 1
	protected_identity="$(proof journal-field "$journal_file" protectedPaths)" || return 1
	load_journal_witness
}

rollback_activation() {
	local exit_code="$1" predecessor_sha candidate_sha topology expected_pid expected_generation rollback_owner
	local failure_code="$1" rollback_pid="" rollback_generation=""
	((failure_code != 0)) || failure_code=1
	((rollback_armed)) || return "$exit_code"
	rollback_armed=0
	load_journal_witness || return "$failure_code"
	verification_deadline=0
	predecessor_sha="$(proof journal-field "$journal_file" predecessor.sha)" || return "$failure_code"
	candidate_sha="$(proof journal-field "$journal_file" candidate.sha)" || return "$failure_code"
	topology="$(proof journal-field "$journal_file" topology)" || return "$failure_code"
	if [[ "$candidate_sha" != "$predecessor_sha" ]]; then
		record_failed "$candidate_sha" || return "$failure_code"
	fi
	if [[ -z "$native_update_root" || ! -f "$native_update_root.receipt.json" ]]; then
		if ! proof compatibility "$candidate_release" "$previous_release" "$state_database" >/dev/null; then
			proof journal-phase "$journal_file" ROLLBACK_FAILED "$session_witness_record" >/dev/null || true
			fail 'rollback compatibility cannot be proved; candidate remains selected and evidence is retained'
			return "$failure_code"
		fi
		# Changed system-deployment policy cannot converge by polling the previous runtime.
		if ! assert_protected_identity || { [[ "$topology" == system ]] && ! assert_policy; }; then
			proof journal-phase "$journal_file" ROLLBACK_FAILED "$session_witness_record" >/dev/null || true
			return "$failure_code"
		fi
	fi
	if [[ "$topology" == migration-user ]]; then
		if ! restore_migration_topology; then
			proof journal-phase "$journal_file" ROLLBACK_FAILED "$session_witness_record" >/dev/null || true
			fail 'migration rollback failed; automation stopped and evidence retained'
			return "$failure_code"
		fi
		if ! wait_for_live "$previous_release" "$previous_info" migration-user; then
			proof journal-phase "$journal_file" ROLLBACK_FAILED "$session_witness_record" >/dev/null || true
			fail 'migration rollback failed; automation stopped and evidence retained'
			return "$failure_code"
		fi
	else
		expected_pid="$(proof journal-field "$journal_file" process.pid)" || return "$failure_code"
		expected_generation="$(proof journal-field "$journal_file" process.generation)" || return "$failure_code"
		if ((predecessor_stopped == 0)); then
			rollback_owner=untouched
			recheck_generation system "$expected_pid" "$expected_generation" || {
				proof journal-phase "$journal_file" ROLLBACK_FAILED "$session_witness_record" >/dev/null || true
				fail 'predecessor changed before observed shutdown; refusing rollback restart'
				return 1
			}
		elif predecessor_is_stopped "$expected_pid"; then
			rollback_owner=stopped
		elif ((restart_failed)); then
			proof journal-phase "$journal_file" ROLLBACK_FAILED "$session_witness_record" >/dev/null || true
			fail 'failed-start ownership changed during rollback proof; refusing restart'
			return 1
		else
			rollback_owner=running
			if ! prepare_rollback_suspension; then
				proof journal-phase "$journal_file" ROLLBACK_FAILED "$session_witness_record" >/dev/null || true
				return 1
			fi
		fi
		if [[ -n "$native_update_root" && -f "$native_update_root.receipt.json" ]]; then
			if [[ "$rollback_owner" == running ]]; then
				assert_policy && check_suspension && arm_suspension_handoff || return "$failure_code"
				system_systemctl stop openclaw-gateway.service || return "$failure_code"
				held_suspension=""
				predecessor_is_stopped "$expected_pid" || return "$failure_code"
				rollback_owner=stopped
			fi
			native_update restore || {
				proof journal-phase "$journal_file" ROLLBACK_FAILED "$session_witness_record" >/dev/null || true
				fail 'native update restoration failed; Gateway remains stopped with recovery evidence retained'
				return "$failure_code"
			}
			proof compatibility "$candidate_release" "$previous_release" "$state_database" >/dev/null &&
				assert_protected_identity && assert_policy || return "$failure_code"
		fi
		restore_pointer_topology || return "$failure_code"
		if [[ -n "$native_update_root" && "$rollback_owner" != untouched ]]; then
			native_update_permit publish || return "$failure_code"
		fi
		if [[ "$rollback_owner" == untouched ]]; then
			resume_suspension || return 1
			if [[ "$mode" != runtime ]]; then
				rollback_pid="$expected_pid"; rollback_generation="$expected_generation"
			fi
		else
			if [[ "$rollback_owner" == stopped ]]; then
				predecessor_is_stopped "$expected_pid" || {
					proof journal-phase "$journal_file" ROLLBACK_FAILED "$session_witness_record" >/dev/null || true
					fail 'failed-start ownership changed before rollback restart'
					return 1
				}
			elif ! assert_protected_identity || ! assert_policy || ! check_suspension || ! arm_suspension_handoff; then
				proof journal-phase "$journal_file" ROLLBACK_FAILED "$session_witness_record" >/dev/null || true
				fail 'successor suspension or generation changed before rollback restart'
				return 1
			fi
			system_systemctl restart openclaw-gateway.service || {
				proof journal-phase "$journal_file" ROLLBACK_FAILED "$session_witness_record" >/dev/null || true
				return "$failure_code"
			}
			held_suspension=""
			if [[ "$mode" != runtime ]]; then
				capture_service_generation system || return "$failure_code"
				rollback_pid="$service_pid"; rollback_generation="$service_generation"
				[[ "$(realpath "$proc_root/$rollback_pid/cwd")" == "$previous_release" ]] || return "$failure_code"
			fi
		fi
		if ! wait_for_live "$previous_release" "$previous_info" system "$rollback_pid" "$rollback_generation"; then
			proof journal-phase "$journal_file" ROLLBACK_FAILED "$session_witness_record" >/dev/null || true
			fail 'activation rollback failed; automation stopped and evidence retained'
			return "$failure_code"
		fi
	fi
	# on_exit calls rollback in an OR-list, so retirement cannot rely on errexit.
	proof unlink-exact "$journal_file" "$root_uid" "$session_witness_record" || return 1
	printf 'ROLLED_BACK sha=%s\n' "$predecessor_sha" >&2
	return "$exit_code"
}

restore_migration_enablement() {
	local expected_system_enabled="$1" expected_user_enabled="$2"
	if [[ "$(system_unit_state)" != "$expected_system_enabled" ]]; then
		system_systemctl disable openclaw-gateway.service || return 1
	fi
	if [[ "$(user_unit_state)" != "$expected_user_enabled" ]]; then
		if [[ "$expected_user_enabled" == enabled ]]; then
			user_unit_file enable || return 1
		else
			user_unit_file disable || return 1
		fi
	fi
}

restore_migration_topology() {
	local expected_system_enabled expected_user_enabled expected_system_active expected_user_active
	local observed_system observed_user
	expected_system_enabled="$(proof journal-field "$journal_file" services.system.unitFileState)" || return 1
	expected_user_enabled="$(proof journal-field "$journal_file" services.user.unitFileState)" || return 1
	expected_system_active="$(proof journal-field "$journal_file" services.system.activeState)" || return 1
	expected_user_active="$(proof journal-field "$journal_file" services.user.activeState)" || return 1
	[[ "$expected_system_enabled" == disabled &&
		("$expected_user_enabled" == enabled || "$expected_user_enabled" == disabled) &&
		"$expected_system_active" == inactive && "$expected_user_active" == active ]] ||
		fail 'migration journal cannot restore an unsupported predecessor topology'
	observed_system="$(system_state)"
	observed_user="$(user_state)"
	[[ ! ("$observed_system" == active && "$observed_user" == active) ]] ||
		fail 'migration rollback found simultaneous Gateway owners'
	if [[ "$observed_system" == active ]]; then
		[[ "$observed_user" != active ]] || return 1
		prepare_rollback_suspension || return 1
		assert_protected_identity || return 1
		assert_policy || return 1
		check_suspension || return 1
		arm_suspension_handoff || return 1
		system_systemctl stop openclaw-gateway.service || return 1
		predecessor_stopped=1
		held_suspension=""
	fi
	restore_pointer_topology || return 1
	restore_migration_enablement "$expected_system_enabled" "$expected_user_enabled" || return 1
	if [[ "$(user_state)" != active ]]; then
		assert_exclusive_owner quiescent || return 1
		predecessor_stopped=1
		held_suspension=""
		user_systemctl start openclaw-gateway.service || return 1
	else
		resume_suspension || return 1
	fi
	assert_exclusive_owner migration-user
}

restore_pointer_topology() {
	local current_present previous_present previous_sha
	current_present="$(proof journal-field "$journal_file" pointerTopology.current.present)" || return 1
	previous_present="$(proof journal-field "$journal_file" pointerTopology.previous.present)" || return 1
	if [[ "$current_present" == true ]]; then
		if [[ "$(read_pointer "$current_link")" != "$previous_release" ]]; then
			atomic_pointer "$previous_release" "$current_link" || return 1
		fi
	elif [[ -L "$current_link" ]]; then
		proof unlink-pointer "$current_link" "$serving_root" "$root_uid" || return 1
	fi
	if [[ "$previous_present" == true ]]; then
		previous_sha="$(proof journal-field "$journal_file" pointerTopology.previous.sha)" || return 1
		if [[ ! -L "$previous_link" || "$(read_pointer "$previous_link")" != "$releases_root/$previous_sha" ]]; then
			atomic_pointer "$releases_root/$previous_sha" "$previous_link" || return 1
		fi
	elif [[ -L "$previous_link" ]]; then
		proof unlink-pointer "$previous_link" "$serving_root" "$root_uid" || return 1
	fi
	if [[ "$(proof gateway-runtime changing "$serving_root")" == yes ]]; then
		proof gateway-runtime restore "$serving_root" >/dev/null || return 1
		system_systemctl daemon-reload || return 1
		load_gateway_runtime || return 1
	fi
}

on_exit() {
	local exit_code=$? cleanup_exit
	if [[ "$mode" == prepare-release || "$mode" == continue-frozen ]]; then exit "$exit_code"; fi
	# Admission failure or a lost journal receipt carries no profile cleanup authority.
	if [[ -n "$frozen_target_sha" && -z "$held_suspension" ]] && ((rollback_armed == 0)); then exit "$exit_code"; fi
	if [[ "$mode" == handoff-source ]]; then
		trap - EXIT
		if [[ -n "$held_suspension" ]]; then resume_suspension || exit_code=1; fi
		exit "$exit_code"
	fi
	if [[ "$mode" == handoff-* ]]; then
		trap - EXIT
		if ((exit_code != 0)); then printf 'HANDOFF_TARGET_RETAINED forward-recovery=required journal-and-source-evidence=retained\n' >&2; fi
		exit "$exit_code"
	fi
	if ((start_stopped_current)); then
		trap - EXIT
		stopped_current_finish "$exit_code"
		exit $?
	fi
	trap - EXIT ERR
	# The exec-based capture mode owns its own permit and has no deployment cleanup authority.
	[[ "$mode" != steady-profile ]] || exit "$exit_code"
	profile_finish false || { cleanup_exit=$?; ((exit_code != 0)) || exit_code="$cleanup_exit"; }
	if ((exit_code != 0)); then
		profile_close deployment-exit || { cleanup_exit=$?; ((exit_code != 0)) || exit_code="$cleanup_exit"; }
	fi
	if ((profile_owner_unsafe)); then
		((exit_code != 0)) || exit_code="$profile_owner_unsafe"
		exit "$exit_code"
	fi
	if [[ "$mode" == operator-restart ]] && ((operator_receipt_open && !operator_restart_verified)); then
		((exit_code != 0)) || exit_code=1
		operator_receipt failed "$exit_code" || true
	fi
	if ((rollback_armed)); then
		rollback_activation "$exit_code" || exit_code=$?
	fi
	# A rollback lease can belong to the successor even though the original predecessor stopped.
	if [[ -n "$held_suspension" ]]; then
		resume_suspension || exit_code=1
	fi
	if ((exit_code == 0 && profile_keep_arm)); then
		profile_maintenance "$profile_operation" || { cleanup_exit=$?; ((exit_code != 0)) || exit_code="$cleanup_exit"; }
	else
		profile_maintenance || { cleanup_exit=$?; ((exit_code != 0)) || exit_code="$cleanup_exit"; }
	fi
	if ((exit_code == 0 && profile_keep_arm)); then
		# This is the final closeout action. Earlier journal retirement is not capture eligibility.
		profile_command terminalize-arm "{\"operation\":\"$profile_operation\"}" ||
			{ profile_close terminalization || exit_code=$?; }
	elif ((arm_steady_profile)); then
		profile_close exit-maintenance || { cleanup_exit=$?; ((exit_code != 0)) || exit_code="$cleanup_exit"; }
	fi
	exit "$exit_code"
}

recheck_recovery_generation() {
	if [[ "$phase" != MIGRATION_SWITCHING ]]; then
		recheck_generation "$suspension_scope" "$suspension_pid" "$suspension_generation"
		return $?
	fi
	# Before stop, migration may have changed enablement, but never active ownership.
	local observed_system_enabled observed_user_enabled
	[[ "$(system_state)" == inactive && "$(user_state)" == active ]] || return 1
	observed_system_enabled="$(system_unit_state)"
	observed_user_enabled="$(user_unit_state)"
	[[ ("$observed_system_enabled" == enabled || "$observed_system_enabled" == disabled) &&
		("$observed_user_enabled" == disabled || "$observed_user_enabled" == "$expected_user_enabled") ]] || return 1
	read_service user || return 1
	[[ "$service_pid" == "$suspension_pid" &&
		"$(process_generation "$service_pid")" == "$suspension_generation" ]] || {
		fail 'migration predecessor generation changed during recovery'
		return 1
	}
	assert_single_listener "$service_pid"
}

check_recovery_predecessor() {
	proof recovery-check "$journal_file" "$1" "$serving_root" "$2" >/dev/null || return 1
	if [[ -n "${recovery_pid:-}" ]]; then
		[[ ! -e "$proc_root/$suspension_pid" && ! -L "$proc_root/$suspension_pid" ]] || return 1
		recovery_jobs_settled || return 1
		recheck_generation system "$recovery_pid" "$recovery_generation" || return 1
		[[ "$(realpath "$proc_root/$recovery_pid/cwd")" == "$previous_release" ]] || return 1
		proof gateway-runtime process "$serving_root" "$gateway_runtime_snapshot" "$proc_root" "$recovery_pid" >/dev/null || return 1
		[[ "$(read_process_instance "$previous_release" "$recovery_pid")" == "$recovery_instance" ]] || return 1
		recheck_generation system "$recovery_pid" "$recovery_generation"
		return $?
	fi
	recheck_recovery_generation
}

observe_recovery_suspension() {
	local evidence="$1" params="$2" response
	if [[ -n "${recovery_pid:-}" ]]; then
		check_recovery_predecessor "$evidence" "$recovery_pointer_view" || return 1
		# The historical ID satisfies the status contract; only RUNNING is accepted for this replacement.
		response="$(run_gateway "$previous_release" gateway call gateway.suspend.status --params "$params" --json --timeout 30000)" || return 1
		check_recovery_predecessor "$evidence" "$recovery_pointer_view" || return 1
		proof_json suspension-running "$response" >/dev/null || return 1
		printf 'RESTARTED\n'
		return 0
	fi
	recheck_recovery_generation || return 1
	response="$(run_gateway "$previous_release" gateway call gateway.suspend.status \
		--params "$params" --json --timeout 30000)" || return 1
	recheck_recovery_generation || return 1
	proof_json suspension-recovery-status "$response" "$evidence"
}

recover_predecessor() {
	local evidence lease params outcome response expected_system_enabled expected_user_enabled witness_receipt=""
	local recovery_pid="${2:-}" recovery_generation="${3:-}" recovery_instance="" recovery_pointer_view=observed
	if [[ -n "$reconcile_request" ]]; then
		recovery_pointer_view=restored
		evidence="$(proof recovery-snapshot "$journal_file" "$serving_root" "$1" restored "$reconcile_request")" || return 1
		reconcile_evidence="$evidence"
		protected_identity="$(proof fingerprint "$config_file" "$state_database")" || return 1
		assert_protected_identity || return 1
	else
		evidence="$(proof recovery-snapshot "$journal_file" "$serving_root" "$1")" || return 1
	fi
	if [[ -n "$recovery_pid" ]]; then
		[[ "$mode" == recover && "$topology" == system && "$session_proof" == original-pre-cutover &&
			"$(proof gateway-runtime changing "$serving_root")" == no &&
			! -e "$proc_root/$suspension_pid" && ! -L "$proc_root/$suspension_pid" ]] || return 1
		recheck_generation system "$recovery_pid" "$recovery_generation" || return 1
		[[ "$(realpath "$proc_root/$recovery_pid/cwd")" == "$previous_release" ]] || return 1
		recovery_instance="$(read_process_instance "$previous_release" "$recovery_pid")" || return 1
	fi
	if [[ "$phase" == MIGRATION_SWITCHING ]]; then
		expected_system_enabled="$(proof journal-field "$journal_file" services.system.unitFileState)" || return 1
		expected_user_enabled="$(proof journal-field "$journal_file" services.user.unitFileState)" || return 1
	fi
	lease="$(proof journal-field "$journal_file" suspension.id)" || return 1
	params="$(proof suspension-lease-params "$lease")" || return 1
	check_recovery_predecessor "$evidence" "$recovery_pointer_view" || return 1
	outcome="$(observe_recovery_suspension "$evidence" "$params")" || return 1
	# The running release is still the predecessor; no candidate migration is admitted here.
	proof compatibility "$previous_release" "$candidate_release" "$state_database" >/dev/null || return 1
	assert_policy || return 1
	check_recovery_predecessor "$evidence" "$recovery_pointer_view" || return 1
	outcome="$(observe_recovery_suspension "$evidence" "$params")" || return 1
	check_recovery_predecessor "$evidence" "$recovery_pointer_view" || return 1
	if [[ -z "$reconcile_request" ]]; then restore_pointer_topology || return 1; fi
	recovery_pointer_view=restored
	check_recovery_predecessor "$evidence" restored || return 1
	outcome="$(observe_recovery_suspension "$evidence" "$params")" || return 1
	check_recovery_predecessor "$evidence" restored || return 1
	if [[ "$phase" == MIGRATION_SWITCHING ]]; then
		restore_migration_enablement "$expected_system_enabled" "$expected_user_enabled" || return 1
		check_recovery_predecessor "$evidence" restored || return 1
		outcome="$(observe_recovery_suspension "$evidence" "$params")" || return 1
		check_recovery_predecessor "$evidence" restored || return 1
	fi
	if [[ "$outcome" == ACTIVE ]]; then
		held_suspension="$lease"
		resume_suspension || return 1
		outcome=resumed
	elif [[ "$outcome" != RESTARTED ]]; then
		# Expiry already reopened admission. This is observation, not a resume.
		outcome=already-resumed
	fi
	if [[ -n "$recovery_pid" ]]; then
		wait_for_live "$previous_release" "$previous_info" system "$recovery_pid" "$recovery_generation" || return 1
	else
		response="$(run_gateway "$previous_release" gateway call system.info --params '{}' --json --timeout 30000)" || return 1
		proof recovery-process "$response" "$evidence" >/dev/null || return 1
	fi
	assert_protected_identity || return 1
	assert_policy || return 1
	if [[ "$session_proof" == original-pre-cutover ]]; then
		printf '%s' "$session_witness" | proof session-preservation-verify \
			"$previous_release" "$candidate_release" "$state_database" >/dev/null || return 1
		witness_receipt=' session-proof=original-pre-cutover'
	else
		proof compatibility "$previous_release" "$candidate_release" "$state_database" >/dev/null || return 1
	fi
	check_recovery_predecessor "$evidence" restored || return 1
	if [[ -n "$recovery_pid" ]]; then
		observe_recovery_suspension "$evidence" "$params" >/dev/null || return 1
		check_recovery_predecessor "$evidence" restored || return 1
	else
		response="$(run_gateway "$previous_release" gateway call gateway.suspend.status \
			--params "$params" --json --timeout 30000)" || return 1
		proof_json suspension-running "$response" >/dev/null || return 1
		recheck_generation "$suspension_scope" "$suspension_pid" "$suspension_generation" || return 1
	fi
	if [[ "$phase" == MIGRATION_SWITCHING ]]; then
		[[ "$(system_unit_state)" == "$expected_system_enabled" &&
		"$(user_unit_state)" == "$expected_user_enabled" ]] || return 1
	fi
	proof recovery-finish "$journal_file" "$evidence" "$serving_root" restored >/dev/null || return 1
	if [[ -n "$reconcile_evidence" ]]; then
		printf 'RECOVERY_CONFIG_RECONCILED config_sha256=%s journal_sha256=%s original-journal=retired-without-rewrite\n' "$reconcile_config_hash" "$reconcile_journal_hash"
	fi
	printf 'RECOVERED phase=%s action=restore-pointer-no-restart suspension=%s%s\n' "$phase" "$outcome" "$witness_receipt"
}

worker_config_stopped() {
	migration_native_guard || return 1
	if [[ "${1:-}" != accepted ]]; then
		proof migration-permit-absent "$serving_root" >/dev/null || return 1
	fi
	stopped_system_owners || return 1
	[[ ! -e "$proc_root/$operator_expected_pid" || "$(process_generation "$operator_expected_pid")" != "$operator_expected_generation" ]] || return 1
	proof worker-config check "$serving_root" >/dev/null
}

finish_worker_config() {
	local recovery="$1" outcome old_pid old_generation
	protected_identity="$(proof journal-field "$journal_file" protectedPaths)" || return 1
	assert_protected_identity || return 1
	assert_policy || return 1
	proof worker-config check "$serving_root" >/dev/null || return 1
	proof compatibility "$previous_release" "$previous_release" "$state_database" >/dev/null || return 1
	if [[ "$(system_state)" != active ]]; then
		# Recovery may observe a published permit before start was requested or completed.
		worker_config_stopped accepted || return 1
		proof worker-config permit "$serving_root" >/dev/null || return 1
		migration_native_guard || return 1
		proof worker-config check "$serving_root" >/dev/null || return 1
		system_systemctl start openclaw-gateway.service || return 1
	else
		proof migration-permit-present "$serving_root" >/dev/null || return 1
	fi
	capture_service_generation system || return 1
	old_pid="$service_pid"; old_generation="$service_generation"
	wait_for_live "$previous_release" "$previous_info" system || return 1
	recheck_generation system "$old_pid" "$old_generation" || return 1
	proof worker-config check "$serving_root" >/dev/null || return 1
	outcome="$(proof worker-config finish "$serving_root")" || return 1
	printf '%s sha=%s action=repair-worker-bootstrap result=%s session-proof=original-pre-maintenance APPROVAL_DELTA=0\n' \
		"$recovery" "$(basename "$previous_release")" "$outcome"
}

recover_worker_config() {
	local phase job token restore_phase restore_value expected_value phase_log live_cancellation cancel_live=0
	local cancellation_params cancellation_response cancellation_result
	phase="$(proof worker-config pending "$serving_root")" || return 1
	[[ "$phase" != none ]] || return 0
	previous_release="$(read_pointer "$current_link")" || return 1
	[[ "$(basename "$previous_release")" == "$(proof journal-field "$journal_file" predecessor.sha)" ]] || return 1
	previous_info="$(proof validate-release "$previous_release" "$(basename "$previous_release")" "$root_uid" sealed)" || return 1
	operator_expected_pid="$(proof journal-field "$journal_file" process.pid)" || return 1
	operator_expected_generation="$(proof journal-field "$journal_file" process.generation)" || return 1
	session_witness="$(proof worker-config witness "$serving_root")" || return 1
	[[ -n "$session_witness" ]] || return 1
	migration_guard="$(proof migration-guard-descriptor "$serving_root")" || return 1
	migration_native_guard || return 1
	assert_policy || return 1
	proof compatibility "$previous_release" "$previous_release" "$state_database" >/dev/null || return 1
	live_cancellation="$(proof worker-config live-cancellation "$serving_root")" || return 1
	if [[ ("$phase" == CONFIG_PREPARED || "$live_cancellation" == yes) && "$(system_state)" == active ]]; then
		capture_generation system || return 1
		if [[ "$service_pid" == "$operator_expected_pid" && "$service_generation" == "$operator_expected_generation" ]]; then
			cancel_live=1
		elif [[ "$phase" == CONFIG_PREPARED ]]; then
			fail 'worker config predecessor changed before cancellation'
			return 1
		fi
	fi
	if ((cancel_live)); then
		# Cancellation intent survives permit/resume crash windows without lending the old lease to a new process.
		recheck_generation system "$operator_expected_pid" "$operator_expected_generation" || return 1
		[[ "$(system_systemctl show openclaw-gateway.service --property=ControlPID --value)" == 0 ]] || return 1
		job="$(system_systemctl show openclaw-gateway.service --property=Job --value)" || return 1
		[[ -z "$job" || "$job" == 0 ]] || return 1
		proof worker-config cancel "$serving_root" >/dev/null || return 1
		proof worker-config permit "$serving_root" >/dev/null || return 1
		suspension_scope=system; suspension_release="$previous_release"
		suspension_pid="$operator_expected_pid"; suspension_generation="$operator_expected_generation"
		cancellation_params="$(proof suspension-lease-params "$(proof journal-field "$journal_file" suspension.id)")" || return 1
		# This journal, not the generic exit trap, owns an interrupted cancellation.
		held_suspension=""
		recheck_generation system "$operator_expected_pid" "$operator_expected_generation" || return 1
		cancellation_response="$(run_gateway "$previous_release" gateway call gateway.suspend.resume --params "$cancellation_params" --json --timeout 30000)" || return 1
		recheck_generation system "$operator_expected_pid" "$operator_expected_generation" || return 1
		cancellation_result="$(printf '%s' "$cancellation_response" | proof worker-config cancellation-result "$serving_root")" || return 1
		printf 'WORKER_CONFIG_CANCELLATION suspension=%s\n' "$cancellation_result" || return 1
	elif [[ "$phase" != CONFIG_VERIFIED && "$phase" != CONFIG_RESTORED ]]; then
		worker_config_stopped || return 1
		# Unknown config drift refuses this operation; native .bak files and SQLite are never restored.
		restore_phase="$(proof worker-config restore "$serving_root")" || return 1
		if [[ "$restore_phase" == CONFIG_RESTORING ]]; then
			token="$(proof journal-field "$journal_file" request)" || return 1
			restore_value="$(proof worker-config expected-value "$serving_root" "$token")" || return 1
			expected_value="$(proof worker-config replacement-value "$serving_root" "$token")" || return 1
			phase_log="$(proof worker-config log-path "$serving_root" "$token")" || return 1
			worker_config_stopped || return 1
			(umask 077; set -o noclobber; run_gateway "$previous_release" config set cloudWorkers.profiles.aws.settings.setup "$restore_value" --strict-json --expect-current-json "$expected_value" \
				>"$phase_log.restore.$$.stdout" 2>"$phase_log.restore.$$.stderr") || { fail 'worker config inverse write failed; native evidence and start fence retained'; return 1; }
			worker_config_stopped || return 1
			[[ "$(proof worker-config restore "$serving_root")" == CONFIG_RESTORED ]] || return 1
		fi
	fi
	finish_worker_config RECOVERED
}

repair_worker_bootstrap() {
	local value_json expected_value payload runtime_gid phase_log
	[[ ! -e "$journal_file" && ! -L "$journal_file" ]] || { fail 'worker config repair refuses an existing activation journal'; return 1; }
	previous_release="$(read_pointer "$current_link")" || return 1
	[[ "$previous_release" == "$releases_root/$operator_expected_sha" ]] || { fail 'worker config expected serving SHA changed'; return 1; }
	previous_info="$(proof validate-release "$previous_release" "$operator_expected_sha" "$root_uid" sealed)" || return 1
	recheck_generation system "$operator_expected_pid" "$operator_expected_generation" || return 1
	assert_protected_identity || return 1
	assert_policy || return 1
	assert_scheduler_owner "$previous_release" || return 1
	proof compatibility "$previous_release" "$previous_release" "$state_database" >/dev/null || return 1
	migration_guard="$(proof migration-guard-descriptor "$serving_root")" || return 1
	migration_native_guard || return 1
	proof migration-permit-present "$serving_root" >/dev/null || return 1
	runtime_gid="$(id -g openclaw)" || return 1
	worker_config_token="$(proof worker-config prepare "$serving_root" "$config_file" "$state_database" \
		"$operator_expected_sha" "$worker_expected_config_hash" "$worker_expected_setup_hash" "$runtime_uid" "$runtime_gid")" || return 1
	value_json="$(proof worker-config replacement-value "$serving_root" "$worker_config_token")" || return 1
	expected_value="$(proof worker-config expected-value "$serving_root" "$worker_config_token")" || return 1
	phase_log="$(proof worker-config log-path "$serving_root" "$worker_config_token")" || return 1
	printf 'WORKER_CONFIG_EVIDENCE=%s\n' "${phase_log%/native-config}" || return 1
	# Only the reviewed credential-free setup leaf enters argv; whole config stays private.
	# The public CLI forbids expectations during dry-run and in batch mode.
	(umask 077; set -o noclobber; run_gateway "$previous_release" config set cloudWorkers.profiles.aws.settings.setup "$value_json" --strict-json --dry-run \
		>"$phase_log.preflight.stdout" 2>"$phase_log.preflight.stderr") || { fail 'worker config dry-run failed; inspect private native-config preflight logs'; return 1; }
	proof worker-config check-prepared "$serving_root" "$worker_config_token" >/dev/null || return 1
	recheck_generation system "$operator_expected_pid" "$operator_expected_generation" || return 1
	suspension_scope=system; suspension_pid="$operator_expected_pid"; suspension_generation="$operator_expected_generation"
	suspension_instance="$(read_process_instance "$previous_release" "$operator_expected_pid")" || return 1
	session_witness="$(proof session-preservation-witness "$previous_release" "$previous_release" "$state_database")" || return 1
	prepare_suspension "$previous_release" || return $?
	check_suspension || return 1
	payload="$(printf '%s' "$session_witness" | "$node_bin" -e '
const fs=require("node:fs"),[pid,generation,instance,id,expiry,state]=process.argv.slice(1);
process.stdout.write(JSON.stringify({process:{pid:Number(pid),generation,instance},suspension:{id,expiresAtMs:Number(expiry),status:state.toLowerCase(),terminalPolicy:"preserve"},witness:JSON.parse(fs.readFileSync(0,"utf8"))}));
' "$operator_expected_pid" "$operator_expected_generation" "$suspension_instance" "$held_suspension" "$suspension_expiry" "$suspension_state")" || return 1
	printf '%s' "$payload" | proof worker-config begin "$serving_root" "$worker_config_token" >/dev/null || return 1
	migration_stop_contract || return 1
	assert_protected_identity || return 1
	assert_policy || return 1
	check_suspension || return 1
	proof worker-config revoke "$serving_root" >/dev/null || return 1
	check_suspension || return 1
	arm_suspension_handoff || return 1
	bounded 180 "$systemctl_bin" stop openclaw-gateway.service || return 1
	held_suspension=""; predecessor_stopped=1
	worker_config_stopped || return 1
	proof worker-config writing "$serving_root" >/dev/null || return 1
	# Full-raw hash binds owner admission, not an external CLI CAS. The CLI owns its snapshot CAS.
	# Its leaf expectation prevents stale-target overwrite; post-write proof refuses unrelated drift.
	proof worker-config check-prepared "$serving_root" "$worker_config_token" >/dev/null || return 1
	(umask 077; set -o noclobber; run_gateway "$previous_release" config set cloudWorkers.profiles.aws.settings.setup "$value_json" --strict-json --expect-current-json "$expected_value" \
		>"$phase_log.write.stdout" 2>"$phase_log.write.stderr") || { fail 'worker config native write failed; private logs and original backup retained'; return 1; }
	worker_config_stopped || return 1
	proof worker-config accept "$serving_root" >/dev/null || return 1
	finish_worker_config SUCCESS
}

check_stopped_recovery() {
	stopped_system_owners || return 1
	migration_guard="$(proof migration-guard-descriptor "$serving_root")" || return 1
	migration_native_guard || return 1
	proof migration-permit-present "$serving_root" >/dev/null || return 1
	assert_protected_identity && assert_policy || return 1
	printf '%s' "$session_witness" | proof session-preservation-verify "$previous_release" "$previous_release" "$state_database" >/dev/null || return 1
	proof recovery-start-writers "$journal_file" "$reconcile_evidence" "$serving_root" "$proc_root" >/dev/null || return 1
	stopped_system_owners
}

recovery_jobs_settled() {
	local controller job
	for controller in system_systemctl user_systemctl; do
		[[ "$("$controller" show openclaw-gateway.service --property=ControlPID --value)" == 0 ]] || return 1
		job="$("$controller" show openclaw-gateway.service --property=Job --value)" || return 1
		[[ -z "$job" || "$job" == 0 ]] || { fail 'recovery Gateway control jobs have not settled'; return 1; }
	done
}

recover_journal() {
	local phase predecessor_sha candidate_sha current_sha topology expected_pid expected_generation
	local observed_system observed_user recovery_record evidence recovery_position recovery_release recovery_info recovery_label
	local migration_phase worker_phase missing stopped_snapshot started_restored=0 need_start=0
	local recovery_owned_pid="" recovery_owned_generation=""
	local start_state start_result=0 runtime_reload
	if [[ -n "$reconcile_config_hash" ]]; then
		[[ -e "$journal_file" ]] || return 1
		phase="$(proof journal-field "$journal_file" phase)" || return 1
		[[ "$phase" == ROLLBACK_FAILED || (("$phase" == C_CURRENT_SELECTED || "$phase" == D_VERIFYING) && "$reconcile_stopped" == 0) ]] || {
			fail 'config reconciliation requires a retained failed rollback journal or live prepared C journal'; return 1;
		}
		[[ "$phase" != C_CURRENT_SELECTED || "$(proof gateway-runtime changing "$serving_root")" == no ]] || return 1
	fi
	worker_phase="$(proof worker-config pending "$serving_root")" || return 1
	if [[ "$worker_phase" != none ]]; then
		[[ -z "$reconcile_config_hash" ]] || { fail 'config reconciliation refuses a worker config journal'; return 1; }
		recover_worker_config || return $?
		worker_config_recovered=1
		return 0
	fi
	migration_check_existing_guard || return 1
	[[ -e "$journal_file" ]] || return 0
	recovery_record="$(proof journal-read "$journal_file")" || {
		fail 'activation journal is ambiguous; pointer and service state were not changed'
		return 1
	}
	session_witness_record="$recovery_record"
	load_journal_witness || return 1
	phase="$(proof journal-field "$journal_file" phase)"
	predecessor_sha="$(proof journal-field "$journal_file" predecessor.sha)"
	candidate_sha="$(proof journal-field "$journal_file" candidate.sha)"
	topology="$(proof journal-field "$journal_file" topology)"
	expected_pid="$(proof journal-field "$journal_file" process.pid)"
	expected_generation="$(proof journal-field "$journal_file" process.generation)"
	suspension_scope="$topology"
	suspension_pid="$expected_pid"
	suspension_generation="$expected_generation"
	previous_release="$releases_root/$predecessor_sha"
	suspension_release="$previous_release"
	candidate_release="$releases_root/$candidate_sha"
	previous_info="$(proof validate-release "$previous_release" "$predecessor_sha" "$root_uid" sealed)"
	candidate_info="$(proof validate-release "$candidate_release" "$candidate_sha" "$root_uid" sealed)"
	protected_identity="$(proof journal-field "$journal_file" protectedPaths)"
	native_update_root="$(proof journal-field "$journal_file" nativeUpdate.backupRoot 2>/dev/null || true)"
	[[ "$native_update_root" != null ]] || native_update_root=""
	policy_identity="$(proof policy "$config_file")"
	current_sha=absent
	if [[ -L "$current_link" ]]; then
		current_sha="$(basename "$(read_pointer "$current_link")")"
	fi
	if [[ -n "$native_update_root" ]]; then
		# A migrated candidate may already be serving new writes; verify before restoring its backup.
		if [[ "$mode" == recover && "$current_sha" == "$candidate_sha" && "$candidate_sha" != "$predecessor_sha" ]] &&
			bind_successor_generation &&
			[[ "$suspension_pid:$suspension_generation" != "$expected_pid:$expected_generation" &&
				"$(realpath "$proc_root/$suspension_pid/cwd")" == "$candidate_release" ]] &&
			wait_for_live "$candidate_release" "$candidate_info" system "$suspension_pid" "$suspension_generation"; then
			recheck_generation system "$suspension_pid" "$suspension_generation" || return 1
			[[ "$(read_pointer "$current_link")" == "$candidate_release" ]] || return 1
			verified_markers record "$candidate_sha" || return 1
			proof unlink-exact "$journal_file" "$root_uid" "$session_witness_record" || return 1
			printf 'RECOVERED phase=%s action=verified-migrated-candidate-forward session-proof=%s\n' "$phase" "$session_proof"
			return 0
		fi
		predecessor_stopped=1
		if ! predecessor_is_stopped "$expected_pid"; then bind_successor_generation || return 1; fi
		rollback_armed=1
		rollback_activation 0
		return $?
	fi
	if [[ -z "$reconcile_config_hash" ]]; then assert_protected_identity; fi
	policy_identity="$(proof policy "$config_file")"
	if [[ "$(proof gateway-runtime changing "$serving_root")" == yes ]]; then
		# File publication and daemon-reload are separate commits. Reconcile only
		# a journal-authorized command after all earlier manager jobs have settled.
		recovery_jobs_settled || return 1
		runtime_reload="$(system_systemctl show openclaw-gateway.service --property=ExecStart --value | proof gateway-runtime reload-check "$serving_root")" || return 1
		case "$runtime_reload" in
		CACHED) ;;
		RELOAD)
			system_systemctl daemon-reload || return 1
			load_gateway_runtime || return 1
			;;
		*) fail 'runtime recovery returned an unknown manager state'; return 1 ;;
		esac
	fi
	if [[ -n "$reconcile_config_hash" ]]; then
		[[ "$topology" == system && "$candidate_sha" != "$predecessor_sha" && "$current_sha" == "$operator_expected_sha" &&
			(( ("$phase" == ROLLBACK_FAILED || "$phase" == C_CURRENT_SELECTED) && "$current_sha" == "$predecessor_sha") ||
			 ("$phase" == ROLLBACK_FAILED && "$current_sha" == "$candidate_sha" && "$reconcile_stopped" == 0 &&
			  "$(proof gateway-runtime changing "$serving_root")" == no)) ]] || {
			fail 'config reconciliation requires the expected restored predecessor or live selected failed candidate'; return 1;
		}
		if ((reconcile_stopped == 0)); then recheck_generation system "$operator_expected_pid" "$operator_expected_generation" || return 1; fi
		reconcile_request="$("$node_bin" -e 'process.stdout.write(JSON.stringify({config:{path:process.argv[1],sha256:process.argv[2]},journalSha256:process.argv[3],databasePath:process.argv[4],runtimeUid:Number(process.argv[5]),...(process.argv[6]==="1"?{startStopped:true}:{}),...(process.argv[7]==="selected"?{process:{pid:Number(process.argv[8]),generation:process.argv[9]}}:{})}))' \
			"$config_file" "$reconcile_config_hash" "$reconcile_journal_hash" "$state_database" "$runtime_uid" "$reconcile_stopped" \
			"$([[ "$current_sha" == "$candidate_sha" ]] && echo selected || echo restored)" "$operator_expected_pid" "$operator_expected_generation")" || return 1
	fi
	case "$phase" in
	A_PREPARED | B_PREVIOUS_PUBLISHED | C_CURRENT_SELECTED)
		if [[ -n "$reconcile_request" ]]; then
			[[ "$phase" == C_CURRENT_SELECTED && "$session_proof" == original-pre-cutover &&
				! -e "$proc_root/$expected_pid" && ! -L "$proc_root/$expected_pid" ]] || {
				fail 'prepared config reconciliation requires an independently bound replacement predecessor'; return 1;
			}
			recheck_generation system "$operator_expected_pid" "$operator_expected_generation" || return 1
			[[ "$(realpath "$proc_root/$operator_expected_pid/cwd")" == "$previous_release" ]] || return 1
			recover_predecessor "$recovery_record" "$operator_expected_pid" "$operator_expected_generation"
		elif recheck_generation "$topology" "$expected_pid" "$expected_generation"; then
			recover_predecessor "$recovery_record"
		elif [[ "$topology" == system && ("$mode" == recover || "$phase" == C_CURRENT_SELECTED) ]] && predecessor_is_stopped "$expected_pid"; then
			proof recovery-snapshot "$journal_file" "$serving_root" "$recovery_record" >/dev/null || return 1
			# A crash after stop has no successor lease. The exit owner retains
			# the original witness and performs the existing compatibility-checked rollback.
			rollback_armed=1
			predecessor_stopped=1
			if [[ "$mode" == recover ]]; then
				rollback_activation 0 || return $?
				printf 'RECOVERED phase=%s action=restored-stopped-predecessor session-proof=%s\n' "$phase" "$session_proof"
			else
				fail 'prepared activation recovery requires one compatibility-checked stopped rollback'
			fi
		elif [[ "$mode" == recover && "$topology" == system &&
			"$(proof gateway-runtime changing "$serving_root")" == no ]] && capture_generation system &&
			[[ "$(realpath "$proc_root/$service_pid/cwd")" == "$previous_release" ]]; then
			recover_predecessor "$recovery_record" "$service_pid" "$service_generation"
		elif [[ "$phase" == C_CURRENT_SELECTED && "$current_sha" == "$candidate_sha" ]] &&
			assert_exclusive_owner system && wait_for_live "$candidate_release" "$candidate_info" system; then
			verified_markers record "$candidate_sha" || return 1
			proof unlink-exact "$journal_file" "$root_uid" "$session_witness_record"
			printf 'RECOVERED phase=%s action=verified-successor-no-restart session-proof=%s\n' "$phase" "$session_proof"
		else
			fail 'selected-pointer recovery cannot prove either the predecessor or verified successor'
		fi
		;;
	MIGRATION_SWITCHING)
		[[ "$topology" == migration-user ]] || fail 'migration switching topology is invalid'
		observed_system="$(system_state)"
		observed_user="$(user_state)"
		[[ ! ("$observed_system" == active && "$observed_user" == active) ]] ||
			fail 'migration switching recovery found simultaneous Gateway owners'
		if [[ "$observed_system" == active && "$observed_user" != active ]]; then
			[[ "$current_sha" == "$candidate_sha" ]] || fail 'migration successor pointer changed'
			bind_successor_generation || fail 'migration successor durable ownership is ambiguous'
			if wait_for_live "$candidate_release" "$candidate_info" system; then
				proof unlink-exact "$journal_file" "$root_uid" "$session_witness_record"
				printf 'RECOVERED phase=%s action=verified-system-no-restart\n' "$phase"
			else
				rollback_armed=1
				predecessor_stopped=1
				fail 'migration successor verification failed and requires compatibility-checked rollback'
			fi
		elif [[ "$observed_system" == inactive && "$observed_user" == active ]]; then
			recover_predecessor "$recovery_record"
		elif [[ "$observed_system" != active && "$observed_user" != active ]]; then
			[[ "$current_sha" == "$candidate_sha" ]] || fail 'stopped migration pointer changed'
			predecessor_stopped=1
			proof compatibility "$candidate_release" "$previous_release" "$state_database" >/dev/null ||
				fail 'migration predecessor compatibility cannot be proved during switching recovery'
			restore_migration_topology || { fail 'migration switching recovery cannot restore exact predecessor ownership'; return 1; }
			wait_for_live "$previous_release" "$previous_info" migration-user || return 1
			proof unlink-exact "$journal_file" "$root_uid" "$session_witness_record"
			printf 'RECOVERED phase=%s action=restored-user-topology\n' "$phase"
		else
			fail 'migration switching service topology is ambiguous'
		fi
		;;
	D_VERIFYING)
		[[ "$current_sha" == "$candidate_sha" ]] || fail 'verification journal points at an unexpected release'
		# A stopped successor needs absence proof, not a mandatory live lease or RPC.
		if ! predecessor_is_stopped "$expected_pid"; then
			bind_successor_generation || return 1
			if wait_for_live "$candidate_release" "$candidate_info" system; then
				verified_markers record "$candidate_sha" || return 1
				proof unlink-exact "$journal_file" "$root_uid" "$session_witness_record"
				printf 'RECOVERED phase=%s action=verified-no-restart session-proof=%s\n' "$phase" "$session_proof"
				return 0
			fi
		fi
		rollback_armed=1
		predecessor_stopped=1
		fail 'verification recovery failed and requires one compatibility-checked rollback'
		;;
	ROLLBACK_FAILED)
		if [[ "$(proof gateway-runtime changing "$serving_root")" == yes ]]; then
			[[ "$mode" == recover ]] || { fail 'runtime rollback requires --recover'; return 1; }
			if recheck_generation system "$expected_pid" "$expected_generation"; then
				recover_predecessor "$recovery_record"
				return $?
			fi
			if ! predecessor_is_stopped "$expected_pid"; then
				bind_successor_generation || return 1
				if wait_for_live "$candidate_release" "$candidate_info" system; then
					proof unlink-exact "$journal_file" "$root_uid" "$session_witness_record" || return 1
					printf 'RECOVERED phase=ROLLBACK_FAILED action=verified-runtime-no-restart\n'
					return 0
				fi
			fi
			rollback_armed=1; predecessor_stopped=1
			fail 'runtime recovery requires the existing compatibility-checked rollback'
			return 1
		fi
		if [[ "$mode" != recover || "$topology" != system || "$candidate_sha" == "$predecessor_sha" ]]; then
			if [[ "$mode" != recover ]]; then missing='--recover is required';
			elif [[ "$topology" != system ]]; then missing='journal topology must be system';
			else missing='journal candidate and predecessor SHAs must be distinct'; fi
			fail "failed rollback requires explicit recovery of a distinct system release; evidence retained; missing precondition: $missing"
			return 1
		fi
		if [[ "$current_sha" == "$candidate_sha" ]]; then
			recovery_position=selected
			recovery_release="$candidate_release"
			recovery_info="$candidate_info"
			recovery_label="current-candidate"
		elif [[ "$current_sha" == "$predecessor_sha" ]]; then
			recovery_position=restored
			recovery_release="$previous_release"
			recovery_info="$previous_info"
			recovery_label=restored-predecessor
		else
			fail 'failed rollback current pointer matches neither journal release; evidence retained'
			return 1
		fi
		# A bound original is mandatory; only descriptor-less legacy journals use fresh-current proof.
		evidence="$(proof recovery-snapshot "$journal_file" "$serving_root" "$recovery_record" "$recovery_position" "$reconcile_request")" || return 1
		if [[ -n "$reconcile_request" ]]; then
			reconcile_evidence="$evidence"
			reconcile_position="$recovery_position"
			protected_identity="$(proof fingerprint "$config_file" "$state_database")" || return 1
			assert_protected_identity || return 1
		fi
		proof compatibility "$candidate_release" "$recovery_release" "$state_database" >/dev/null || return 1
		proof recovery-check "$journal_file" "$evidence" "$serving_root" "$recovery_position" >/dev/null || return 1
		if ((reconcile_stopped)); then
			start_state="$(proof recovery-start-status "$journal_file" "$evidence" "$serving_root")" || return 1
			if [[ "$start_state" == UNUSED ]]; then
				need_start=1
			else
				recovery_jobs_settled && capture_service_generation system || { fail 'stopped predecessor start is unverified; intent and journal retained; repeat start refused'; return 1; }
			fi
		elif [[ -n "$reconcile_evidence" ]]; then
			capture_generation system || return 1
		elif ! capture_generation system; then
			need_start=1
		fi
		if ((need_start)); then
			if [[ "$recovery_position" != restored || -n "$requested_sha" && "$requested_sha" != "$predecessor_sha" ]]; then
				refuse_verified_recovery "$candidate_sha"
				return 1
			fi
			stopped_snapshot="$(stopped_restored_snapshot "$expected_pid")" || return 1
			migration_guard="$(proof migration-guard-descriptor "$serving_root")" || return 1
			migration_native_guard || return 1
			proof migration-permit-present "$serving_root" >/dev/null || return 1
			proof compatibility "$candidate_release" "$recovery_release" "$state_database" >/dev/null || return 1
			assert_protected_identity || return 1
			assert_policy || return 1
			proof recovery-check "$journal_file" "$evidence" "$serving_root" restored >/dev/null || return 1
			if ((reconcile_stopped)); then
				check_stopped_recovery || return 1
				proof recovery-start-intent "$journal_file" "$evidence" "$serving_root" >/dev/null || return 1
				check_stopped_recovery || return 1
			fi
			migration_native_guard || return 1
			proof migration-permit-present "$serving_root" >/dev/null || return 1
			[[ "$(read_pointer "$current_link")" == "$recovery_release" &&
				"$(stopped_restored_snapshot "$expected_pid")" == "$stopped_snapshot" ]] || {
				fail 'stopped restored Gateway ownership changed before start; journal retained'
				return 1
			}
			rollback_armed=0
			held_suspension=""
			bounded 180 "$systemctl_bin" start openclaw-gateway.service || { start_result=$?; ((reconcile_stopped)) || return "$start_result"; }
			if ((reconcile_stopped)); then
				printf 'RECOVERY_START_REQUEST result=%s repeat-start=refused\n' "$start_result"
				recovery_jobs_settled || return 1
			fi
			started_restored=1
			capture_service_generation system || {
				if ((reconcile_stopped)); then fail 'stopped predecessor start is unverified; intent and journal retained; repeat start refused'; return 1; fi
				fail 'restored predecessor start occurred but generation binding failed; journal retained; no additional start attempted'
				return 1
			}
			[[ "$(realpath "$proc_root/$service_pid/cwd")" == "$recovery_release" ]] || {
				fail 'restored predecessor start occurred but the working release is unexpected; journal retained; no additional start attempted'
				return 1
			}
			recovery_owned_pid="$service_pid"
			recovery_owned_generation="$service_generation"
			suspension_release="$recovery_release"
			suspension_pid="$service_pid"
			suspension_generation="$service_generation"
		fi
		if ((reconcile_stopped)); then operator_expected_pid="$service_pid"; operator_expected_generation="$service_generation"; fi
		if [[ -n "$reconcile_evidence" ]]; then
			[[ "$service_pid" == "$operator_expected_pid" && "$service_generation" == "$operator_expected_generation" ]] || {
				fail 'reviewed reconciliation process generation changed'; return 1;
			}
			recovery_owned_pid="$service_pid"; recovery_owned_generation="$service_generation"
		fi
		expected_pid="$service_pid"
		expected_generation="$service_generation"
		wait_for_live "$recovery_release" "$recovery_info" system "$recovery_owned_pid" "$recovery_owned_generation" || {
			if ((started_restored)); then
				fail 'restored predecessor start occurred but verification failed; journal retained; no additional start attempted'
			else
				fail 'current release verification failed; rollback evidence retained without restart'
			fi
			return 1
		}
		if ((reconcile_stopped)); then recovery_jobs_settled || return 1; fi
		if ! { proof compatibility "$candidate_release" "$recovery_release" "$state_database" >/dev/null &&
			assert_protected_identity && assert_policy &&
			proof recovery-check "$journal_file" "$evidence" "$serving_root" "$recovery_position" >/dev/null &&
			recheck_generation system "$expected_pid" "$expected_generation"; }; then
			if ((started_restored)); then
				fail 'restored predecessor start occurred but final verification failed; journal retained; no additional start attempted'
			fi
			return 1
		fi
		proof recovery-finish "$journal_file" "$evidence" "$serving_root" "$recovery_position" >/dev/null || return 1
		if [[ -n "$reconcile_evidence" ]]; then
			printf 'RECOVERY_CONFIG_RECONCILED config_sha256=%s journal_sha256=%s original-journal=retired-without-rewrite\n' "$reconcile_config_hash" "$reconcile_journal_hash"
		fi
		if ((started_restored)); then
			printf 'RECOVERED phase=ROLLBACK_FAILED action=started-restored-predecessor session-proof=%s\n' "$session_proof"
		elif [[ -n "$reconcile_evidence" && "$recovery_position" == selected ]]; then
			printf 'RECOVERED phase=ROLLBACK_FAILED action=verified-selected-candidate-reconciled-config session-proof=%s\n' "$session_proof"
		else
			printf 'RECOVERED phase=ROLLBACK_FAILED action=verified-%s-no-restart session-proof=%s\n' "$recovery_label" "$session_proof"
		fi
		;;
	*) fail 'activation journal has an unknown durable phase' ;;
	esac
}

freeze_origin_main() {
	local remote_url
	[[ -d "$mirror" && ! -L "$mirror" ]] || fail 'isolated deploy-account mirror has not been installed'
	remote_url="$(bounded_build "$fetch_budget" git -C "$mirror" remote get-url origin)" || return 1
	[[ "$remote_url" == "$official_origin" ]] || fail 'deploy-account mirror origin is not official'
	proof release-capacity fetch "$mirror" "$mirror" "$build_home" "$serving_root" "$runtime_home" >&2 || return 1
	bounded_build "$fetch_budget" git "${mirror_git_config[@]}" -C "$mirror" fetch --prune origin \
		+refs/heads/main:refs/remotes/origin/main +refs/heads/main:refs/heads/main
	frozen_main="$(run_build git -C "$mirror" rev-parse refs/remotes/origin/main)"
	[[ "$frozen_main" =~ ^[a-f0-9]{40}$ ]] || fail 'frozen official origin/main is not an exact SHA'
}

prepare_release() {
	local timer enabled active prepared_info
	[[ ! -e "$journal_file" && ! -L "$journal_file" ]] || fail 'release preparation refuses a pending activation journal'
	for timer in openclaw-hourly-update.timer openclaw-nightly-update.timer; do
		enabled="$(system_systemctl is-enabled "$timer" 2>/dev/null || true)"
		active="$(system_systemctl is-active "$timer" 2>/dev/null || true)"
		[[ ("$enabled" == disabled || "$enabled" == not-found) && "$active" == inactive ]] ||
			fail 'release preparation requires disabled inactive deployment timers'
	done
	# Both existing owners require ordinary errexit context, never an OR-list caller.
	freeze_origin_main
	bounded_build "$fetch_budget" git -C "$mirror" merge-base --is-ancestor "$requested_sha" "$frozen_main" ||
		fail 'prepared SHA is not in the verified official main history'
	if [[ -e "$releases_root/$requested_sha" ]]; then
		# An already sealed release needs validation, never resealing or activation.
		prepared_info="$(proof validate-release "$releases_root/$requested_sha" "$requested_sha" "$root_uid" sealed)" || return 1
		prewarm_release "$releases_root/$requested_sha"
	else
		prepared_info="$(publish_release "$requested_sha" "$mirror")"
	fi
	[[ ! -e "$journal_file" && ! -L "$journal_file" ]] || fail 'activation journal appeared during release preparation'
	printf 'PREPARED sha=%s action=prepare-release restart=0 activation=0\n' "$requested_sha"
	printf '%s\n' "$prepared_info"
}

assert_frozen_predecessor() {
	[[ -n "$frozen_target_sha" ]] || return 0
	local instance="${1:-}" observed timer enabled active
	operator_check_lock held || return 1
	proof maintenance-idle "$ancestor_boundary" >/dev/null || return 1
	for timer in openclaw-hourly-update.timer openclaw-nightly-update.timer; do
		enabled="$(system_systemctl is-enabled "$timer" 2>/dev/null || true)"
		active="$(system_systemctl is-active "$timer" 2>/dev/null || true)"
		[[ ("$enabled" == disabled || "$enabled" == not-found) && "$active" == inactive ]] || {
			fail 'frozen continuation requires disabled inactive deployment timers'; return 1;
		}
	done
	observed="$(proof validate-frozen-release "$candidate_release" "$frozen_target_sha" "$frozen_manifest_sha" "$root_uid")" || return 1
	[[ "$observed" == "$frozen_release_binding" ]] || { fail 'frozen release identity changed after admission'; return 1; }
	[[ "$(read_pointer "$current_link")" == "$releases_root/$frozen_predecessor_sha" ]] || {
		fail 'frozen continuation predecessor changed'; return 1;
	}
	recheck_generation system "$frozen_predecessor_pid" "$frozen_predecessor_start" || return 1
	if [[ -z "$instance" ]]; then
		instance="$(read_process_instance "$previous_release" "$frozen_predecessor_pid")" || return 1
	fi
	[[ "$instance" == "$frozen_process_instance" ]] || { fail 'frozen continuation process instance changed'; return 1; }
	recheck_generation system "$frozen_predecessor_pid" "$frozen_predecessor_start" || return 1
	proof gateway-runtime process "$serving_root" "$gateway_runtime_snapshot" "$proc_root" "$frozen_predecessor_pid" >/dev/null || return 1
	observed="$(proof validate-frozen-release "$candidate_release" "$frozen_target_sha" "$frozen_manifest_sha" "$root_uid")" || return 1
	[[ "$observed" == "$frozen_release_binding" ]] || { fail 'frozen release identity changed after admission'; return 1; }
	[[ "$(read_pointer "$current_link")" == "$previous_release" ]] || return 1
	assert_protected_identity || return 1
	assert_policy
}

continue_frozen_release() {
	operator_check_lock held
	[[ ! -e "$journal_file" && ! -L "$journal_file" ]] || fail 'frozen continuation refuses a pending journal; use its existing recovery owner'
	assert_protected_identity
	assert_policy
	previous_release="$(read_pointer "$current_link")"
	[[ "$previous_release" == "$releases_root/$frozen_predecessor_sha" ]] || fail 'frozen continuation predecessor changed'
	previous_info="$(proof validate-release "$previous_release" "$frozen_predecessor_sha" "$root_uid" sealed)"
	candidate_release="$releases_root/$frozen_target_sha"
	frozen_release_binding="$(proof validate-frozen-release "$candidate_release" "$frozen_target_sha" "$frozen_manifest_sha" "$root_uid")"
	candidate_info="$(printf '%s' "$frozen_release_binding" | "$node_bin" -e 'let s="";process.stdin.on("data",x=>s+=x);process.stdin.on("end",()=>process.stdout.write(JSON.stringify(JSON.parse(s).release)));')"
	recheck_generation system "$frozen_predecessor_pid" "$frozen_predecessor_start"
	frozen_process_instance="$(read_process_instance "$previous_release" "$frozen_predecessor_pid")"
	assert_frozen_predecessor "$frozen_process_instance"
	assert_scheduler_owner "$previous_release"
	freeze_origin_main
	bounded_build "$fetch_budget" git -C "$mirror" merge-base --is-ancestor "$frozen_target_sha" "$frozen_main" ||
		fail 'frozen candidate is not in current official main history'
	bounded_build "$fetch_budget" git -C "$mirror" merge-base --is-ancestor "$frozen_predecessor_sha" "$frozen_target_sha" ||
		fail 'frozen candidate is not a successor of the expected predecessor'
	assert_frozen_predecessor
	[[ ! -e "$journal_file" && ! -L "$journal_file" ]] || fail 'activation journal appeared during frozen admission'
	activate_prepared_release
}

activate_prepared_release() {
	# The candidate update owner decides whether the installed databases can migrate.
	native_update_root=""
	if [[ "$(proof migration-kind "$previous_info" "$candidate_info")" != none || "$offline_doctor_repair" == 1 ]]; then
		native_update_root="$runtime_home/.openclaw-update/update-$(basename "$candidate_release")-$("$node_bin" -e 'process.stdout.write(require("node:crypto").randomUUID())')/native"
		((capture_startup_profile + arm_steady_profile == 0)) || fail 'startup profiling cannot accompany a native state update'
	fi
	if [[ "$mode" == continue-frozen ]]; then mode=deploy; fi
	activate_release system
}

handoff_source() {
	local phase admission running_containers schedule_snapshot timer maintenance_state
	phase="$(proof host-handoff status "$serving_root")"
	previous_release="$(read_pointer "$current_link")"
	[[ "$previous_release" == "$releases_root/$operator_expected_sha" ]] || fail 'handoff source SHA changed'
	previous_info="$(proof validate-release "$previous_release" "$operator_expected_sha" "$root_uid" sealed)"
	if [[ "$phase" != none ]]; then
		phase="$(proof host-handoff source-check "$serving_root" "$operator_expected_sha" "$operator_expected_pid" \
			"$operator_expected_generation" "$handoff_peer" "$handoff_target_sha")"
	fi
	assert_protected_identity
	assert_policy
	migration_guard="$(proof migration-guard-descriptor "$serving_root")"
	migration_native_guard
	if [[ "$phase" == none || "$phase" == SOURCE_PREPARED ]]; then
		if [[ "$phase" == SOURCE_PREPARED ]] && predecessor_is_stopped "$operator_expected_pid" &&
			proof migration-permit-absent "$serving_root" >/dev/null; then
			proof host-handoff source-stopped "$serving_root" >/dev/null
		else
			recheck_generation system "$operator_expected_pid" "$operator_expected_generation"
			[[ "$(realpath "$proc_root/$operator_expected_pid/cwd")" == "$previous_release" ]] || fail 'handoff source process is not the selected release'
			assert_scheduler_owner "$previous_release"
			suspension_scope=system; suspension_release="$previous_release"
			suspension_pid="$operator_expected_pid"; suspension_generation="$operator_expected_generation"
			suspension_instance="$(read_process_instance "$previous_release" "$operator_expected_pid")"
			prepare_suspension "$previous_release"
			check_suspension
			schedule_snapshot="$(for timer in openclaw-hourly-health-check.timer openclaw-disk-maintenance.timer; do
				printf '%s\n' "$timer"
				system_systemctl show "$timer" --property=UnitFileState --property=ActiveState --property=SubState --property=LastTriggerUSec --property=NextElapseUSecRealtime || exit 1
			done)"
			admission="$(SOURCE="$previous_info" PID="$suspension_pid" GENERATION="$suspension_generation" INSTANCE="$suspension_instance" \
				LEASE="$held_suspension" EXPIRY="$suspension_expiry" LEASE_STATE="$suspension_state" SCHEDULES="$schedule_snapshot" "$node_bin" -e '
const e=process.env,schedules={};let name;for(const line of e.SCHEDULES.split("\n")){const i=line.indexOf("=");if(i<0){name=line;schedules[name]={};}else schedules[name][line.slice(0,i)]=line.slice(i+1);}
process.stdout.write(JSON.stringify({source:JSON.parse(e.SOURCE),process:{pid:Number(e.PID),generation:e.GENERATION,instance:e.INSTANCE},suspension:{id:e.LEASE,expiresAtMs:Number(e.EXPIRY),status:e.LEASE_STATE.toLowerCase(),terminalPolicy:"preserve"},schedules}));')"
			if [[ "$phase" == none ]]; then
				printf '%s' "$admission" | proof host-handoff source-begin "$serving_root" "$handoff_peer" "$handoff_target_sha" "$config_file" "$state_database" @stdin >/dev/null
			else
				printf '%s' "$admission" | proof host-handoff source-suspension "$serving_root" @stdin >/dev/null
			fi
			migration_stop_contract
			assert_protected_identity
			assert_policy
			check_suspension
			arm_suspension_handoff
			proof host-handoff source-revoke "$serving_root" >/dev/null
			check_suspension
			[[ "$(realpath "$proc_root/$operator_expected_pid/cwd")" == "$previous_release" ]] || fail 'handoff source cwd changed before stop'
			bounded 180 "$systemctl_bin" stop openclaw-gateway.service
			held_suspension=""; predecessor_stopped=1
			predecessor_is_stopped "$operator_expected_pid"
			proof host-handoff source-stopped "$serving_root" >/dev/null
		fi
	fi
	predecessor_is_stopped "$operator_expected_pid"
	if ((handoff_stop_only)); then
		printf 'HANDOFF_STOPPED sha=%s source-start-permit=absent external-writers=must-settle-before-export\n' "$operator_expected_sha"
		return 0
	fi
	for timer in openclaw-hourly-health-check.timer openclaw-disk-maintenance.timer; do
		[[ "$(system_systemctl is-enabled "$timer" 2>/dev/null || true)" == disabled &&
			"$(system_systemctl is-active "$timer" 2>/dev/null || true)" == inactive ]] ||
			fail 'source health and disk-maintenance timers must be retired before final export'
		maintenance_state="$(system_systemctl show "${timer%.timer}.service" --property=ActiveState --property=MainPID --property=ControlPID --property=Job)"
		MAINTENANCE_STATE="$maintenance_state" "$node_bin" -e '
const s=Object.fromEntries(process.env.MAINTENANCE_STATE.trim().split("\n").map(line=>{const i=line.indexOf("=");return[line.slice(0,i),line.slice(i+1)]}));
if(!["inactive","failed"].includes(s.ActiveState)||s.MainPID!=="0"||s.ControlPID!=="0"||!["","0"].includes(s.Job))process.exit(1);
' || fail 'source maintenance service has not settled before final export'
	done
	if [[ -x /usr/bin/podman ]]; then
		running_containers="$(cd "$runtime_home" && run_runtime /usr/bin/podman --remote=false ps --format '{{.ID}}')"
		[[ -z "$running_containers" ]] || fail 'running rootless Podman containers must settle through their owner before handoff export'
	fi
	proof host-handoff source-export "$serving_root"
	if [[ -x /usr/bin/podman ]]; then
		running_containers="$(cd "$runtime_home" && run_runtime /usr/bin/podman --remote=false ps --format '{{.ID}}')"
		[[ -z "$running_containers" ]] || fail 'a rootless Podman writer appeared during export; settle it before final filesystem transfer'
	fi
	printf 'HANDOFF_EXPORTED sha=%s target=%s source-start-permit=absent source-journal=retained\n' "$operator_expected_sha" "$handoff_target_sha"
}

handoff_target_stopped() {
	local system_json manager_json user_json
	system_json="$(system_systemctl show openclaw-gateway.service --property=ActiveState --property=SubState --property=MainPID --property=ControlPID --property=Job --property=UnitFileState)"
	manager_json="$(system_systemctl show "user@$runtime_uid.service" --property=ActiveState --property=MainPID --property=ControlPID --property=Job)"
	user_json=""
	if [[ "$manager_json" == *'ActiveState=active'* ]]; then
		user_json="$(user_systemctl show openclaw-gateway.service --property=ActiveState --property=MainPID --property=ControlPID --property=Job --property=UnitFileState)"
	fi
	SYSTEM_SNAPSHOT="$system_json" MANAGER_SNAPSHOT="$manager_json" USER_SNAPSHOT="$user_json" "$node_bin" -e '
const parse=text=>Object.fromEntries(text.trim().split("\n").map(line=>{const i=line.indexOf("=");return[line.slice(0,i),line.slice(i+1)]}));
const stopped=x=>["inactive","failed"].includes(x.ActiveState)&&x.MainPID==="0"&&x.ControlPID==="0"&&["","0"].includes(x.Job);
const s=parse(process.env.SYSTEM_SNAPSHOT),m=parse(process.env.MANAGER_SNAPSHOT),u=parse(process.env.USER_SNAPSHOT);
if(!stopped(s)||!["disabled","enabled"].includes(s.UnitFileState))process.exit(1);
if(m.ActiveState==="active"){if(!stopped(u)||!["disabled","not-found"].includes(u.UnitFileState))process.exit(1);}else if(!stopped(m))process.exit(1);
'
	[[ -z "$("$ss_bin" -H -ltnp "sport = :$gateway_port")" ]] || fail 'target Gateway listener is already owned'
	migration_guard="$(proof migration-guard-descriptor "$serving_root")"
	migration_native_guard
}

handoff_select_target() {
	local pointer
	previous_release="$releases_root/$(proof journal-field "$journal_file" predecessor.sha)"
	candidate_release="$releases_root/$(proof journal-field "$journal_file" candidate.sha)"
	# The imported journal precedes both names; previous is always published first.
	for pointer in "$previous_link" "$current_link"; do
		proof host-handoff target-selection "$serving_root" prefix >/dev/null
		if [[ ! -e "$pointer" && ! -L "$pointer" ]]; then
			if [[ "$pointer" == "$previous_link" ]]; then atomic_pointer "$previous_release" "$pointer"
			else atomic_pointer "$candidate_release" "$pointer"; fi
		fi
	done
	# A retry can observe a rename that completed before its original directory fsync.
	proof sync-directory "$serving_root" >/dev/null
	proof host-handoff target-selection "$serving_root" complete >/dev/null
}

handoff_target() {
	local phase
	phase="$(proof host-handoff status "$serving_root")"
	if [[ "$mode" == handoff-import ]]; then
		handoff_target_stopped
		proof migration-permit-absent "$serving_root" >/dev/null
		proof host-handoff target-import "$serving_root" "$handoff_manifest" "$handoff_manifest_hash"
		handoff_select_target
		printf 'HANDOFF_IMPORTED next=native-doctor candidate-selected=true start-permit=absent\n'
		return 0
	fi
	[[ "$phase" == TARGET_* ]] || fail 'target handoff has no imported source journal'
	previous_release="$releases_root/$(proof journal-field "$journal_file" predecessor.sha)"
	candidate_release="$releases_root/$(proof journal-field "$journal_file" candidate.sha)"
	candidate_info="$(proof validate-release "$candidate_release" "$(basename "$candidate_release")" "$root_uid" sealed)"
	if [[ "$mode" == handoff-doctor ]]; then
		handoff_target_stopped
		proof migration-permit-absent "$serving_root" >/dev/null
		proof host-handoff target-doctor "$serving_root"
		printf 'HANDOFF_DOCTOR_VERIFIED next=native-worktree-relocation-and-approved-config\n'
		return 0
	fi
	if [[ "$phase" != TARGET_VERIFYING ]]; then
		handoff_target_stopped
		proof host-handoff target-selection "$serving_root" complete >/dev/null
		proof host-handoff target-ready "$serving_root" "$handoff_config_hash" >/dev/null
	fi
	if [[ "$(system_state)" != active ]]; then
		handoff_target_stopped
		proof host-handoff target-permit "$serving_root" >/dev/null
		system_systemctl enable openclaw-gateway.service
		bounded 180 "$systemctl_bin" start openclaw-gateway.service
	fi
	[[ "$(read_pointer "$current_link")" == "$candidate_release" ]] || fail 'target serving pointer changed'
	protected_identity="$(proof fingerprint "$config_file" "$state_database")"
	policy_identity="$(proof policy "$config_file")"
	session_witness="$(proof host-handoff target-witness "$serving_root")"
	wait_for_live "$candidate_release" "$candidate_info" system
	verified_markers record "$(basename "$candidate_release")"
	proof host-handoff target-finish "$serving_root" >/dev/null
	printf 'SUCCESS sha=%s action=host-handoff previous=%s source-writer=fenced APPROVAL_DELTA=0\n' "$(basename "$candidate_release")" "$(basename "$previous_release")"
}

clean_candidate() {
	local pathname="$1" parent="$2" owner="$3" group="$4" binding="$5" device inode
	read -r device inode <<<"$binding"
	bounded 120 "$node_bin" --no-warnings "$release_lib" clean-candidate \
		"$pathname" "$parent" "$owner" "$group" "$device" "$inode" >/dev/null
}

cleanup_candidate_failure() {
	local exit_code="$1"
	trap - EXIT
	if [[ -n "${candidate_staging_identity:-}" && -e "$candidate_staging_path" ]]; then
		clean_candidate "$candidate_staging_path" "$serving_root/staging" "$root_uid" "$root_gid" "$candidate_staging_identity" || exit_code=1
	fi
	if [[ -n "${candidate_build_identity:-}" && -e "$candidate_build_path" ]]; then
		clean_candidate "$candidate_build_path" "$work_root" "$deploy_file_uid" "$deploy_gid" "$candidate_build_identity" || exit_code=1
	fi
	exit "$exit_code"
}

prewarm_release() {
	local release="$1"
	# The final filenames and runtime UID must match the cutover boot's cache keys.
	if [[ -f "$release/dist/gateway-prewarm.js" ]]; then
		bounded 120 "$runuser_bin" -u openclaw -- env -i HOME="$runtime_home" PATH=/usr/bin:/bin \
			"$gateway_runtime_bin" "$release/gateway-prewarm.mjs" >&2 ||
			printf 'WARNING compile-cache prewarm failed; the Gateway will compile on startup\n' >&2
	fi
}

publish_release() (
	set -Eeuo pipefail
	local target_sha="$1" reference_release="$2"
	local build_directory="$work_root/$target_sha.$$" staging="$serving_root/staging/$target_sha.$$"
	local published="$releases_root/$target_sha" origin_after published_info
	candidate_build_path="$build_directory"
	candidate_staging_path="$staging"
	candidate_build_identity=""
	candidate_staging_identity=""
	if [[ -e "$published" ]]; then
		published_info="$(proof validate-release "$published" "$target_sha" "$root_uid" sealed)"
		proof release-capacity activate "$published" "$mirror" "$build_home" "$serving_root" "$runtime_home" >&2
		prewarm_release "$published"
		printf '%s\n' "$published_info"
		return 0
	fi
	[[ ! -e "$build_directory" && ! -e "$staging" ]] || fail 'candidate path already exists'
	proof release-capacity build "$reference_release" "$mirror" "$build_home" "$serving_root" "$runtime_home" >&2
	trap 'cleanup_candidate_failure "$?"' EXIT
	run_build mkdir -m 0700 -- "$build_directory"
	candidate_build_identity="$(proof stat-identity "$build_directory")"
	# pnpm owns content integrity and pruning; imports must never share writable store inodes.
	local cache_environment=(env "PNPM_CONFIG_STORE_DIR=$build_home/cache/store" "BUILD_ALL_CACHE_ROOT=$build_home/cache/build-artifacts" PNPM_CONFIG_PACKAGE_IMPORT_METHOD=clone-or-copy PNPM_CONFIG_VERIFY_STORE_INTEGRITY=true)
	run_build git init "$build_directory" >&2
	bounded_build "$clone_budget" git "${mirror_git_config[@]}" -C "$build_directory" fetch --depth=1 --no-tags \
		"file://$mirror" "$target_sha" >&2
	bounded_build "$clone_budget" git "${mirror_git_config[@]}" -C "$build_directory" checkout --detach "$target_sha" >&2
	run_build git -C "$build_directory" remote add origin "$official_origin" >&2
	origin_after="$(run_build git -C "$build_directory" remote get-url origin)"
	[[ "$origin_after" == "$official_origin" &&
		"$(run_build git -C "$build_directory" rev-parse HEAD)" == "$target_sha" &&
		-d "$build_directory/.git" && ! -L "$build_directory/.git" &&
		! -e "$build_directory/.git/objects/info/alternates" ]] ||
		fail 'candidate is not a standalone clone of the frozen official SHA'
	bounded_build "$build_budget" "${cache_environment[@]}" pnpm -C "$build_directory" install --frozen-lockfile >&2
	bounded_build "$build_budget" "${cache_environment[@]}" env NODE_OPTIONS=--max-old-space-size=24576 OPENCLAW_RUN_NODE_SKIP_DTS_BUILD=1 \
		pnpm -C "$build_directory" build >&2
	proof validate-release "$build_directory" "$target_sha" "$root_uid" candidate >/dev/null
	proof release-capacity stage "$build_directory" "$mirror" "$build_home" "$serving_root" "$runtime_home" >&2
	install -d -m 0700 "$staging"
	candidate_staging_identity="$(proof stat-identity "$staging")"
	cp -a --reflink=auto "$build_directory/." "$staging"
	chown -hR "$root_uid:$root_uid" "$staging"
	proof manifest "$staging" "$target_sha" "$root_uid" "$frozen_main" >/dev/null
	"$node_bin" --no-warnings "$release_lib" seal-tree "$staging" 8>&- 9>&-
	proof validate-release "$staging" "$target_sha" "$root_uid" sealed >/dev/null
	[[ "$(proof stat-identity "$staging" | cut -d' ' -f1)" == "$(proof stat-identity "$releases_root" | cut -d' ' -f1)" ]] ||
		fail 'candidate publication crosses filesystem devices'
	mv -T "$staging" "$published"
	candidate_staging_identity=""
	proof sync-directory "$releases_root"
	published_info="$(proof validate-release "$published" "$target_sha" "$root_uid" sealed)"
	clean_candidate "$candidate_build_path" "$work_root" "$deploy_file_uid" "$deploy_gid" "$candidate_build_identity"
	candidate_build_identity=""
	trap - EXIT
	proof release-capacity activate "$published" "$mirror" "$build_home" "$serving_root" "$runtime_home" >&2
	prewarm_release "$published"
	printf '%s\n' "$published_info"
)

cleanup_releases() {
	local current_sha previous_sha protected_sha field directory sha identity device inode quarantine quarantine_name extra
	local verified_shas verified_count=0 release_device
	local selected_sha="${1:-}"
	local -a directories protected
	current_sha="$(read_pointer "$current_link")" || return 1
	current_sha="$(basename "$current_sha")"
	previous_sha=""
	if [[ -L "$previous_link" ]]; then
		previous_sha="$(read_pointer "$previous_link")" || return 1
		previous_sha="$(basename "$previous_sha")"
	fi
	protected=("$current_sha" "$previous_sha" "$selected_sha")
	verified_shas="$(verified_markers list)" || return 1
	while IFS= read -r protected_sha; do
		[[ -n "$protected_sha" ]] || continue
		protected+=("$protected_sha")
		verified_count=$((verified_count + 1))
		((verified_count < 3)) || break
	done <<<"$verified_shas"
	if [[ -e "$journal_file" ]]; then
		# Original pointer targets are needed to restore topology after an interrupted cutover.
		for field in predecessor.sha candidate.sha pointerTopology.current.sha pointerTopology.previous.sha; do
			protected_sha="$(proof journal-field "$journal_file" "$field")" || return 1
			[[ "$protected_sha" == null ]] || protected+=("$protected_sha")
		done
	fi
	for quarantine in "$serving_root/quarantine"/*; do
		[[ -e "$quarantine" ]] || continue
		quarantine_name="$(basename "$quarantine")"
		IFS=. read -r sha device inode extra <<<"$quarantine_name"
		[[ "$sha" =~ ^[a-f0-9]{40}$ && "$device" =~ ^[0-9]+$ &&
			"$inode" =~ ^[0-9]+$ && -z "$extra" ]] || continue
		[[ " ${protected[*]} " != *" $sha "* &&
			! -e "$serving_root/pins/$sha" ]] || continue
		if [[ "$(proof prune-quarantine "$quarantine" "$serving_root" "$root_uid" "$device" "$inode")" == PRUNED ]]; then
			printf 'PRUNED_RELEASE=%s\n' "$sha"
		fi
	done
	directories=()
	while IFS= read -r directory; do
		[[ -n "$directory" ]] && directories+=("$directory")
	done < <(proof list-releases "$releases_root")
	identity="$(proof stat-identity "$releases_root")" || return 1
	read -r release_device inode <<<"$identity"
	for directory in "${directories[@]}"; do
		sha="$(basename "$directory")"
		[[ "$sha" =~ ^[a-f0-9]{40}$ ]] || continue
		[[ " ${protected[*]} " != *" $sha "* &&
			! -e "$serving_root/pins/$sha" ]] || continue
		# Failure markers do not pin trees; retain mounted diagnostics before scanning.
		identity="$(proof stat-identity "$directory")" || continue
		read -r device inode <<<"$identity"
		if [[ "$device" != "$release_device" ]]; then
			printf 'RETAIN different-filesystem release=%s\n' "$sha" >&2
			continue
		fi
		if ! proof validate-release "$directory" "$sha" "$root_uid" sealed >/dev/null 2>&1; then
			printf 'RETAIN uncertain release=%s\n' "$sha" >&2
			continue
		fi
		quarantine="$serving_root/quarantine/$sha.$device.$inode"
		if proof quarantine "$directory" "$quarantine" "$root_uid" "$device" "$inode" >/dev/null; then
			if [[ "$(proof prune-quarantine "$quarantine" "$serving_root" "$root_uid" "$device" "$inode")" == PRUNED ]]; then
				printf 'PRUNED_RELEASE=%s\n' "$sha"
			else
				printf 'QUARANTINED_RELEASE=%s\n' "$sha"
			fi
		else
			printf 'RETAIN replacement-safe quarantine refused release=%s\n' "$sha" >&2
		fi
	done
	# A release removed in this pass must not leave a stale verification claim.
	verified_markers list >/dev/null
}

activate_release() {
	local topology="$1" old_pid old_generation old_instance suspension_result predecessor_sha candidate_sha
	local prepare_profile=0
	local prior_current_sha=absent prior_previous_sha=absent
	local prior_system_enabled prior_system_active prior_user_enabled prior_user_active
	predecessor_sha="$(basename "$previous_release")"
	candidate_sha="$(basename "$candidate_release")"
	capture_generation "$topology"
	old_pid="$service_pid"
	old_generation="$service_generation"
	suspension_scope="$topology"
	suspension_pid="$old_pid"
	suspension_generation="$old_generation"
	prior_system_enabled="$(system_unit_state)"
	prior_system_active="$(system_state)"
	prior_user_enabled="$(user_unit_state)"
	prior_user_active="$(user_state)"
	assert_protected_identity
	assert_policy
	assert_scheduler_owner "$previous_release"
	if [[ -n "${native_update_root:-}" ]]; then
		migration_install_guard || return 1
	else
		run_gateway "$candidate_release" config validate >/dev/null
	fi
	session_witness="$(proof session-preservation-witness "$previous_release" "$candidate_release" "$state_database")"
	# Bind before suspension fences ordinary RPCs; frozen continuation must keep
	# its admitted predecessor through the ordinary activation owner.
	old_instance="$(read_process_instance "$previous_release" "$old_pid")"
	assert_frozen_predecessor "$old_instance"
	# Sealed release audits belong before admission closes; the deployment lock
	# excludes other pointer writers while the Gateway cannot modify these trees.
	if [[ -L "$current_link" ]]; then
		prior_current_sha="$(read_pointer "$current_link")" || return 1
		prior_current_sha="${prior_current_sha##*/}"
	fi
	if [[ -L "$previous_link" ]]; then
		prior_previous_sha="$(read_pointer "$previous_link")" || return 1
		prior_previous_sha="${prior_previous_sha##*/}"
	fi
	suspension_instance="$old_instance"
	if prepare_suspension "$previous_release"; then
		:
	else
		suspension_result=$?
		return "$suspension_result"
	fi
	assert_protected_identity
	assert_policy
	proof compatibility "$previous_release" "$candidate_release" "$state_database" >/dev/null
	assert_frozen_predecessor "$old_instance"
	if ! check_suspension; then
		printf 'DEFERRED preserved Gateway suspension expired during compatibility proof\n'
		return 75
	fi
	if [[ "$prior_current_sha" != absent || -L "$current_link" ]]; then
		[[ "$prior_current_sha" == "$predecessor_sha" && -L "$current_link" &&
			"$(realpath "$current_link")" == "$previous_release" ]] || {
			fail 'current release changed before journal publication'
			return 1
		}
	fi
	set -- "$journal_file" "$previous_info" "$candidate_info" "$topology" \
		"$old_pid" "$old_generation" "$old_instance" "$protected_identity" \
		"$prior_current_sha" "$prior_previous_sha" \
		"$prior_system_enabled" "$prior_system_active" "$prior_user_enabled" "$prior_user_active" \
		"$held_suspension" "$suspension_expiry" "$suspension_state"
	if [[ "$topology" == system ]]; then
		printf '%s' "$session_witness" | proof journal-create "$@" @stdin "$runtime_target" "${native_update_root:-}" >/dev/null
	else
		proof journal-create "$@" >/dev/null
	fi
	session_witness_record="$(proof journal-read "$journal_file")"
	load_journal_witness
	if [[ "$mode" != runtime ]]; then atomic_pointer "$previous_release" "$previous_link"; fi
	proof journal-phase "$journal_file" B_PREVIOUS_PUBLISHED "$session_witness_record" >/dev/null
	if ((capture_startup_profile || arm_steady_profile)); then
		[[ "$topology" == system ]] || fail 'startup profiling requires an ordinary system deployment'
		migration_check_existing_guard
		if profile_inspect; then
			prepare_profile=1
		elif ((arm_steady_profile)); then
			profile_skip_no_effect || return $?
		else
			return 1
		fi
	fi
	if [[ "$topology" == system && "$mode" != runtime ]]; then
		assert_stop_start_contract || return 1
		if ! assert_frozen_predecessor "$old_instance" || ! assert_protected_identity || ! assert_policy || ! check_suspension || ! arm_suspension_handoff; then
			printf 'DEFERRED owned suspension or predecessor changed before system stop\n'
			return 75
		fi
		# Publishing current while this process lives can trigger its installation-replacement restart.
		rollback_armed=1
		if ! system_systemctl stop openclaw-gateway.service; then
			restart_failed=1
			if predecessor_is_stopped "$old_pid"; then
				predecessor_stopped=1
				held_suspension=""
			fi
			fail 'system stop failed; retaining exact predecessor ownership for rollback'
			return 1
		fi
		predecessor_stopped=1
		held_suspension=""
		predecessor_is_stopped "$old_pid" || return 1
		assert_protected_identity
		assert_policy
		if [[ -n "${native_update_root:-}" ]]; then
			install -d -m 0700 -o "$runtime_uid" -g "$(id -g openclaw)" "$(dirname "$native_update_root")"
			native_update_permit revoke || return 1
			native_update migrate || return 1
			assert_policy || return 1
		fi
	fi
	if [[ "$mode" == runtime ]]; then
		rollback_armed=1
		proof gateway-runtime apply "$serving_root" >/dev/null
		system_systemctl daemon-reload
		load_gateway_runtime
	else
		atomic_pointer "$candidate_release" "$current_link"
	fi
	proof journal-phase "$journal_file" C_CURRENT_SELECTED "$session_witness_record" >/dev/null
	if [[ -n "${native_update_root:-}" ]]; then native_update_permit publish || return 1; fi
	rollback_armed=1
	if [[ "$topology" == migration-user ]]; then
		proof journal-phase "$journal_file" MIGRATION_SWITCHING "$session_witness_record" >/dev/null
		user_unit_file disable
		system_systemctl enable openclaw-gateway.service
		# Enablement has moved, but this lease still belongs to the exact live user process.
		if ! check_suspension migration-switching; then
			printf 'DEFERRED preserved Gateway suspension expired during migration switching\n'
			return 75
		fi
		user_systemctl stop openclaw-gateway.service
		predecessor_stopped=1
		held_suspension=""
		assert_exclusive_owner quiescent
		assert_protected_identity
		system_systemctl start openclaw-gateway.service
	elif [[ "$mode" == runtime ]]; then
		if ! assert_protected_identity || ! assert_policy || ! check_suspension || ! arm_suspension_handoff; then
			printf 'DEFERRED owned suspension or predecessor changed before runtime restart\n'
			return 75
		fi
		if ! system_systemctl restart openclaw-gateway.service; then
			restart_failed=1
			if predecessor_is_stopped "$old_pid"; then
				predecessor_stopped=1
				held_suspension=""
			fi
			fail 'runtime restart failed; retaining exact predecessor ownership for rollback'
			return 1
		fi
		predecessor_stopped=1
		held_suspension=""
	else
		if ((prepare_profile)); then
			# A slow stop must not spend the successor's immutable admission window.
			# Stopped ownership is recorded before preparation can fail and roll back.
			if ! predecessor_is_stopped "$old_pid" || ! assert_protected_identity || ! assert_policy; then
				fail 'profiling preparation requires the exact stopped predecessor and unchanged policy'
				return 1
			fi
			profile_prepare || return $?
			if ! predecessor_is_stopped "$old_pid" || ! assert_protected_identity || ! assert_policy; then
				fail 'profiled start refused changed stopped ownership or policy during preparation'
				return 1
			fi
		fi
		if ! predecessor_is_stopped "$old_pid" || ! assert_protected_identity || ! assert_policy || ! assert_stop_start_contract; then
			fail 'successor start requires the exact stopped predecessor and unchanged policy'
			return 1
		fi
		if ! system_systemctl start openclaw-gateway.service; then
			restart_failed=1
			fail 'successor start failed; retaining exact stopped or unknown ownership'
			return 1
		fi
	fi
	bind_successor_generation || return 1
	profile_admit || return $?
	proof journal-phase "$journal_file" D_VERIFYING "$session_witness_record" >/dev/null
	verify_deployed_candidate || { fail 'candidate activation verification failed'; return 1; }
	profile_finish true || return $?
	rollback_armed=0
	if [[ "$mode" == deploy ]]; then verified_markers record "$candidate_sha"; fi
	proof unlink-exact "$journal_file" "$root_uid" "$session_witness_record"
	if [[ -n "${native_update_root:-}" ]]; then finish_native_update; fi
	if [[ "$mode" != runtime ]]; then cleanup_releases; fi
	printf 'SUCCESS sha=%s action=%s previous=%s APPROVAL_DELTA=0\n' \
		"$candidate_sha" "$mode" "$predecessor_sha"
}

verify_deployed_candidate() {
	local candidate_config_verification=0
	local candidate_config_pid="$suspension_pid" candidate_config_generation="$suspension_generation"
	# Only this ordinary deployment's bound successor can normalize its startup config.
	[[ "$mode" != deploy && "$mode" != continue-frozen ]] || candidate_config_verification=1
	# The bound process may not have a listener yet. Keep its full convergence
	# window after start, with generation checks on every poll and verification step.
	wait_for_live "$candidate_release" "$candidate_info" system "$candidate_config_pid" "$candidate_config_generation" || return $?
	recheck_generation system "$candidate_config_pid" "$candidate_config_generation"
}

operator_check_lock() {
	# Validate before opening, then bind the held descriptor to the root-owned lock.
	"$node_bin" -e '
const fs = require("node:fs"), path = require("node:path");
const [file, rawUid, rawGid, phase] = process.argv.slice(1);
const uid = Number(rawUid), gid = Number(rawGid), parent = path.dirname(file);
const safe = (s) => s.uid === uid && s.gid === gid && !(s.mode & 0o022);
const directory = fs.lstatSync(parent);
if (process.getuid() !== uid || !directory.isDirectory() || !safe(directory) || fs.realpathSync(parent) !== parent) process.exit(1);
const entry = fs.lstatSync(file, { throwIfNoEntry: false });
if (entry && (!entry.isFile() || entry.nlink !== 1 || !safe(entry))) process.exit(1);
if (phase === "held") {
  const held = fs.fstatSync(9);
  if (!entry || held.dev !== entry.dev || held.ino !== entry.ino || !safe(held)) process.exit(1);
}
' "$lock_file" "$root_uid" "$root_gid" "$1" || fail 'operator restart requires the exact root-owned canonical lock'
}

operator_pointer_snapshot() {
	local name pointer target pointer_identity
	for name in current previous; do
		pointer="$serving_root/$name"
		if [[ -L "$pointer" ]]; then
			target="$(readlink "$pointer")" || return 1
			pointer_identity="$(proof stat-identity "$pointer")" || return 1
			printf '%s %s %s\n' "$name" "$target" "$pointer_identity"
		elif [[ "$name" == previous && ! -e "$pointer" ]]; then
			printf 'previous absent\n'
		else
			fail 'operator restart pointer is missing or not a symlink'
			return 1
		fi
	done
}

operator_assert_unchanged() {
	[[ ! -e "$journal_file" && ! -L "$journal_file" ]] || {
		fail 'operator restart refuses any activation journal'
		return 1
	}
	[[ "$(operator_pointer_snapshot)" == "$operator_pointers" ]] || {
		fail 'operator restart pointer identity changed'
		return 1
	}
	operator_check_lock held || return 1
	assert_protected_identity || return 1
	assert_policy
}

operator_assert_predecessor() {
	local stop_budget
	# The existing system owner must stop the whole old process group with a finite budget.
	stop_budget="$(system_systemctl show openclaw-gateway.service --property=TimeoutStopUSec --value)" || return 1
	[[ "$(system_systemctl show openclaw-gateway.service --property=KillMode --value)" == control-group &&
	"$(system_systemctl show openclaw-gateway.service --property=SendSIGKILL --value)" == yes &&
	"$stop_budget" =~ ^[1-9][0-9]*(us|ms|s|min)([[:space:]].*)?$ ]] || {
		fail 'operator restart requires bounded system-unit process-group shutdown'
		return 1
	}
	operator_assert_unchanged || return 1
	recheck_generation system "$operator_expected_pid" "$operator_expected_generation" || return 1
	[[ "$(realpath "$proc_root/$service_pid/cwd")" == "$operator_release" ]] || {
		fail 'operator restart process cwd is not the exact current sealed release'
		return 1
	}
}

operator_receipt() {
	local line
	printf -v line 'OPERATOR_RESTART phase=%s rc=%s sha=%s predecessor-pid=%s predecessor-generation=%s authority=operator-forced suspension=no-native-lease in-memory-preservation-unknown' \
		"$1" "$2" "$operator_expected_sha" "$operator_expected_pid" "$operator_expected_generation"
	printf '%s\n' "$line" >&8 || return 1
	"$node_bin" -e 'require("node:fs").fsyncSync(8)' || return 1
	printf '%s\n' "$line"
}

operator_restart_current() {
	local info previous probe_code response _attempt old_generation receipt_file
	operator_release="$(read_pointer "$current_link")" || return 1
	[[ "$operator_release" == "$releases_root/$operator_expected_sha" ]] || {
		fail 'operator restart expected current SHA does not match'
		return 1
	}
	if [[ -L "$previous_link" ]]; then
		previous="$(read_pointer "$previous_link")" || return 1
	elif [[ -e "$previous_link" ]]; then
		fail 'operator restart previous pointer is unsafe'
		return 1
	fi
	operator_pointers="$(operator_pointer_snapshot)" || return 1
	info="$(proof validate-release "$operator_release" "$operator_expected_sha" "$root_uid" sealed)" || return 1
	# Bind persisted transcript evidence before interruption, without requiring a live Gateway RPC.
	session_witness="$(proof session-preservation-witness "$operator_release" "$operator_release" "$state_database")" || return 1
	operator_assert_predecessor || return 1
	for _attempt in 1 2 3; do
		operator_assert_predecessor || return 1
		probe_code=0
		response="$(bounded 5 "$curl_bin" --disable --noproxy '*' --silent --connect-timeout 2 --max-time 3 \
			--output /dev/null --write-out '%{http_code}' "http://127.0.0.1:$gateway_port/readyz")" || probe_code=$?
		# Only curl timeout with no HTTP response qualifies. Outer timeout or any status refuses.
		[[ "$probe_code" == 28 && "$response" == 000 ]] || {
			fail 'operator restart refused: repeated no-response HTTP transport timeout not proved'
			return 1
		}
		operator_assert_predecessor || return 1
	done
	receipt_file="$serving_root/journal/operator-restart-$(date -u +%Y%m%dT%H%M%SZ)-$$.log"
	umask 077
	set -o noclobber
	exec 8>"$receipt_file" || return 1
	operator_receipt_open=1
	operator_receipt intent 0 || return 1
	proof sync-directory "$serving_root/journal" || return 1
	operator_assert_predecessor || return 1
	# This is explicit operator authority, not native readiness or an in-memory preservation claim.
	system_systemctl restart openclaw-gateway.service || return 1
	operator_assert_unchanged || return 1
	if [[ -e "$proc_root/$operator_expected_pid" ]]; then
		old_generation="$(process_generation "$operator_expected_pid")" || return 1
		[[ "$old_generation" != "$operator_expected_generation" ]] || {
			fail 'operator restart did not replace the exact old process generation'
			return 1
		}
	fi
	wait_for_live "$operator_release" "$info" system || return 1
	operator_assert_unchanged || return 1
	[[ "$(realpath "$proc_root/$verified_pid/cwd")" == "$operator_release" ]] || return 1
	recheck_generation system "$verified_pid" "$verified_generation" || return 1
	operator_receipt verified 0 || return 1
	operator_restart_verified=1
}

stopped_current_snapshot() {
	local gateway updater timer
	gateway="$(system_systemctl show openclaw-gateway.service --property=FragmentPath --property=DropInPaths \
		--property=EnvironmentFiles --property=Environment --property=ExecStart --property=User --property=Group \
		--property=WorkingDirectory --property=PassEnvironment --property=UnsetEnvironment)" || return 1
	updater="$(system_systemctl show openclaw-hourly-update.service --property=FragmentPath --property=DropInPaths \
		--property=EnvironmentFiles --property=Environment --property=ExecStart --property=MainPID --property=ControlPID \
		--property=Job --property=ActiveState --property=SubState)" || return 1
	timer="$(system_systemctl show openclaw-hourly-update.timer --property=FragmentPath --property=DropInPaths \
		--property=UnitFileState --property=ActiveState --property=SubState --property=Job --property=InvocationID --property=TimersCalendar \
		--property=Persistent --property=RandomizedDelayUSec --property=AccuracyUSec --property=Triggers \
		--property=NextElapseUSecRealtime --property=LastTriggerUSec)" || return 1
	GATEWAY_INPUT="$gateway" UPDATER_INPUT="$updater" TIMER_INPUT="$timer" bounded 30 "$node_bin" -e '
const fs=require("node:fs"),path=require("node:path"),crypto=require("node:crypto"),assert=require("node:assert/strict");
const [boundary,procRoot,root,config,owner,library,rawUid,rawGid,rawRuntimeUid,ownerPid,boot,phase,snapshotPid]=process.argv.slice(1);
const uid=Number(rawUid),gid=Number(rawGid),runtimeUid=Number(rawRuntimeUid);
const digest=bytes=>crypto.createHash("sha256").update(bytes).digest("hex");
const parse=raw=>{
  const result=Object.create(null);
  for(const line of raw.split("\n")) {
    const split=line.indexOf("=");assert(split>0);const name=line.slice(0,split),value=line.slice(split+1);
    if(name==="EnvironmentFiles")(result[name]??=[]).push(value);
    else {assert(!Object.hasOwn(result,name),"duplicate scalar unit property");result[name]=value;}
  }
  return result;
};
const identity=s=>({dev:s.dev,ino:s.ino,uid:s.uid,gid:s.gid,mode:s.mode,size:s.size,mtimeMs:s.mtimeMs,ctimeMs:s.ctimeMs});
function file(name,allowed=[uid],checkParents=false) {
  const before=fs.lstatSync(name);assert(before.isFile()&&before.nlink===1&&allowed.includes(before.uid)&&!(before.mode&0o022));
  assert(before.size<=4*1024*1024);assert.equal(fs.realpathSync(name),name);
  if(checkParents) {
    const relative=path.relative(boundary,name);assert(relative&&!relative.startsWith("../")&&!path.isAbsolute(relative));
    for(let parent=path.dirname(name);;parent=path.dirname(parent)) {
      const entry=fs.lstatSync(parent);
      assert(entry.isDirectory()&&allowed.includes(entry.uid)&&!(entry.mode&0o022),"environment file ancestor is unsafe");
      if(parent===boundary)break;assert(parent!=="/","environment file boundary is not an ancestor");
    }
  }
  const fd=fs.openSync(name,fs.constants.O_RDONLY|fs.constants.O_NOFOLLOW|fs.constants.O_NONBLOCK);
  try {assert.deepEqual(identity(fs.fstatSync(fd)),identity(before));const bytes=fs.readFileSync(fd);
    assert.equal(bytes.length,before.size);assert.deepEqual(identity(fs.fstatSync(fd)),identity(before));
    assert.deepEqual(identity(fs.lstatSync(name)),identity(before));return {path:name,...identity(before),sha256:digest(bytes)};
  } finally {fs.closeSync(fd);}
}
function directory(name,group=gid) {
  const s=fs.lstatSync(name);assert(s.isDirectory()&&s.uid===uid&&s.gid===group&&!(s.mode&0o022));
  assert.equal(fs.realpathSync(name),name);return {path:name,dev:s.dev,ino:s.ino,uid:s.uid,gid:s.gid,mode:s.mode};
}
assert.equal(fs.readFileSync(path.join(procRoot,"sys/kernel/random/boot_id"),"utf8").trim(),boot);
const gateway=parse(process.env.GATEWAY_INPUT),updater=parse(process.env.UPDATER_INPUT),timer=parse(process.env.TIMER_INPUT);
assert.equal(timer.UnitFileState,"enabled");assert(["active","inactive"].includes(timer.ActiveState));
assert.equal(timer.SubState,timer.ActiveState==="active"?"waiting":"dead");assert(["","0"].includes(timer.Job));
assert.equal(timer.Persistent,"yes");assert.equal(timer.RandomizedDelayUSec,"10min");assert.equal(timer.AccuracyUSec,"30s");
assert.equal(timer.Triggers,"openclaw-hourly-update.service");
assert(/^\{ OnCalendar=\*-\*-\* 05\/6:17:00 UTC ; next_elapse=.* \}$/.test(timer.TimersCalendar));
assert.equal(updater.MainPID,"0");assert.equal(updater.ControlPID,"0");assert(["","0"].includes(updater.Job));
assert(updater.ActiveState==="inactive"&&updater.SubState==="dead"||updater.ActiveState==="failed"&&updater.SubState==="failed");
assert.equal(gateway.PassEnvironment,"");assert.equal(gateway.UnsetEnvironment,"");
assert(!gateway.Environment.includes("NODE_OPTIONS="));
const command=raw=>{const matches=[...raw.matchAll(/argv\[\]=(.*?) ;/g)];assert.equal(matches.length,1);return matches[0][1];};
assert.equal(command(updater.ExecStart),"/usr/local/sbin/openclaw-release-deploy --interrupt-after-drain --allow-cpu-degraded");
assert(/(?:^| )OPENCLAW_TEAM_DRAIN_BUDGET=30(?: |$)/.test(updater.Environment));
const units=[];
for(const [name,input] of [["openclaw-gateway.service",gateway],["openclaw-hourly-update.service",updater],["openclaw-hourly-update.timer",timer]]) {
  assert.equal(input.FragmentPath,path.join(boundary,"etc/systemd/system",name));
  const paths=[input.FragmentPath,...input.DropInPaths.split(" ").filter(Boolean)];assert(paths.every(p=>!p.includes("\\")));
  const parents=[...new Set(paths.map(p=>path.dirname(p)))].map(p=>directory(p));
  const files=paths.map(p=>file(p));
  const environmentFiles=[];
  for(const value of input.EnvironmentFiles??[]) {
    let remaining=value;
    while(remaining) {
      const match=/^(\/[^\s\\]+) \(ignore_errors=no\)(?: |$)/.exec(remaining);assert(match,"unsupported environment-file admission");
      environmentFiles.push(file(match[1],[uid,runtimeUid],true));remaining=remaining.slice(match[0].length);
    }
  }
  // Keep effective values private. File and loaded-input hashes bind this operation.
  units.push({name,parents,files,environmentFiles,loaded:digest(JSON.stringify(name.endsWith(".timer")?
    [timer.Persistent,timer.RandomizedDelayUSec,timer.AccuracyUSec,timer.Triggers,"*-*-* 05/6:17:00 UTC"]:
    [command(input.ExecStart),input.Environment,input.EnvironmentFiles,input.User,input.Group,input.WorkingDirectory,input.PassEnvironment,input.UnsetEnvironment]))});
}
const profile=path.join(boundary,"var/lib/openclaw-team-diagnostics");
const profiles=[];
if(fs.existsSync(profile)) {
  directory(profile);
  for(const operation of fs.readdirSync(profile).sort()) {
    assert(/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/.test(operation),"unfinished profiling publication");
    const folder=path.join(profile,operation);directory(folder,fs.lstatSync(folder).gid);
    const recordFile=path.join(folder,"record.json"),recordDescriptor=file(recordFile);
    const record=JSON.parse(fs.readFileSync(recordFile,"utf8"));assert.equal(record.operation,operation);
    for(const name of ["admission.json","steady-request.json"])
      assert(!fs.lstatSync(path.join(folder,name),{throwIfNoEntry:false}),"profiling admission remains present");
    for(const binding of [record.intent?.owner,record.binding,record.retentionBinding,record.steadyRequest?.owner]) {
      if(!binding||record.intent.bootId!==boot)continue;
      assert(Number.isSafeInteger(binding.pid)&&binding.pid>0&&/^[1-9][0-9]*$/.test(binding.start));
      const processDirectory=path.join(procRoot,String(binding.pid));
      try {const fields=fs.readFileSync(path.join(processDirectory,"stat"),"utf8").split(") ").at(-1).split(" ");
        assert(fields[19]!==binding.start||["Z","X","x"].includes(fields[0]),"profiling owner or dependency remains live");
      } catch(error) {if(error.code==="ENOENT"&&!fs.existsSync(processDirectory))continue;throw error;}
    }
    const revoked=path.join(folder,"steady-revoked.json");
    const revocation=fs.lstatSync(revoked,{throwIfNoEntry:false})?file(revoked):null;
    if(revocation)assert.equal(JSON.parse(fs.readFileSync(revoked,"utf8")).operation,operation);
    assert(record.arm?.state==="closed"||record.archiveReceipt||record.steadyOutcome,"unfinished profiling operation");
    if(record.intent?.mode==="steady")assert(revocation,"steady profiling authority was not revoked");
    profiles.push({operation,record:recordDescriptor,revocation});
  }
}
for(const parent of [path.join(boundary,"run/systemd/system/openclaw-gateway.service.d"),path.join(boundary,"etc/systemd/system/openclaw-gateway.service.d"),path.join(root,"journal")]) {
  if(!fs.existsSync(parent))continue;directory(parent);
  assert(!fs.readdirSync(parent).some(name=>name.includes(".next.")||name.includes("team-cpu-profile")),"unfinished publication or profile enablement");
}
if(phase==="admit") {
  const ownProcesses=new Set([ownerPid,snapshotPid,String(process.pid),String(process.ppid)]);
  for(const pid of fs.readdirSync(procRoot).filter(name=>/^[1-9][0-9]*$/.test(name)&&!ownProcesses.has(name))) {
    const target=path.join(procRoot,pid);
    try {const bytes=fs.readFileSync(path.join(target,"cmdline"));assert(bytes.length<=1024*1024);
      const argv=bytes.toString().split("\0");
      assert(!argv.some(arg=>["openclaw-release-deploy","openclaw-hourly-update"].includes(path.basename(arg))),"another deployment owner is present");
    } catch(error){if(error.code==="ENOENT"&&!fs.existsSync(target))continue;throw error;}
  }
}
const configFile=file(config,[uid,runtimeUid]),configBytes=fs.readFileSync(config);
assert.equal(digest(configBytes),configFile.sha256,"config changed during include admission");
function rejectIncludes(value) {
  if(!value||typeof value!=="object")return;
  for(const [key,child] of Object.entries(value)){assert(key!=="$include","stopped current recovery requires a self-contained config");rejectIncludes(child);}
}
rejectIncludes(JSON.parse(configBytes.toString("utf8")));
const result={boot,units,profiles,controller:[owner,library,path.join(path.dirname(library),"startup-profile.mjs")].map(p=>file(p)),
  config:configFile,permit:file(path.join(root,"journal/gateway-start-permit")),
  marker:file(path.join(root,"verified",path.basename(fs.realpathSync(path.join(root,"current"))))) };
assert(timer.InvocationID===""||/^[a-f0-9]{32}$/.test(timer.InvocationID));
process.stdout.write(JSON.stringify({protected:result,timer:{state:timer.ActiveState,invocation:timer.InvocationID,next:timer.NextElapseUSecRealtime,last:timer.LastTriggerUSec}}));
' "$ancestor_boundary" "$proc_root" "$serving_root" "$config_file" "${BASH_SOURCE[0]}" "$release_lib" \
		"$root_uid" "$root_gid" "$runtime_uid" "$$" "$stopped_expected_boot" "$1" "${BASHPID:-$$}"
}

stopped_current_common() {
	local snapshot
	operator_assert_unchanged || return 1
	proof maintenance-idle "$ancestor_boundary" >/dev/null || return 1
	migration_guard="$(proof migration-guard-descriptor "$serving_root")" || return 1
	migration_native_guard || return 1
	proof migration-permit-present "$serving_root" >/dev/null || return 1
	verified_markers check "$operator_expected_sha" || return 1
	snapshot="$(stopped_current_snapshot "$1")" || return 1
	printf '%s' "$snapshot" | "$node_bin" -e '
const fs=require("node:fs"),assert=require("node:assert/strict"),value=JSON.parse(fs.readFileSync(0,"utf8"));
assert.deepEqual(value.protected,JSON.parse(process.argv[1]).protected);
if(process.argv[2]==="admit")assert.deepEqual(value.timer,JSON.parse(process.argv[1]).timer,"timer changed before Gateway start");
' "$stopped_inputs" "$1" || return 1
}

stopped_current_admission() {
	local snapshot
	stopped_current_common admit || return 1
	snapshot="$(stopped_restored_snapshot "$operator_expected_pid")" || return 1
	[[ "$snapshot" == "$stopped_history" ]] || { fail 'stopped current execution history or cgroup changed'; return 1; }
	[[ "$(proof stopped-current-state "$operator_release" "$config_file" "$state_database" "$proc_root" stopped "$runtime_uid")" == "$stopped_stores" ]] || return 1
	printf '%s' "$session_witness" | proof session-preservation-verify "$operator_release" "$operator_release" "$state_database" >/dev/null || return 1
	# Recheck native ownership after the potentially lengthy preservation read.
	[[ "$(stopped_restored_snapshot "$operator_expected_pid")" == "$stopped_history" ]] || return 1
	stopped_current_common admit
}

stopped_current_receipt() {
	local phase="$1" code="$2"
	"$node_bin" -e '
const fs=require("node:fs"),assert=require("node:assert/strict"),[path,uid,gid]=process.argv.slice(1);
const held=fs.fstatSync(8),named=fs.lstatSync(path);
assert(held.isFile()&&named.isFile()&&held.nlink===1&&held.uid===Number(uid)&&held.gid===Number(gid)&&
  (held.mode&0o7777)===0o600&&held.dev===named.dev&&held.ino===named.ino,"operator receipt ownership changed");
' "$stopped_receipt_file" "$root_uid" "$root_gid" || return 1
	printf 'START_STOPPED_CURRENT phase=%s rc=%s sha=%s boot=%s last-pid=%s last-start-us=%s last-invocation=%s\n' \
		"$phase" "$code" "$operator_expected_sha" "$stopped_expected_boot" "$operator_expected_pid" \
		"$operator_expected_generation" "$stopped_expected_invocation" >&8 || return 1
	"$node_bin" -e 'require("node:fs").fsyncSync(8)' || return 1
}

stopped_current_wait_updater() {
	local deadline=$((SECONDS + verification_budget)) remaining updater state
	while :; do
		remaining=$((deadline - SECONDS))
		((remaining > 0)) || { fail 'restored timer updater did not settle within verification budget'; return 1; }
		updater="$(bounded "$remaining" "$systemctl_bin" show openclaw-hourly-update.service \
			--property=MainPID --property=ControlPID --property=Job --property=ActiveState --property=SubState)" || return 1
		remaining=$((deadline - SECONDS))
		((remaining > 0)) || { fail 'restored timer updater did not settle within verification budget'; return 1; }
		state="$(UPDATER_INPUT="$updater" bounded "$remaining" "$node_bin" -e '
const assert=require("node:assert/strict"),value=Object.create(null);
for(const line of process.env.UPDATER_INPUT.split("\n")) {
  const match=/^(MainPID|ControlPID|Job|ActiveState|SubState)=(.*)$/.exec(line);
  assert(match&&!Object.hasOwn(value,match[1]),"malformed restored timer updater state");value[match[1]]=match[2];
}
assert.equal(Object.keys(value).length,5);
assert(/^(0|[1-9][0-9]*)$/.test(value.MainPID)&&/^(0|[1-9][0-9]*)$/.test(value.ControlPID));
assert(/^(?:|0|[1-9][0-9]*)$/.test(value.Job));
const settled=value.ActiveState==="inactive"&&value.SubState==="dead"||value.ActiveState==="failed"&&value.SubState==="failed";
assert(settled||["active","activating","deactivating","reloading"].includes(value.ActiveState)&&/^[a-z][a-z0-9-]*$/.test(value.SubState));
process.stdout.write(settled&&value.MainPID==="0"&&value.ControlPID==="0"&&["","0"].includes(value.Job)?"settled":"pending");
')" || return 1
		[[ "$state" == settled ]] && return 0
		remaining=$((deadline - SECONDS))
		((remaining > 0)) || { fail 'restored timer updater did not settle within verification budget'; return 1; }
		sleep 1
	done
}

stopped_current_timer() {
	local snapshot state timer_code=0
	stopped_current_common finish || return 1
	recovery_jobs_settled || return 1
	snapshot="$(stopped_current_snapshot finish)" || return 1
	state="$(printf '%s' "$snapshot" | "$node_bin" -e 'process.stdout.write(JSON.parse(require("node:fs").readFileSync(0,"utf8")).timer.state)')" || return 1
	stopped_timer_binding="$(printf '%s' "$snapshot" | "$node_bin" -e '
const fs=require("node:fs"),assert=require("node:assert/strict"),timer=JSON.parse(fs.readFileSync(0,"utf8")).timer;
assert.deepEqual(timer,JSON.parse(process.argv[1]).timer,"timer activity changed outside the admitted recovery");
process.stdout.write(JSON.stringify(timer));
' "$stopped_inputs")" || return 1
	if [[ "$state" == inactive ]]; then
		((stopped_timer_attempted == 0)) || return 1
		stopped_timer_attempted=1
		stopped_current_receipt timer-intent 0 || return 1
		# The same enabled timer is the only scheduler. No reload, enable, or timestamp rewrite.
		bounded 30 "$systemctl_bin" start openclaw-hourly-update.timer --job-mode=fail || timer_code=$?
		stopped_current_receipt timer-command "$timer_code" || return 1
		((timer_code == 0)) || return 1
		# Persistent timers can immediately launch one catch-up invocation.
		stopped_current_wait_updater || return 1
		stopped_timer_binding=""
	fi
	stopped_timer_binding="$(stopped_current_observe_timer)"
}

stopped_current_observe_timer() {
	local snapshot
	snapshot="$(stopped_current_snapshot finish)" || return 1
	printf '%s' "$snapshot" | "$node_bin" -e '
const fs=require("node:fs"),assert=require("node:assert/strict"),value=JSON.parse(fs.readFileSync(0,"utf8"));
assert.deepEqual(value.protected,JSON.parse(process.argv[1]).protected);assert.equal(value.timer.state,"active");
assert(/^[a-f0-9]{32}$/.test(value.timer.invocation));assert(Date.parse(value.timer.next)>Date.now(),"timer has no future deadline");
if(process.argv[2])assert.deepEqual(value.timer,JSON.parse(process.argv[2]),"timer schedule changed during recovery");
fs.writeSync(8,JSON.stringify({timer:value.timer})+"\n");fs.fsyncSync(8);process.stdout.write(JSON.stringify(value.timer));
' "$stopped_inputs" "${stopped_timer_binding:-}"
}

stopped_current_finish() {
	local code="$1"
	if ((stopped_admitted && stopped_start_attempted)); then
		if ! recovery_jobs_settled; then
			stopped_current_receipt unknown-native-job 1 || :
			return 1
		fi
		stopped_current_timer || { stopped_current_receipt timer-partial 1 || :; return 1; }
	fi
	if ((stopped_receipt_open)); then
		if ((code == 0 && stopped_verified)); then
			stopped_current_final || { stopped_current_receipt verification-partial 1 || :; return 1; }
			stopped_current_receipt recovered 0 || return 1
			printf 'RECOVERED sha=%s action=verified-current-after-start-request native-start-attribution=unproven APPROVAL_DELTA=0 session-proof=original-stopped-state\n' "$operator_expected_sha"
		else
			stopped_current_receipt unaccepted "$code" || :
			return 1
		fi
	fi
	return "$code"
}

stopped_current_final() {
	local stores
	stopped_current_common finish || return 1
	stores="$(proof stopped-current-state "$operator_release" "$config_file" "$state_database" "$proc_root" live "$runtime_uid")" || return 1
	printf '%s' "$stores" | "$node_bin" -e '
const fs=require("node:fs"),assert=require("node:assert/strict"),current=JSON.parse(fs.readFileSync(0,"utf8"));
for(const original of JSON.parse(process.argv[1]))assert.deepEqual(current.find(entry=>entry.path===original.path),original);
' "$stopped_stores" || return 1
	recheck_generation system "$stopped_successor_pid" "$stopped_successor_generation" || return 1
	[[ "$(system_systemctl show openclaw-gateway.service --property=InvocationID --value)" == "$stopped_successor_invocation" &&
		"$(realpath "$proc_root/$stopped_successor_pid/cwd")" == "$operator_release" ]] || return 1
	recovery_jobs_settled || return 1
	stopped_current_observe_timer >/dev/null || return 1
	recheck_generation system "$stopped_successor_pid" "$stopped_successor_generation"
}

recover_stopped_current() {
	local info receipt_file start_code=0 invocation started_pid started_generation native_outcome
	operator_check_lock held || return 1
	[[ ! -e "$journal_file" && ! -L "$journal_file" ]] || { fail 'stopped current recovery refuses any activation journal'; return 1; }
	operator_release="$(read_pointer "$current_link")" || return 1
	[[ "$operator_release" == "$releases_root/$operator_expected_sha" ]] || return 1
	operator_pointers="$(operator_pointer_snapshot)" || return 1
	assert_protected_identity && assert_policy || return 1
	verified_markers check "$operator_expected_sha" || return 1
	info="$(proof validate-release "$operator_release" "$operator_expected_sha" "$root_uid" sealed)" || return 1
	stopped_history="$(stopped_restored_snapshot "$operator_expected_pid")" || return 1
	printf '%s' "$stopped_history" | "$node_bin" -e '
const fs=require("node:fs"),assert=require("node:assert/strict"),s=JSON.parse(fs.readFileSync(0,"utf8")).system;
assert.equal(s.ExecMainPID,process.argv[1]);assert.equal(s.ExecMainStartTimestampMonotonic,process.argv[2]);assert.equal(s.InvocationID,process.argv[3]);
' "$operator_expected_pid" "$operator_expected_generation" "$stopped_expected_invocation" || return 1
	stopped_inputs="$(stopped_current_snapshot admit)" || return 1
	stopped_stores="$(proof stopped-current-state "$operator_release" "$config_file" "$state_database" "$proc_root" stopped "$runtime_uid")" || return 1
	session_witness="$(proof session-preservation-witness "$operator_release" "$operator_release" "$state_database")" || return 1
	run_gateway "$operator_release" config validate --json >/dev/null || { fail 'stopped current configuration validation failed; no start requested'; return 1; }
	stopped_current_admission || return 1
	receipt_file="$serving_root/journal/operator-start-current-$stopped_expected_boot-$stopped_expected_invocation.log"
	stopped_receipt_file="$receipt_file"
	umask 077
	set -o noclobber
	exec 8>"$receipt_file" || { fail 'stopped current incident receipt is already spent'; return 1; }
	stopped_receipt_open=1
	printf '%s\n%s\n%s\n%s\n%s\n' "$stopped_inputs" "$stopped_history" "$session_witness" "$stopped_stores" "$info" | "$node_bin" -e '
const fs=require("node:fs"),crypto=require("node:crypto"),bytes=fs.readFileSync(0);
fs.writeSync(8,JSON.stringify({admissionSha256:crypto.createHash("sha256").update(bytes).digest("hex"),
  inputs:bytes.toString().trimEnd().split("\n").map(line=>JSON.parse(line)),pointers:process.argv[1]})+"\n");fs.fsyncSync(8);
' "$operator_pointers" || return 1
	stopped_current_admission || return 1
	stopped_current_receipt start-intent 0 || return 1
	proof sync-directory "$serving_root/journal" || return 1
	stopped_current_admission || return 1
	stopped_admitted=1
	stopped_start_attempted=1
	bounded 180 "$systemctl_bin" start openclaw-gateway.service --job-mode=fail || start_code=$?
	stopped_current_receipt start-command "$start_code" || return 1
	native_outcome="$(bounded 15 "$systemctl_bin" show openclaw-gateway.service --property=ActiveState --property=SubState \
		--property=MainPID --property=ControlPID --property=Job --property=ExecMainPID \
		--property=ExecMainStartTimestampMonotonic --property=InvocationID)" || return 1
	printf '%s\n' "$native_outcome" >&8 || return 1
	stopped_current_receipt native-outcome 0 || return 1
	recovery_jobs_settled || return 1
	((start_code == 0)) || return 1
	capture_service_generation system || return 1
	started_pid="$service_pid"; started_generation="$service_generation"
	invocation="$(system_systemctl show openclaw-gateway.service --property=InvocationID --value)" || return 1
	[[ "$invocation" =~ ^[a-f0-9]{32}$ && "$invocation" != "$stopped_expected_invocation" && "$started_pid" != "$operator_expected_pid" ]] || return 1
	stopped_successor_pid="$started_pid"; stopped_successor_generation="$started_generation"; stopped_successor_invocation="$invocation"
	printf 'successor pid=%s start=%s invocation=%s\n' "$started_pid" "$started_generation" "$invocation" >&8 || return 1
	stopped_current_receipt successor-bound 0 || return 1
	[[ "$(realpath "$proc_root/$started_pid/cwd")" == "$operator_release" ]] || return 1
	wait_for_live "$operator_release" "$info" system "$started_pid" "$started_generation" || return 1
	stopped_current_common finish || return 1
	recheck_generation system "$started_pid" "$started_generation" || return 1
	[[ "$(system_systemctl show openclaw-gateway.service --property=InvocationID --value)" == "$invocation" ]] || return 1
	stopped_current_receipt gateway-verified 0 || return 1
	stopped_verified=1
}

while (($#)); do
	case "$1" in
	--continue-frozen-release)
		[[ "$mode" == deploy && $# -ge 6 && -z "$requested_sha" && -z "$frozen_target_sha" ]] || fail 'frozen continuation requires TARGET_SHA MANIFEST_SHA256 CURRENT_SHA PID START'
		mode=continue-frozen; frozen_target_sha="$2"; frozen_manifest_sha="$3"
		frozen_predecessor_sha="$4"; frozen_predecessor_pid="$5"; frozen_predecessor_start="$6"
		shift 5
		;;
	--switch-runtime)
		[[ "$mode" == deploy && $# -ge 4 ]] || fail '--switch-runtime requires node|bun, absolute binary, and SHA-256'
		mode=runtime; runtime_kind="$2"; runtime_path="$3"; runtime_hash="$4"
		shift 3
		;;
	--handoff-import)
		[[ "$mode" == deploy && $# -ge 3 && "$3" =~ ^[a-f0-9]{64}$ ]] || fail 'handoff import requires MANIFEST SHA256'
		mode=handoff-import; handoff_manifest="$2"; handoff_manifest_hash="$3"; shift 2
		;;
	--handoff-verify-doctor)
		[[ "$mode" == deploy ]] || fail 'handoff Doctor verification cannot be combined with another action'
		mode=handoff-doctor
		;;
	--handoff-activate)
		[[ "$mode" == deploy && $# -ge 2 && "$2" =~ ^[a-f0-9]{64}$ ]] || fail 'handoff activation requires approved CONFIG_SHA256'
		mode=handoff-activate; handoff_config_hash="$2"; shift
		;;
	--handoff-export | --handoff-stop)
		[[ "$mode" == deploy && $# -ge 6 ]] || fail 'handoff export requires SOURCE_SHA PID START TARGET_MACHINE_ID TARGET_SHA'
		[[ "$1" != --handoff-stop ]] || handoff_stop_only=1
		mode=handoff-source; operator_expected_sha="$2"; operator_expected_pid="$3"; operator_expected_generation="$4"
		handoff_peer="$5"; handoff_target_sha="$6"; shift 5
		;;
	--prepare-release)
		[[ "$mode" == deploy && $# -ge 2 && -z "$requested_sha" && "$2" =~ ^[a-f0-9]{40}$ ]] || fail 'release preparation requires one exact official SHA'
		mode=prepare-release; requested_sha="$2"
		shift
		;;
	--repair-worker-bootstrap)
		[[ "$mode" == deploy && $# -ge 6 ]] || fail 'worker config repair requires current SHA, PID, start, config hash, and old setup hash'
		mode=worker-config
		operator_expected_sha="$2"; operator_expected_pid="$3"; operator_expected_generation="$4"
		worker_expected_config_hash="$5"; worker_expected_setup_hash="$6"
		shift 5
		;;
	--capture-steady-profile)
		[[ "$mode" == deploy && $# -ge 5 ]] || fail 'steady capture requires operation, current SHA, PID and process generation'
		mode=steady-profile
		profile_operation="$2"
		operator_expected_sha="$3"
		operator_expected_pid="$4"
		operator_expected_generation="$5"
		shift 4
		;;
	--operator-restart-current | --install-migration-guard)
		[[ "$mode" == deploy && $# -ge 4 ]] || fail 'bound action requires expected current SHA, PID, and process generation'
		if [[ "$1" == --operator-restart-current ]]; then mode=operator-restart; else mode=guard-install; fi
		operator_expected_sha="$2"
		operator_expected_pid="$3"
		operator_expected_generation="$4"
		shift 3
		;;
	--migrate)
		[[ "$mode" == deploy ]] || fail 'migration mode may be selected only once'
		mode=migrate
		;;
	--recover)
		[[ "$mode" == deploy ]] || fail 'recovery mode cannot be combined with another action'
		mode=recover
		;;
	--start-stopped-current)
		((start_stopped_current == 0)) && [[ $# -ge 6 ]] || fail 'stopped current recovery requires SHA BOOT_ID LAST_EXEC_PID LAST_EXEC_START_US LAST_INVOCATION'
		start_stopped_current=1
		operator_expected_sha="$2"; stopped_expected_boot="$3"; operator_expected_pid="$4"
		operator_expected_generation="$5"; stopped_expected_invocation="$6"
		shift 5
		;;
	--reconcile-config)
		[[ $# -ge 6 && -z "$reconcile_config_hash" && -n "$5" && -n "$6" ]] || fail '--reconcile-config requires SHA PID START CONFIG_SHA256 JOURNAL_SHA256 once'
		operator_expected_sha="$2"; operator_expected_pid="$3"; operator_expected_generation="$4"
		reconcile_config_hash="$5"; reconcile_journal_hash="$6"
		shift 5
		;;
	--reconcile-stopped-predecessor)
		[[ $# -ge 4 && -z "$reconcile_config_hash" && -n "$3" && -n "$4" ]] || fail '--reconcile-stopped-predecessor requires SHA CONFIG_SHA256 JOURNAL_SHA256 once'
		operator_expected_sha="$2"; reconcile_config_hash="$3"; reconcile_journal_hash="$4"
		reconcile_stopped=1
		shift 3
		;;
	--cleanup)
		[[ "$mode" == deploy ]] || fail 'cleanup mode cannot be combined with another action'
		mode=cleanup
		;;
	--sha)
		shift
		[[ $# -gt 0 && -z "$requested_sha" ]] || fail '--sha requires one exact value'
		requested_sha="$1"
		;;
	--offline-doctor-repair)
		((offline_doctor_repair == 0)) || fail 'offline Doctor may be selected only once'
		offline_doctor_repair=1
		;;
	--arm-steady-profile)
		((arm_steady_profile == 0)) || fail 'steady profiling may be armed only once'
		arm_steady_profile=1
		;;
	--capture-startup-profile)
		((capture_startup_profile == 0)) || fail 'startup profiling may be selected only once'
		capture_startup_profile=1
		;;
	--allow-cpu-degraded)
		((allow_cpu_degraded == 0)) || fail 'CPU waiver may be selected only once'
		allow_cpu_degraded=1
		;;
	--interrupt-after-drain)
		((interrupt_after_drain == 0)) || fail 'interruption intent may be selected only once'
		interrupt_after_drain=1
		;;
	--waive-stale-terminal-persistence)
		# only meaningful with --interrupt-after-drain; receipted as TERMINAL_PERSISTENCE_WAIVED.
		waive_terminal_persistence=1
		;;
	*) fail 'usage: openclaw-release-deploy [--continue-frozen-release TARGET_SHA MANIFEST_SHA256 CURRENT_SHA PID START|--prepare-release OFFICIAL_SHA|--switch-runtime node|bun BINARY SHA256|--migrate|--recover|--cleanup] [--reconcile-config SHA PID START CONFIG_SHA256 JOURNAL_SHA256|--reconcile-stopped-predecessor SHA CONFIG_SHA256 JOURNAL_SHA256] [--repair-worker-bootstrap SHA PID START CONFIG_SHA256 OLD_SETUP_SHA256] [--sha OFFICIAL_MAIN_SHA] [--interrupt-after-drain] [--offline-doctor-repair] [--allow-cpu-degraded] [--capture-startup-profile|--arm-steady-profile] [--capture-steady-profile OPERATION SHA PID START]' ;;
	esac
	shift
done

if [[ "$mode" == continue-frozen ]]; then
	[[ -z "$requested_sha" && -z "$reconcile_config_hash" && "$start_stopped_current" == 0 &&
		"$frozen_target_sha" =~ ^[a-f0-9]{40}$ && "$frozen_manifest_sha" =~ ^[a-f0-9]{64}$ &&
		"$frozen_predecessor_sha" =~ ^[a-f0-9]{40}$ && "$frozen_target_sha" != "$frozen_predecessor_sha" &&
		"$frozen_predecessor_pid" =~ ^[1-9][0-9]*$ && "$frozen_predecessor_start" =~ ^[1-9][0-9]*$ ]] ||
		fail 'frozen continuation requires exact candidate, manifest and predecessor bindings without another action'
	operator_check_lock before
fi

if ((start_stopped_current)); then
	[[ "$mode" == recover && -z "$requested_sha" && -z "$reconcile_config_hash" &&
		"$operator_expected_sha" =~ ^[a-f0-9]{40}$ && "$stopped_expected_boot" =~ ^[a-f0-9]{8}(-[a-f0-9]{4}){3}-[a-f0-9]{12}$ &&
		"$operator_expected_pid" =~ ^[1-9][0-9]*$ && "$operator_expected_generation" =~ ^[1-9][0-9]*$ &&
		"$stopped_expected_invocation" =~ ^[a-f0-9]{32}$ ]] || fail 'stopped current recovery requires --recover and exact incident bindings without another action'
	operator_check_lock before
fi

if [[ -n "$reconcile_config_hash" ]]; then
	[[ "$mode" == recover && -z "$requested_sha" && "$operator_expected_sha" =~ ^[a-f0-9]{40}$ &&
		("$reconcile_stopped" == 1 || ("$operator_expected_pid" =~ ^[1-9][0-9]*$ && "$operator_expected_generation" =~ ^[1-9][0-9]*$)) &&
		"$reconcile_config_hash" =~ ^[a-f0-9]{64}$ && "$reconcile_journal_hash" =~ ^[a-f0-9]{64}$ ]] ||
		fail 'config reconciliation requires --recover and exact SHA/PID/generation/config/journal bindings, without --sha'
	operator_check_lock before
fi

((offline_doctor_repair == 0)) || [[ ("$mode" == deploy || "$mode" == continue-frozen) && "$interrupt_after_drain" == 1 &&
  "$capture_startup_profile" == 0 && "$arm_steady_profile" == 0 ]] ||
  fail '--offline-doctor-repair requires a fenced system deployment with --interrupt-after-drain, without profiling'

((capture_startup_profile + arm_steady_profile <= 1)) || fail 'select only one profiling mode'
((capture_startup_profile + arm_steady_profile == 0)) || [[ "$mode" == deploy ]] ||
	fail 'startup profiling is only valid for an ordinary deployment without migration'

((allow_cpu_degraded == 0)) || [[ "$mode" == deploy || "$mode" == continue-frozen || "$mode" == recover || "$mode" == runtime || "$mode" == handoff-source || "$mode" == handoff-activate ]] ||
	fail '--allow-cpu-degraded is only valid for deployment, runtime switching, recovery, or handoff'
((interrupt_after_drain == 0)) || [[ "$mode" == deploy || "$mode" == continue-frozen || "$mode" == worker-config || "$mode" == runtime || "$mode" == handoff-source ]] ||
	fail '--interrupt-after-drain is only valid for deployment, runtime switching, worker config repair, or source handoff'

if [[ "$mode" == operator-restart || "$mode" == guard-install || "$mode" == steady-profile || "$mode" == worker-config || "$mode" == handoff-source ]]; then
	[[ -z "$requested_sha" && "$operator_expected_sha" =~ ^[a-f0-9]{40}$ &&
		"$operator_expected_pid" =~ ^[1-9][0-9]*$ && "$operator_expected_generation" =~ ^[1-9][0-9]*$ ]] ||
		fail 'operator restart requires exact current SHA/PID/generation and cannot combine with --sha'
	operator_check_lock before
fi

if [[ "$mode" == handoff-source ]]; then
	[[ "$handoff_peer" =~ ^[a-f0-9]{32}$ && "$handoff_target_sha" =~ ^[a-f0-9]{40}$ ]] || fail 'handoff target identity is invalid'
fi

[[ "$mode" != handoff-* || -z "$requested_sha" ]] || fail 'handoff modes cannot be combined with --sha'

if [[ "$mode" == worker-config ]]; then
	[[ "$worker_expected_config_hash" =~ ^[a-f0-9]{64}$ && "$worker_expected_setup_hash" =~ ^[a-f0-9]{64}$ ]] ||
		fail 'worker config repair requires exact config and old setup SHA-256 bindings'
fi
if [[ "$mode" == runtime ]]; then
	[[ -z "$requested_sha" && ("$runtime_kind" == node || "$runtime_kind" == bun) && "$runtime_hash" =~ ^[a-f0-9]{64}$ ]] || fail 'runtime switch requires a pinned binary and cannot select a release'
	operator_check_lock before
fi

exec 9>"$lock_file"
flock -n 9 || {
	if [[ "$mode" == deploy ]]; then
		# Maintenance joins its descendants under this same lock; let it settle.
		printf 'DEPLOY_LOCK_WAIT waiting up to 180 seconds for deployment or maintenance\n'
		flock -w 180 -E 75 9 || {
			lock_exit=$?
			printf 'DEPLOY_LOCK_UNAVAILABLE exit=%s; retry after the current owner finishes\n' "$lock_exit" >&2
			exit "$lock_exit"
		}
	elif [[ "$mode" == operator-restart || "$mode" == guard-install || "$mode" == steady-profile || "$mode" == worker-config || "$mode" == runtime || "$mode" == prepare-release || "$mode" == continue-frozen || "$mode" == handoff-* || -n "$reconcile_config_hash" || "$start_stopped_current" == 1 ]]; then
		fail 'bound maintenance action refused: canonical deployment lock is busy'
		exit 1
	else
		printf 'SKIP another release deployment is running\n'
		exit 0
	fi
}
proof maintenance-idle "$ancestor_boundary" >/dev/null ||
	fail 'scheduled maintenance has not settled; deployment admission refused'
if [[ -n "$reconcile_config_hash" ]]; then operator_check_lock held; fi
if [[ "$mode" != handoff-* && ( -e "$journal_file" || -L "$journal_file" ) ]]; then
	handoff_pending="$(proof host-handoff status "$serving_root")"
	[[ "$handoff_pending" != SOURCE_* && "$handoff_pending" != TARGET_* ]] ||
		fail 'cross-host handoff is retained; resume its matching --handoff action, never ordinary deployment or rollback'
fi
trap on_exit EXIT

if [[ "$mode" == steady-profile ]]; then
	operator_check_lock held
	[[ -z "${NODE_OPTIONS:-}" ]] || fail 'steady capture owner refuses inherited Node options'
	# Exec retains one PID/start/FD9 owner until the library revokes its permit and closes the lock.
	exec "$node_bin" --no-warnings "$release_lib" steady-profile "$profile_operation" "$serving_root" \
		"$operator_expected_sha" "$operator_expected_pid" "$operator_expected_generation"
fi

audit_root
load_gateway_runtime
if ((capture_startup_profile || arm_steady_profile)); then proof gateway-runtime profile "$serving_root" >/dev/null; fi
if [[ "$mode" == handoff-* ]]; then
	for timer in openclaw-hourly-update.timer openclaw-nightly-update.timer; do
		enabled="$(system_systemctl is-enabled "$timer" 2>/dev/null || true)"
		active="$(system_systemctl is-active "$timer" 2>/dev/null || true)"
		[[ ("$enabled" == disabled || "$enabled" == not-found) && "$active" == inactive ]] ||
			fail 'host handoff requires disabled inactive deployment timers'
	done
fi
if [[ "$mode" == handoff-source ]]; then
	operator_check_lock held
	proof migration-publication-recover "$serving_root" >/dev/null
	handoff_source
	exit 0
fi
if [[ "$mode" == handoff-* ]]; then
	proof migration-publication-recover "$serving_root" >/dev/null
	handoff_target
	exit 0
fi
if [[ "$mode" == prepare-release ]]; then
	prepare_release
	exit 0
fi
if [[ "$mode" == continue-frozen ]]; then
	continue_frozen_release
	exit 0
fi
if ((start_stopped_current)); then
	recover_stopped_current
	exit 0
fi
profile_maintenance
if ((capture_startup_profile || arm_steady_profile)) && [[ -e "$journal_file" || -L "$journal_file" ]]; then
	fail 'startup profiling refuses an existing deployment journal; settle recovery first'
fi
proof migration-publication-recover "$serving_root" >/dev/null
if [[ "$mode" == worker-config ]]; then
	operator_check_lock held
	if repair_worker_bootstrap; then
		exit 0
	else
		worker_config_exit=$?
		if [[ -n "$worker_config_token" && "$(proof journal-field "$journal_file" request 2>/dev/null)" == "$worker_config_token" ]]; then
			recover_worker_config || printf 'WORKER_CONFIG_RETAINED recovery=explicit-owner journal-and-backup-retained\n' >&2
		fi
		exit "$worker_config_exit"
	fi
fi
if [[ "$mode" == guard-install ]]; then
	operator_check_lock held
	install_migration_guard_current
	exit 0
fi
if [[ "$mode" == operator-restart ]]; then
	migration_check_existing_guard
	operator_restart_current
	exit 0
fi
if [[ "$mode" == runtime ]]; then
	operator_check_lock held
	[[ ! -e "$journal_file" && ! -L "$journal_file" ]] || fail 'runtime switch refuses a pending activation; use --recover first'
	assert_exclusive_owner system
	previous_release="$(read_pointer "$current_link")"
	previous_info="$(proof validate-release "$previous_release" "$(basename "$previous_release")" "$root_uid" sealed)"
	candidate_release="$previous_release"; candidate_info="$previous_info"
	runtime_target="$(proof gateway-runtime target "$serving_root" "$runtime_kind" "$runtime_path" "$runtime_hash")"
	runtime_path="$(printf '%s' "$runtime_target" | "$node_bin" -e 'let s="";process.stdin.on("data",x=>s+=x);process.stdin.on("end",()=>process.stdout.write(JSON.parse(s).executable));')"
	(cd /; run_runtime env -i HOME="$runtime_home" PATH=/usr/bin:/bin "$runtime_path" -e 'const{DatabaseSync}=require("node:sqlite");const db=new DatabaseSync(":memory:");console.log(JSON.stringify({node:process.versions.node,bun:process.versions.bun,sqlite:db.prepare("select sqlite_version() as version").get().version}));db.close();') | proof gateway-runtime probe "$serving_root" "$runtime_target" >/dev/null
	capture_generation system
	proof gateway-runtime process "$serving_root" "$gateway_runtime_snapshot" "$proc_root" "$service_pid" >/dev/null
	if [[ "$(proof gateway-runtime same "$serving_root" "$runtime_target")" == SAME ]]; then
		wait_for_live "$previous_release" "$previous_info" system
		printf 'SUCCESS sha=%s action=runtime runtime=%s restart=0\n' "$(basename "$previous_release")" "$runtime_kind"
		exit 0
	fi
	activate_release system
	exit 0
fi
if [[ "$mode" == recover ]]; then
	recover_journal
	exit 0
fi
if [[ "$mode" == cleanup ]]; then
	if [[ -e "$journal_file" ]]; then
		proof journal-read "$journal_file" >/dev/null ||
			fail 'cleanup cannot inspect an ambiguous in-flight deployment journal'
	fi
	cleanup_releases
	exit 0
fi
recover_journal
((worker_config_recovered == 0)) || exit 0

assert_protected_identity
assert_policy

if [[ "$mode" == migrate ]]; then
	[[ -z "$requested_sha" ]] || fail 'migration always freezes the exact serving baseline SHA'
	assert_exclusive_owner migration-user
	[[ -L "$legacy_current" ]] || fail 'legacy user Gateway pointer is missing'
	legacy_release="$(realpath "$legacy_current")"
	legacy_sha="$("$node_bin" -e 'process.stdout.write(JSON.parse(require("node:fs").readFileSync(process.argv[1],"utf8")).commit)' \
		"$legacy_release/dist/build-info.json")"
	[[ "$legacy_sha" =~ ^[a-f0-9]{40}$ ]] || fail 'legacy serving SHA is not exact'
	assert_scheduler_owner "$legacy_release"
	freeze_origin_main
	bounded_build "$fetch_budget" git -C "$mirror" merge-base --is-ancestor "$legacy_sha" "$frozen_main" ||
		fail 'legacy serving SHA is not proven to be in official origin/main history'
	candidate_release="$releases_root/$legacy_sha"
	candidate_info="$(publish_release "$legacy_sha" "$legacy_release")"
	previous_release="$candidate_release"
	previous_info="$candidate_info"
	if [[ -e "$current_link" || -L "$current_link" ]]; then
		[[ "$(read_pointer "$current_link")" == "$candidate_release" ]] ||
			fail 'preinstalled migration pointer does not select the exact serving baseline'
	fi
	activate_release migration-user
	exit 0
fi

assert_exclusive_owner system
previous_release="$(read_pointer "$current_link")" || fail 'current release pointer is unsafe'
previous_sha="$(basename "$previous_release")"
previous_info="$(proof validate-release "$previous_release" "$previous_sha" "$root_uid" sealed)"
assert_scheduler_owner "$previous_release"
freeze_origin_main
if [[ -n "$requested_sha" && "$requested_sha" != "$frozen_main" ]]; then
	fail 'supplied SHA is not the exact frozen official origin/main commit'
fi
target_sha="$frozen_main"
if [[ "$target_sha" == "$previous_sha" ]]; then
	((offline_doctor_repair == 0)) || fail 'offline Doctor requires a distinct approved candidate; current-SHA repair is not supported'
	((capture_startup_profile + arm_steady_profile == 0)) || fail 'startup profiling requires a new release; current-SHA restart is not supported'
	if wait_for_live "$previous_release" "$previous_info" system; then
		cleanup_releases
		printf 'SUCCESS sha=%s action=current restart=0\n' "$target_sha"
		exit 0
	fi
	fail 'current release is unhealthy; refusing an unattended restart'
fi
candidate_release="$releases_root/$target_sha"
# Reclaim headroom even when publication or activation will fail or defer.
# Even an old published candidate stays protected until its exact attempt settles.
cleanup_releases "$target_sha"
candidate_info="$(publish_release "$target_sha" "$previous_release")"
activate_prepared_release
