#!/usr/bin/env bash set -euo pipefail readonly EX_USAGE=64 readonly EX_UNAVAILABLE=69 readonly MAX_MANIFEST_BYTES=32768 readonly MAX_EVIDENCE_BYTES=131072 readonly FIELD_MILESTONE="logged-smoke-fixture" readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" readonly REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd -P)" readonly MANIFEST_SCHEMA="${SCRIPT_DIR}/fixtures/iop-agent-smoke-manifest.schema.json" daemon_pid="" daemon_start_token="" daemon_log="" usage() { cat <<'EOF' Usage: e2e-iop-agent-logged-smoke.sh --preflight-only e2e-iop-agent-logged-smoke.sh --self-test e2e-iop-agent-logged-smoke.sh --validate-manifest FILE e2e-iop-agent-logged-smoke.sh \ --binary FILE \ --repo-config FILE \ --local-config FILE \ --provider-catalog FILE \ --project-a DIRECTORY \ --project-b DIRECTORY \ --expected-head GIT_SHA \ --output DIRECTORY The full mode is destructive only inside the two explicitly registered clean project clones and the device-local runtime roots declared by --local-config. It requires Darwin, jq, a clean exact source revision, authenticated provider CLIs, two distinct clean clones, and no pre-existing daemon process or socket. Raw command output remains under --output. manifest.json contains only bounded, allow-listed, path-redacted evidence. EOF } fail() { printf 'logged-smoke: %s\n' "$*" >&2 exit 1 } host_gate() { local os_name os_name="$(uname -s)" if [[ "${os_name}" != "Darwin" ]]; then printf 'logged-smoke: Darwin host required; observed %s before provider login or process launch\n' "${os_name}" >&2 exit "${EX_UNAVAILABLE}" fi } sha256_file() { shasum -a 256 "$1" | awk '{print "sha256:" $1}' } sha256_text() { printf '%s' "$1" | shasum -a 256 | awk '{print "sha256:" $1}' } canonical_directory() { [[ -d "$1" ]] || return 1 (cd "$1" && pwd -P) } canonical_file() { [[ -f "$1" ]] || return 1 local directory directory="$(cd "$(dirname "$1")" && pwd -P)" printf '%s/%s\n' "${directory}" "$(basename "$1")" } process_start_token() { local token token="$(ps -o lstart= -p "$1" 2>/dev/null | awk '{$1=$1; print}')" if [[ -n "${token}" ]]; then printf 'ps:%s\n' "${token}" fi } cleanup_owned_daemon() { if [[ -z "${daemon_pid}" || -z "${daemon_start_token}" ]]; then return 0 fi local current current="$(process_start_token "${daemon_pid}" || true)" if [[ "${current}" != "${daemon_start_token}" ]]; then printf 'logged-smoke: skip cleanup for PID %s because its start identity changed\n' "${daemon_pid}" >&2 daemon_pid="" daemon_start_token="" return 0 fi kill -TERM "${daemon_pid}" 2>/dev/null || true local waited=0 while kill -0 "${daemon_pid}" 2>/dev/null; do if (( waited >= 100 )); then current="$(process_start_token "${daemon_pid}" || true)" if [[ "${current}" == "${daemon_start_token}" ]]; then kill -KILL "${daemon_pid}" 2>/dev/null || true fi break fi sleep 0.1 waited=$((waited + 1)) done wait "${daemon_pid}" 2>/dev/null || true daemon_pid="" daemon_start_token="" } crash_owned_daemon_preserving_invocations() { [[ -n "${daemon_pid}" && -n "${daemon_start_token}" ]] || fail "owned daemon identity is unavailable for restart crash" local current current="$(process_start_token "${daemon_pid}" || true)" [[ "${current}" == "${daemon_start_token}" ]] || fail "owned daemon identity changed before restart crash" kill -KILL "${daemon_pid}" wait "${daemon_pid}" 2>/dev/null || true daemon_pid="" daemon_start_token="" if [[ -S "${daemon_socket}" ]]; then rm "${daemon_socket}" fi [[ ! -e "${daemon_socket}" ]] || fail "owned daemon socket remained after restart crash" } diagnose_failure() { local status=$? if (( status != 0 )) && [[ -n "${daemon_log}" && -f "${daemon_log}" ]]; then printf '%s\n' '--- bounded daemon diagnostic (last 80 lines) ---' >&2 tail -n 80 "${daemon_log}" >&2 || true fi cleanup_owned_daemon exit "${status}" } trap diagnose_failure EXIT trap 'exit 130' INT trap 'exit 143' TERM validate_evidence_shape() { local kind="$1" local locator="$2" local evidence_file="$3" local manifest="$4" local filter="" case "${kind}:${locator}" in source_build:build-evidence.json) filter=' . as $e | $manifest as $m | type == "object" and exact_keys(["head","source_tree","declared_binary_sha256","rebuilt_binary_sha256","provider_smoke_sha256","schema_sha256"]) and $e.head == $m.source.head and $e.source_tree == $m.source.source_tree and $e.declared_binary_sha256 == $m.source.binary_sha256 and $e.rebuilt_binary_sha256 == $m.source.rebuilt_binary_sha256 and $e.schema_sha256 == $m.source.schema_sha256 and ($e.provider_smoke_sha256 | digest)' ;; discovery_quota:discovery-quota.json) filter=' type == "object" and exact_keys(["profiles"]) and (.profiles | type == "array" and length >= 1 and length <= 16 and all(.[]; type == "object" and exact_keys(["profile_id_hash","discovery","quota_status","output_sha256"]) and (.profile_id_hash | digest) and .discovery == "ready" and .quota_status == "observed" and (.output_sha256 | digest)))' ;; preview:preview-evidence.json) filter=' type == "object" and exact_keys(["project_a","project_b","selected","unblocked"]) and (.project_a | digest) and (.project_b | digest) and .selected == true and .unblocked == true' ;; state_transition:start-evidence.json) filter=' type == "object" and exact_keys(["start_a","start_b","running_state"]) and (.start_a | digest) and (.start_b | digest) and (.running_state | digest)' ;; cancellation:cancellation-evidence.json) filter=' type == "object" and exact_keys(["stopped_state_sha256","cancelled_process_sha256","sibling_process_sha256","cancelled_process_live","sibling_process_live"]) and (.stopped_state_sha256 | digest) and (.cancelled_process_sha256 | digest) and (.sibling_process_sha256 | digest) and .cancelled_process_live == false and .sibling_process_live == true' ;; invocation_locator:locator-a.json|invocation_locator:locator-b.json) filter=' type == "object" and exact_keys(["snapshots"]) and (.snapshots | type == "array" and length >= 2 and length <= 4 and all(.[]; type == "object" and exact_keys(["project_id_hash","state_revision","project_status","invocations"]) and (.project_id_hash | digest) and (.state_revision | type == "string" and test("^[0-9]+$")) and (.project_status | state) and (.invocations | type == "array" and length >= 1 and length <= 16 and all(.[]; type == "object" and exact_keys(["attempt","dispatch_ordinal","state","locators"]) and (.attempt | type == "string" and test("^[0-9]+$")) and (.dispatch_ordinal | type == "string" and test("^[0-9]+$")) and (.state | type == "string" and length >= 1 and length <= 64) and (.locators | type == "array" and length >= 1 and length <= 2 and any(.[]; .kind == "process") and all(.[]; type == "object" and exact_keys(["kind","revision"]) and (.kind == "process" or .kind == "session") and (.revision | digest)))))))' ;; restart:restart-evidence.json) filter=' type == "object" and exact_keys(["project_id_hash","pre","post","pre_process_live","post_process_live","same_attempt","same_process_locator","same_process_identity","duplicate_invocation"]) and (.project_id_hash | digest) and (.pre | restart_snapshot) and (.post | restart_snapshot) and (.pre.state_revision | tonumber) < (.post.state_revision | tonumber) and .pre.attempt_id_sha256 == .post.attempt_id_sha256 and .pre.process_locator_revision == .post.process_locator_revision and .pre.process_identity_sha256 == .post.process_identity_sha256 and .pre_process_live == true and .post_process_live == true and .same_attempt == true and .same_process_locator == true and .same_process_identity == true and .duplicate_invocation == false' ;; project_log:project-log-a.json|project_log:project-log-b.json) filter=' type == "object" and exact_keys(["project_id_hash","logs"]) and (.project_id_hash | digest) and (.logs | type == "array" and length >= 1 and length <= 16 and all(.[]; type == "object" and exact_keys(["archive_ordinal","event_log_sha256","timeline_sha256"]) and (.archive_ordinal | type == "string" and test("^[0-9]+$")) and (.event_log_sha256 | digest) and (.timeline_sha256 | digest)))' ;; terminal_archive:archive-a.json|terminal_archive:archive-b.json) filter=' type == "object" and exact_keys(["project_id_hash","archives"]) and (.project_id_hash | digest) and (.archives | type == "array" and length >= 1 and length <= 16 and all(.[]; type == "object" and exact_keys(["archive_ordinal","record_count","checksum","terminal","manifest_sha256","event_log_sha256","timeline_sha256"]) and (.archive_ordinal | type == "string" and test("^[0-9]+$")) and (.record_count | type == "number" and . > 0) and (.checksum | digest) and .terminal == true and (.manifest_sha256 | digest) and (.event_log_sha256 | digest) and (.timeline_sha256 | digest)))' ;; terminal_archive:terminal-evidence.json) filter=' type == "object" and exact_keys(["status_a_sha256","status_b_sha256","archive_a_sha256","archive_b_sha256","terminal_status"]) and (.status_a_sha256 | digest) and (.status_b_sha256 | digest) and (.archive_a_sha256 | digest) and (.archive_b_sha256 | digest) and .terminal_status == "completed"' ;; *) fail "evidence kind/locator pair is not allow-listed: ${kind}:${locator}" ;; esac jq -e --slurpfile manifest "${manifest}" " def digest: type == \"string\" and test(\"^sha256:[0-9a-f]{64}$\"); def exact_keys(\$allowed): (keys | sort) == (\$allowed | sort); def state: . == \"observed\" or . == \"started\" or . == \"running\" or . == \"stopped\" or . == \"blocked\" or . == \"completed\" or . == \"terminal_deferred\"; def restart_snapshot: type == \"object\" and exact_keys([\"state_revision\",\"attempt_id_sha256\",\"process_locator_revision\",\"process_identity_sha256\"]) and (.state_revision | type == \"string\" and test(\"^[0-9]+$\")) and (.attempt_id_sha256 | digest) and (.process_locator_revision | digest) and (.process_identity_sha256 | digest); \$manifest[0] as \$manifest | ${filter} " "${evidence_file}" >/dev/null || fail "evidence file violates its ${kind} shape: ${locator}" } validate_evidence_file() { local manifest_dir="$1" local kind="$2" local locator="$3" local expected_sha256="$4" local manifest="$5" [[ "${locator}" == "$(basename "${locator}")" ]] || fail "evidence locator escapes the manifest directory: ${locator}" local evidence_file="${manifest_dir}/${locator}" [[ ! -L "${evidence_file}" ]] || fail "evidence locator is a symlink: ${locator}" [[ -f "${evidence_file}" ]] || fail "evidence locator is not a regular file: ${locator}" local size size="$(wc -c <"${evidence_file}" | tr -d ' ')" (( size > 0 && size <= MAX_EVIDENCE_BYTES )) || fail "evidence ${locator} size ${size} is outside 1..${MAX_EVIDENCE_BYTES} bytes" if LC_ALL=C grep -Eiq \ 'authorization|bearer|api[_-]?key|credential|secret|token|/Users/|/home/|\\\\Users\\\\' \ "${evidence_file}"; then fail "evidence contains a forbidden key or value: ${locator}" fi jq -e . "${evidence_file}" >/dev/null || fail "evidence is not valid JSON: ${locator}" [[ "$(sha256_file "${evidence_file}")" == "${expected_sha256}" ]] || fail "evidence digest mismatch: ${locator}" validate_evidence_shape "${kind}" "${locator}" "${evidence_file}" "${manifest}" } validate_manifest() { local manifest="$1" [[ -f "${MANIFEST_SCHEMA}" ]] || fail "missing manifest schema: ${MANIFEST_SCHEMA}" [[ ! -L "${manifest}" ]] || fail "manifest must not be a symlink" [[ -f "${manifest}" ]] || fail "manifest is not a regular file" command -v jq >/dev/null 2>&1 || fail "jq is required for manifest validation" local size size="$(wc -c <"${manifest}" | tr -d ' ')" (( size > 0 && size <= MAX_MANIFEST_BYTES )) || fail "manifest size ${size} is outside 1..${MAX_MANIFEST_BYTES} bytes" if LC_ALL=C grep -Eiq \ 'authorization|bearer|api[_-]?key|credential|secret|token|/Users/|/home/|\\\\Users\\\\' \ "${manifest}"; then fail "manifest contains a forbidden key or value" fi jq -e ' def digest: type == "string" and test("^sha256:[0-9a-f]{64}$"); def exact_keys($allowed): (keys | sort) == ($allowed | sort); def state: . == "observed" or . == "started" or . == "running" or . == "stopped" or . == "blocked" or . == "completed" or . == "terminal_deferred"; def evidence($root; $kind; $digest): any($root.evidence.records[]; .kind == $kind and .sha256 == $digest); def verified($root; $kind): type == "object" and exact_keys(["status","evidence_sha256"]) and .status == "verified" and (.evidence_sha256 | digest) and evidence($root; $kind; .evidence_sha256); def index_of($value): to_entries | map(select(.value == $value) | .key) | if length == 0 then -1 else .[0] end; def completed_absorbing: (index_of("completed")) as $completed | $completed >= 0 and all(.[$completed:][]; . == "completed") and (index_of("blocked")) == -1 and (index_of("terminal_deferred")) == -1; . as $root | type == "object" and exact_keys(["schema_version","run_id","source","environment","lifecycle","projects","evidence"]) and .schema_version == "2" and (.run_id | digest) and (.source | type == "object" and exact_keys(["head","source_tree","binary_sha256","rebuilt_binary_sha256","binary_build_head","repo_config_sha256","local_config_sha256","provider_catalog_sha256","build_evidence_sha256","schema_sha256"]) and (.head | test("^[0-9a-f]{40,64}$")) and (.source_tree | test("^[0-9a-f]{40,64}$")) and .binary_build_head == .head and .rebuilt_binary_sha256 == .binary_sha256 and (.binary_sha256 | digest) and (.build_evidence_sha256 | digest) and evidence($root; "source_build"; .build_evidence_sha256) and (.schema_sha256 | digest) and .schema_sha256 == $root.evidence.schema_sha256 and (.repo_config_sha256 | digest) and (.local_config_sha256 | digest) and (.provider_catalog_sha256 | digest)) and (.environment | type == "object" and exact_keys(["os","arch"]) and .os == "darwin" and (.arch == "arm64" or .arch == "amd64")) and (.lifecycle | type == "object" and exact_keys(["discovery","preview","manual_start","cancellation","sibling_continuation","new_invocation","restart_recovery","terminal_completion"]) and (.discovery | verified($root; "discovery_quota")) and (.preview | verified($root; "preview")) and (.manual_start | verified($root; "state_transition")) and (.cancellation | verified($root; "cancellation")) and (.sibling_continuation | verified($root; "cancellation")) and (.new_invocation | verified($root; "invocation_locator")) and (.restart_recovery | verified($root; "restart")) and (.terminal_completion | verified($root; "terminal_archive"))) and (.projects | type == "array" and length == 2 and ([.[].slot] | sort) == ["a","b"] and all(.[]; type == "object" and exact_keys(["slot","project_id_hash","workspace_revision","status_trace","terminal_status","initial_invocation_sha256","post_restart_invocation_sha256","locator_evidence_sha256","project_log_sha256","terminal_archive_sha256"]) and (.project_id_hash | digest) and (.workspace_revision | test("^[0-9a-f]{40,64}$")) and (.status_trace | type == "array" and length >= 3 and length <= 16 and all(.[]; state) and completed_absorbing) and (.terminal_status == "completed") and (.initial_invocation_sha256 | digest) and (.post_restart_invocation_sha256 | digest) and (.locator_evidence_sha256 | digest) and (.project_log_sha256 | digest) and (.terminal_archive_sha256 | digest) and evidence($root; "invocation_locator"; .locator_evidence_sha256) and evidence($root; "project_log"; .project_log_sha256) and evidence($root; "terminal_archive"; .terminal_archive_sha256))) and ((.projects[] | select(.slot == "a")) as $a | ($a.status_trace | index_of("running")) >= 0 and ($a.status_trace | index_of("stopped")) > ($a.status_trace | index_of("running")) and ($a.status_trace | index_of("completed")) > ($a.status_trace | index_of("stopped")) and $a.initial_invocation_sha256 != $a.post_restart_invocation_sha256) and ((.projects[] | select(.slot == "b")) as $b | ($b.status_trace | index_of("running")) >= 0 and ($b.status_trace | index_of("stopped")) == -1 and ($b.status_trace | index_of("completed")) > ($b.status_trace | index_of("running")) and $b.initial_invocation_sha256 == $b.post_restart_invocation_sha256) and (.evidence | type == "object" and exact_keys(["manifest_locator","schema_sha256","records"]) and .manifest_locator == "manifest.json" and (.schema_sha256 | digest) and (.records | type == "array" and length == 13 and ([.[].locator] | unique | length) == length and all(.[]; type == "object" and exact_keys(["kind","locator","sha256"]) and (.kind == "source_build" or .kind == "discovery_quota" or .kind == "preview" or .kind == "state_transition" or .kind == "cancellation" or .kind == "invocation_locator" or .kind == "restart" or .kind == "project_log" or .kind == "terminal_archive") and (.locator | test("^[a-z0-9][a-z0-9._-]{0,127}\\.json$")) and (.sha256 | digest)))) ' "${manifest}" >/dev/null || fail "manifest violates the bounded allow-list contract" local manifest_dir manifest_dir="$(cd "$(dirname "${manifest}")" && pwd -P)" [[ "$(jq -r '.evidence.schema_sha256' "${manifest}")" == "$(sha256_file "${MANIFEST_SCHEMA}")" ]] || fail "manifest schema digest differs from the tracked schema" jq -e ' [.evidence.records[] | {kind,locator}] == [ {kind:"source_build",locator:"build-evidence.json"}, {kind:"discovery_quota",locator:"discovery-quota.json"}, {kind:"preview",locator:"preview-evidence.json"}, {kind:"state_transition",locator:"start-evidence.json"}, {kind:"cancellation",locator:"cancellation-evidence.json"}, {kind:"invocation_locator",locator:"locator-a.json"}, {kind:"invocation_locator",locator:"locator-b.json"}, {kind:"restart",locator:"restart-evidence.json"}, {kind:"project_log",locator:"project-log-a.json"}, {kind:"project_log",locator:"project-log-b.json"}, {kind:"terminal_archive",locator:"archive-a.json"}, {kind:"terminal_archive",locator:"archive-b.json"}, {kind:"terminal_archive",locator:"terminal-evidence.json"} ] ' "${manifest}" >/dev/null || fail "manifest evidence record set is not exact" while IFS=$'\t' read -r kind locator expected_sha256; do validate_evidence_file \ "${manifest_dir}" "${kind}" "${locator}" "${expected_sha256}" "${manifest}" done < <(jq -r '.evidence.records[] | [.kind,.locator,.sha256] | @tsv' "${manifest}") printf 'logged-smoke: manifest valid (%s bytes)\n' "${size}" } self_test() { command -v jq >/dev/null 2>&1 || fail "jq is required for self-test" command -v shasum >/dev/null 2>&1 || fail "shasum is required for self-test" local temporary temporary="$(mktemp -d "${TMPDIR:-/tmp}/iop-agent-smoke-self-test.XXXXXX")" local bundle="${temporary}/bundle" mkdir "${bundle}" local safe="${bundle}/manifest.json" local next="${bundle}/manifest.next.json" local baseline="${temporary}/manifest.baseline.json" local digest_a="sha256:$(printf '0%.0s' {1..64})" local digest_b="sha256:$(printf '1%.0s' {1..64})" local digest_c="sha256:$(printf '2%.0s' {1..64})" local schema_digest schema_digest="$(sha256_file "${MANIFEST_SCHEMA}")" jq -n \ --arg a "${digest_a}" --arg schema "${schema_digest}" \ '{ head:"0000000000000000000000000000000000000000", source_tree:"0000000000000000000000000000000000000000", declared_binary_sha256:$a, rebuilt_binary_sha256:$a, provider_smoke_sha256:$a, schema_sha256:$schema }' >"${bundle}/build-evidence.json" jq -n --arg a "${digest_a}" \ '{profiles:[{profile_id_hash:$a,discovery:"ready",quota_status:"observed",output_sha256:$a}]}' \ >"${bundle}/discovery-quota.json" jq -n --arg a "${digest_a}" --arg b "${digest_b}" \ '{project_a:$a,project_b:$b,selected:true,unblocked:true}' \ >"${bundle}/preview-evidence.json" jq -n --arg a "${digest_a}" --arg b "${digest_b}" --arg c "${digest_c}" \ '{start_a:$a,start_b:$b,running_state:$c}' >"${bundle}/start-evidence.json" jq -n --arg a "${digest_a}" --arg b "${digest_b}" --arg c "${digest_c}" \ '{ stopped_state_sha256:$a, cancelled_process_sha256:$b, sibling_process_sha256:$c, cancelled_process_live:false, sibling_process_live:true }' >"${bundle}/cancellation-evidence.json" jq -n --arg project "${digest_a}" --arg revision "${digest_b}" \ '{ snapshots:[ { project_id_hash:$project,state_revision:"2",project_status:"running", invocations:[{ attempt:"1",dispatch_ordinal:"1",state:"running", locators:[{kind:"process",revision:$revision}] }] }, { project_id_hash:$project,state_revision:"8",project_status:"completed", invocations:[{ attempt:"2",dispatch_ordinal:"2",state:"completed", locators:[{kind:"process",revision:$revision}] }] } ] }' >"${bundle}/locator-a.json" jq -n --arg project "${digest_b}" --arg revision "${digest_c}" \ '{ snapshots:[ { project_id_hash:$project,state_revision:"2",project_status:"running", invocations:[{ attempt:"1",dispatch_ordinal:"1",state:"running", locators:[{kind:"process",revision:$revision}] }] }, { project_id_hash:$project,state_revision:"8",project_status:"completed", invocations:[{ attempt:"1",dispatch_ordinal:"1",state:"completed", locators:[{kind:"process",revision:$revision}] }] } ] }' >"${bundle}/locator-b.json" jq -n \ --arg project "${digest_b}" --arg attempt "${digest_a}" \ --arg locator "${digest_b}" --arg process "${digest_c}" \ '{ project_id_hash:$project, pre:{ state_revision:"4",attempt_id_sha256:$attempt, process_locator_revision:$locator,process_identity_sha256:$process }, post:{ state_revision:"5",attempt_id_sha256:$attempt, process_locator_revision:$locator,process_identity_sha256:$process }, pre_process_live:true, post_process_live:true, same_attempt:true, same_process_locator:true, same_process_identity:true, duplicate_invocation:false }' >"${bundle}/restart-evidence.json" jq -n --arg project "${digest_a}" --arg a "${digest_a}" --arg b "${digest_b}" \ '{project_id_hash:$project,logs:[{archive_ordinal:"1",event_log_sha256:$a,timeline_sha256:$b}]}' \ >"${bundle}/project-log-a.json" jq -n --arg project "${digest_b}" --arg a "${digest_b}" --arg b "${digest_c}" \ '{project_id_hash:$project,logs:[{archive_ordinal:"1",event_log_sha256:$a,timeline_sha256:$b}]}' \ >"${bundle}/project-log-b.json" jq -n --arg project "${digest_a}" --arg a "${digest_a}" --arg b "${digest_b}" --arg c "${digest_c}" \ '{ project_id_hash:$project, archives:[{ archive_ordinal:"1",record_count:1,checksum:$a,terminal:true, manifest_sha256:$a,event_log_sha256:$b,timeline_sha256:$c }] }' >"${bundle}/archive-a.json" jq -n --arg project "${digest_b}" --arg a "${digest_a}" --arg b "${digest_b}" --arg c "${digest_c}" \ '{ project_id_hash:$project, archives:[{ archive_ordinal:"1",record_count:1,checksum:$b,terminal:true, manifest_sha256:$c,event_log_sha256:$a,timeline_sha256:$b }] }' >"${bundle}/archive-b.json" jq -n --arg a "${digest_a}" --arg b "${digest_b}" --arg c "${digest_c}" \ '{ status_a_sha256:$a,status_b_sha256:$b, archive_a_sha256:$b,archive_b_sha256:$c,terminal_status:"completed" }' >"${bundle}/terminal-evidence.json" local build_sha discovery_sha preview_sha start_sha cancellation_sha local locator_a_sha locator_b_sha restart_sha log_a_sha log_b_sha local archive_a_sha archive_b_sha terminal_sha build_sha="$(sha256_file "${bundle}/build-evidence.json")" discovery_sha="$(sha256_file "${bundle}/discovery-quota.json")" preview_sha="$(sha256_file "${bundle}/preview-evidence.json")" start_sha="$(sha256_file "${bundle}/start-evidence.json")" cancellation_sha="$(sha256_file "${bundle}/cancellation-evidence.json")" locator_a_sha="$(sha256_file "${bundle}/locator-a.json")" locator_b_sha="$(sha256_file "${bundle}/locator-b.json")" restart_sha="$(sha256_file "${bundle}/restart-evidence.json")" log_a_sha="$(sha256_file "${bundle}/project-log-a.json")" log_b_sha="$(sha256_file "${bundle}/project-log-b.json")" archive_a_sha="$(sha256_file "${bundle}/archive-a.json")" archive_b_sha="$(sha256_file "${bundle}/archive-b.json")" terminal_sha="$(sha256_file "${bundle}/terminal-evidence.json")" jq -n \ --arg a "${digest_a}" \ --arg b "${digest_b}" \ --arg c "${digest_c}" \ --arg schema "${schema_digest}" \ --arg build "${build_sha}" \ --arg discovery "${discovery_sha}" \ --arg preview "${preview_sha}" \ --arg start "${start_sha}" \ --arg cancellation "${cancellation_sha}" \ --arg locator_a "${locator_a_sha}" \ --arg locator_b "${locator_b_sha}" \ --arg restart "${restart_sha}" \ --arg log_a "${log_a_sha}" \ --arg log_b "${log_b_sha}" \ --arg archive_a "${archive_a_sha}" \ --arg archive_b "${archive_b_sha}" \ --arg terminal "${terminal_sha}" \ '{ schema_version:"2", run_id:$a, source:{ head:"0000000000000000000000000000000000000000", source_tree:"0000000000000000000000000000000000000000", binary_sha256:$a, rebuilt_binary_sha256:$a, binary_build_head:"0000000000000000000000000000000000000000", repo_config_sha256:$a, local_config_sha256:$a, provider_catalog_sha256:$a, build_evidence_sha256:$build, schema_sha256:$schema }, environment:{os:"darwin",arch:"arm64"}, lifecycle:{ discovery:{status:"verified",evidence_sha256:$discovery}, preview:{status:"verified",evidence_sha256:$preview}, manual_start:{status:"verified",evidence_sha256:$start}, cancellation:{status:"verified",evidence_sha256:$cancellation}, sibling_continuation:{status:"verified",evidence_sha256:$cancellation}, new_invocation:{status:"verified",evidence_sha256:$locator_a}, restart_recovery:{status:"verified",evidence_sha256:$restart}, terminal_completion:{status:"verified",evidence_sha256:$terminal} }, projects:[ { slot:"a",project_id_hash:$a, workspace_revision:"0000000000000000000000000000000000000000", status_trace:["started","running","stopped","running","completed"], terminal_status:"completed", initial_invocation_sha256:$a, post_restart_invocation_sha256:$b, locator_evidence_sha256:$locator_a, project_log_sha256:$log_a, terminal_archive_sha256:$archive_a }, { slot:"b",project_id_hash:$b, workspace_revision:"0000000000000000000000000000000000000000", status_trace:["started","running","completed"], terminal_status:"completed", initial_invocation_sha256:$c, post_restart_invocation_sha256:$c, locator_evidence_sha256:$locator_b, project_log_sha256:$log_b, terminal_archive_sha256:$archive_b } ], evidence:{ manifest_locator:"manifest.json", schema_sha256:$schema, records:[ {kind:"source_build",locator:"build-evidence.json",sha256:$build}, {kind:"discovery_quota",locator:"discovery-quota.json",sha256:$discovery}, {kind:"preview",locator:"preview-evidence.json",sha256:$preview}, {kind:"state_transition",locator:"start-evidence.json",sha256:$start}, {kind:"cancellation",locator:"cancellation-evidence.json",sha256:$cancellation}, {kind:"invocation_locator",locator:"locator-a.json",sha256:$locator_a}, {kind:"invocation_locator",locator:"locator-b.json",sha256:$locator_b}, {kind:"restart",locator:"restart-evidence.json",sha256:$restart}, {kind:"project_log",locator:"project-log-a.json",sha256:$log_a}, {kind:"project_log",locator:"project-log-b.json",sha256:$log_b}, {kind:"terminal_archive",locator:"archive-a.json",sha256:$archive_a}, {kind:"terminal_archive",locator:"archive-b.json",sha256:$archive_b}, {kind:"terminal_archive",locator:"terminal-evidence.json",sha256:$terminal} ] } }' >"${safe}" validate_manifest "${safe}" >/dev/null cp "${safe}" "${baseline}" reject_manifest_mutation() { local name="$1" local filter="$2" jq --arg digest "${digest_b}" "${filter}" "${baseline}" >"${next}" mv "${next}" "${safe}" if (validate_manifest "${safe}" >/dev/null 2>&1); then fail "self-test accepted fabricated evidence: ${name}" fi cp "${baseline}" "${safe}" } reject_manifest_mutation "post-terminal-running" \ '.projects[1].status_trace = ["started","running","completed","running"]' reject_manifest_mutation "reused-cancelled-invocation" \ '.projects[0].post_restart_invocation_sha256 = .projects[0].initial_invocation_sha256' reject_manifest_mutation "binary-head-mismatch" \ '.source.binary_build_head = "1111111111111111111111111111111111111111"' reject_manifest_mutation "changed-record-digest" \ '.evidence.records[0].sha256 = $digest' reject_manifest_mutation "changed-schema-digest" \ '.source.schema_sha256 = $digest | .evidence.schema_sha256 = $digest' reject_manifest_mutation "escaped-locator" \ '.evidence.records[0].locator = "../build-evidence.json"' reject_manifest_mutation "duplicate-locator" \ '.evidence.records[12].locator = .evidence.records[11].locator' mv "${bundle}/preview-evidence.json" "${bundle}/preview-evidence.saved.json" if (validate_manifest "${safe}" >/dev/null 2>&1); then fail "self-test accepted a deleted evidence file" fi mv "${bundle}/preview-evidence.saved.json" "${bundle}/preview-evidence.json" mv "${bundle}/preview-evidence.json" "${bundle}/preview-evidence.saved.json" ln -s "preview-evidence.saved.json" "${bundle}/preview-evidence.json" if (validate_manifest "${safe}" >/dev/null 2>&1); then fail "self-test accepted a symlinked evidence file" fi rm "${bundle}/preview-evidence.json" mv "${bundle}/preview-evidence.saved.json" "${bundle}/preview-evidence.json" cp "${bundle}/preview-evidence.json" "${bundle}/preview-evidence.saved.json" printf ' ' >>"${bundle}/preview-evidence.json" if (validate_manifest "${safe}" >/dev/null 2>&1); then fail "self-test accepted tampered evidence bytes" fi mv "${bundle}/preview-evidence.saved.json" "${bundle}/preview-evidence.json" reject_evidence_mutation() { local name="$1" local locator="$2" local filter="$3" local evidence_saved="${temporary}/${locator}.saved" cp "${bundle}/${locator}" "${evidence_saved}" jq --arg replacement "${digest_a}" "${filter}" "${evidence_saved}" \ >"${bundle}/${locator}" local changed_sha changed_sha="$(sha256_file "${bundle}/${locator}")" jq --arg locator "${locator}" --arg digest "${changed_sha}" ' (.evidence.records[] | select(.locator == $locator)).sha256 = $digest | if $locator == "restart-evidence.json" then .lifecycle.restart_recovery.evidence_sha256 = $digest elif $locator == "preview-evidence.json" then .lifecycle.preview.evidence_sha256 = $digest else . end ' "${baseline}" >"${safe}" if (validate_manifest "${safe}" >/dev/null 2>&1); then fail "self-test accepted fabricated evidence content: ${name}" fi mv "${evidence_saved}" "${bundle}/${locator}" cp "${baseline}" "${safe}" } reject_evidence_mutation "kind-schema-mismatch" "preview-evidence.json" \ '{head:"0000000000000000000000000000000000000000"}' reject_evidence_mutation "same-attempt-new-process" "restart-evidence.json" \ '.post.process_locator_revision = $replacement | .post.process_identity_sha256 = $replacement | .same_process_locator = false | .same_process_identity = false | .duplicate_invocation = false' reject_evidence_mutation "contradictory-duplicate-flag" "restart-evidence.json" \ '.duplicate_invocation = true' local fixture_project="${temporary}/fixture-project" mkdir -p "${fixture_project}" seed_field_task "${fixture_project}" a grep -F 'IOP_AGENT_LOGGED_SMOKE_A_OK' \ "${fixture_project}/agent-task/m-${FIELD_MILESTONE}/01_field_runtime/PLAN-cloud-G10.md" \ >/dev/null || fail "self-test field task is missing its exact marker contract" grep -F '`field-smoke-a.txt`' \ "${fixture_project}/agent-task/m-${FIELD_MILESTONE}/01_field_runtime/PLAN-cloud-G10.md" \ >/dev/null || fail "self-test field task is missing its bounded write set" sleep 30 & daemon_pid=$! daemon_start_token="$(process_start_token "${daemon_pid}")" sleep 30 & local unrelated_pid=$! cleanup_owned_daemon if kill -0 "${unrelated_pid}" 2>/dev/null; then kill -TERM "${unrelated_pid}" 2>/dev/null || true wait "${unrelated_pid}" 2>/dev/null || true else fail "exact-PID cleanup terminated an unrelated fixture process" fi rm -rf "${temporary}" printf '%s\n' 'logged-smoke: self-test passed (derived-evidence rejection matrix and exact-PID cleanup)' } seed_field_task() { local project="$1" local slot="$2" local task_directory="${project}/agent-task/m-${FIELD_MILESTONE}/01_field_runtime" local marker_file="field-smoke-${slot}.txt" local marker="IOP_AGENT_LOGGED_SMOKE_${slot^^}_OK" [[ ! -e "${task_directory}" ]] || fail "field project ${slot} already contains the smoke fixture task" [[ ! -e "${project}/${marker_file}" ]] || fail "field project ${slot} already contains the smoke marker" mkdir -p "${task_directory}" cat >"${task_directory}/PLAN-cloud-G10.md" <"${task_directory}/CODE_REVIEW-cloud-G10.md" <"${output_file}" } status_value() { jq -er '.status' "$1" } record_status() { local project_id="$1" local slot="$2" local sequence="$3" run_agent "${output}/status-${slot}-${sequence}.json" status "${project_id}" } start_daemon() { daemon_log="${output}/daemon.log" "${binary}" serve \ --repo-config "${repo_config}" \ --local-config "${local_config}" \ --provider-catalog "${provider_catalog}" >>"${daemon_log}" 2>&1 & daemon_pid=$! daemon_start_token="$(process_start_token "${daemon_pid}")" [[ -n "${daemon_start_token}" ]] || fail "could not capture daemon process identity" local waited=0 while [[ ! -S "${daemon_socket}" ]]; do kill -0 "${daemon_pid}" 2>/dev/null || fail "daemon exited before socket readiness" (( waited < 200 )) || fail "daemon socket readiness timed out" sleep 0.1 waited=$((waited + 1)) done } wait_for_state() { local project_id="$1" local slot="$2" local desired="$3" local sequence_base="$4" local max_wait="${5:-600}" local waited=0 while (( waited < max_wait )); do record_status "${project_id}" "${slot}" "$((sequence_base + waited))" local state state="$(status_value "${output}/status-${slot}-$((sequence_base + waited)).json")" if [[ "${state}" == "${desired}" ]]; then return 0 fi if [[ "${state}" == "blocked" ]]; then fail "project ${slot} blocked while waiting for ${desired}" fi sleep 1 waited=$((waited + 1)) done fail "project ${slot} timed out waiting for ${desired}" } wait_for_live_invocation() { local project_id="$1" local slot="$2" local sequence_base="$3" local waited=0 while (( waited < 600 )); do record_status "${project_id}" "${slot}" "$((sequence_base + waited))" local state state="$(status_value "${output}/status-${slot}-$((sequence_base + waited)).json")" if [[ "${state}" == "blocked" || "${state}" == "completed" ]]; then fail "project ${slot} reached ${state} before a live invocation was observed" fi if [[ -f "${state_file}" ]]; then local process_identity if process_identity="$(state_process_identity "${state_file}" "${project_id}" 2>/dev/null)"; then local process_pid local process_token IFS=$'\t' read -r process_pid process_token <<<"${process_identity}" if [[ -n "${process_pid}" && -n "${process_token}" ]] && [[ "$(process_start_token "${process_pid}" || true)" == "${process_token}" ]]; then state_invocation_identity "${state_file}" "${project_id}" >/dev/null return 0 fi fi fi sleep 1 waited=$((waited + 1)) done fail "project ${slot} timed out waiting for a live invocation" } wait_for_durable_review_while_live() { local project_id="$1" local slot="$2" local process_pid="$3" local process_token="$4" local overlay_reference local review_path IFS=$'\t' read -r overlay_reference review_path < <( jq -er --arg project "${project_id}" ' [ .state.Projects[$project].Works[]? | select(.Locators.process? != null and .Locators.overlay? != null) | { ordinal:.DispatchOrdinal, overlay:.Locators.overlay.Opaque, review:.Unit.Metadata.review_path } ] | sort_by(.ordinal) | if length > 0 then .[-1] | select( (.overlay | type) == "string" and (.review | type) == "string" ) | [.overlay,.review] | @tsv else error("a retained review locator is required") end ' "${state_file}" ) [[ "${overlay_reference}" =~ ^overlay:[0-9a-f]{64}$ ]] || fail "project ${slot} overlay locator is not bounded" [[ "${review_path}" == agent-task/* && "${review_path}" != *".."* ]] || fail "project ${slot} review path is not contained" local overlay_id="${overlay_reference#overlay:}" local task_view="${overlay_root}/tasks/${overlay_id}/view" local review_file="${task_view}/${review_path}" local marker_file="${task_view}/field-smoke-${slot}.txt" local marker="IOP_AGENT_LOGGED_SMOKE_${slot^^}_OK" local waited=0 while (( waited < 12000 )); do [[ "$(process_start_token "${process_pid}" || true)" == "${process_token}" ]] || fail "project ${slot} exited before durable restart evidence was ready" if [[ -f "${review_file}" && -f "${marker_file}" ]] && [[ "$(tr -d '\r\n' <"${marker_file}")" == "${marker}" ]] && ! grep -F '_Paste actual ' "${review_file}" >/dev/null; then return 0 fi sleep 0.05 waited=$((waited + 1)) done fail "project ${slot} timed out waiting for durable review evidence" } status_trace_json() { local slot="$1" local ordered=() while IFS=$'\t' read -r _ file; do ordered+=("${file}") done < <( for file in "${output}"/status-"${slot}"-*.json; do local sequence="${file##*-}" sequence="${sequence%.json}" printf '%010d\t%s\n' "${sequence}" "${file}" done | sort -n ) jq -s '[.[].status] | reduce .[] as $state ([]; if length == 0 or .[-1] != $state then . + [$state] else . end) | .[:16]' \ "${ordered[@]}" } state_process_identity() { local state_file="$1" local project_id="$2" jq -er --arg project "${project_id}" ' [ .state.Projects[$project].Works[]? | select(.Locators.process? != null) | { ordinal:.DispatchOrdinal, process:(.Locators.process.Opaque | fromjson) } ] | sort_by(.ordinal) | if length > 0 then .[-1].process | select((.pid | type) == "number" and (.start_token | type) == "string") | [.pid, .start_token] | @tsv else error("a process locator is required") end ' "${state_file}" } state_invocation_identity() { local state_file="$1" local project_id="$2" local state_revision attempt_id locator_revision process_pid process_token IFS=$'\t' read -r state_revision attempt_id locator_revision process_pid process_token < <( state_live_invocation_fields "${state_file}" "${project_id}" ) sha256_text "${attempt_id}"$'\t'"${locator_revision}"$'\t'"${process_pid}"$'\t'"${process_token}" } state_live_invocation_fields() { local state_file="$1" local project_id="$2" jq -er --arg project "${project_id}" ' .revision as $revision | [ .state.Projects[$project].Works[]? | select(.Locators.process? != null) | { ordinal:.DispatchOrdinal, attempt_id:.AttemptID, locator_revision:.Locators.process.Revision, process:(.Locators.process.Opaque | fromjson) } ] | sort_by(.ordinal) | if length > 0 then .[-1] as $invocation | $invocation.process | select( ($invocation.attempt_id | type) == "string" and ($invocation.locator_revision | type) == "string" and (.pid | type) == "number" and (.start_token | type) == "string" ) | [ ($revision | tostring), $invocation.attempt_id, $invocation.locator_revision, (.pid | tostring), .start_token ] | @tsv else error("a live invocation identity is required") end ' "${state_file}" } capture_locator_summary() { local state_file="$1" local project_id="$2" local project_hash="$3" local destination="$4" jq -e --arg project "${project_id}" --arg project_hash "${project_hash}" ' .revision as $revision | .state.Projects[$project] as $project_state | { project_id_hash:$project_hash, state_revision:($revision | tostring), project_status:$project_state.Status, invocations:[ $project_state.Works[]? | select(.Locators.process? != null) | { attempt:(.Attempt | tostring), dispatch_ordinal:(.DispatchOrdinal | tostring), state:.State, locators:[ .Locators | to_entries[] | select(.key == "process" or .key == "session") | {kind:.key,revision:.value.Revision} ] } ] } | select((.invocations | length) >= 1) | select(all(.invocations[]; (.locators | length) >= 1)) ' "${state_file}" >"${destination}" || fail "state has no bounded invocation locator summary" } capture_archive_summary() { local project_id="$1" local project_hash="$2" local slot="$3" local destination="$4" local list_file="${output}/archive-list-${slot}.txt" : >"${list_file}" while IFS= read -r candidate; do if jq -e --arg project "${project_id}" ' .project_id == $project and .terminal == true and (.record_count | type == "number" and . > 0) and (.checksum | test("^sha256:[0-9a-f]{64}$")) ' "${candidate}" >/dev/null 2>&1; then printf '%s\n' "${candidate}" >>"${list_file}" fi done < <(find "${log_root}" -type f -name '*.manifest.json' -print | LC_ALL=C sort) [[ -s "${list_file}" ]] || fail "project ${slot} has no terminal project-log archive" local records="${output}/archive-records-${slot}.jsonl" : >"${records}" while IFS= read -r manifest_path; do local stem="${manifest_path%.manifest.json}" local event_log="${stem}.jsonl" local timeline="${stem}.timeline.jsonl" [[ -f "${event_log}" && -f "${timeline}" ]] || fail "project ${slot} archive is missing event or timeline evidence" jq -s -e ' length > 0 and (. as $records | all(range(1; length); $records[.].sequence == $records[. - 1].sequence + 1)) and .[-1].terminal == true ' "${event_log}" >/dev/null || fail "project ${slot} project-log sequence is not ordered and terminal" jq -s -e 'length > 0 and .[-1].terminal == true' "${timeline}" >/dev/null || fail "project ${slot} timeline is not terminal" jq -n \ --arg manifest_sha256 "$(sha256_file "${manifest_path}")" \ --arg event_log_sha256 "$(sha256_file "${event_log}")" \ --arg timeline_sha256 "$(sha256_file "${timeline}")" \ --argjson manifest "$(jq -c . "${manifest_path}")" \ '{ archive_ordinal:($manifest.archive_ordinal | tostring), record_count:$manifest.record_count, checksum:$manifest.checksum, terminal:$manifest.terminal, manifest_sha256:$manifest_sha256, event_log_sha256:$event_log_sha256, timeline_sha256:$timeline_sha256 }' >>"${records}" done <"${list_file}" jq -s --arg project_hash "${project_hash}" ' {project_id_hash:$project_hash,archives:sort_by(.archive_ordinal)} ' "${records}" >"${destination}" } preflight_only=false self_test_only=false validate_only="" binary="" repo_config="" local_config="" provider_catalog="" project_a="" project_b="" expected_head="" output="" while (( $# > 0 )); do case "$1" in --help|-h) usage exit 0 ;; --preflight-only) preflight_only=true shift ;; --self-test) self_test_only=true shift ;; --validate-manifest) (( $# >= 2 )) || { usage >&2; exit "${EX_USAGE}"; } validate_only="$2" shift 2 ;; --binary|--repo-config|--local-config|--provider-catalog|--project-a|--project-b|--expected-head|--output) (( $# >= 2 )) || { usage >&2; exit "${EX_USAGE}"; } case "$1" in --binary) binary="$2" ;; --repo-config) repo_config="$2" ;; --local-config) local_config="$2" ;; --provider-catalog) provider_catalog="$2" ;; --project-a) project_a="$2" ;; --project-b) project_b="$2" ;; --expected-head) expected_head="$2" ;; --output) output="$2" ;; esac shift 2 ;; *) printf 'logged-smoke: unknown argument: %s\n' "$1" >&2 usage >&2 exit "${EX_USAGE}" ;; esac done if [[ -n "${validate_only}" ]]; then validate_manifest "${validate_only}" trap - EXIT exit 0 fi if [[ "${self_test_only}" == true ]]; then self_test trap - EXIT exit 0 fi if [[ "${preflight_only}" == true ]]; then host_gate printf '%s\n' 'logged-smoke: Darwin host gate passed before provider login or process launch' trap - EXIT exit 0 fi for value in binary repo_config local_config provider_catalog project_a project_b expected_head output; do [[ -n "${!value}" ]] || { usage >&2; exit "${EX_USAGE}"; } done host_gate for tool in git go jq shasum ps pgrep stat awk sed tail find; do command -v "${tool}" >/dev/null 2>&1 || fail "required tool is unavailable: ${tool}" done binary="$(canonical_file "${binary}")" || fail "binary must be one existing regular file" repo_config="$(canonical_file "${repo_config}")" || fail "repo config must be one existing regular file" local_config="$(canonical_file "${local_config}")" || fail "local config must be one existing regular file" provider_catalog="$(canonical_file "${provider_catalog}")" || fail "provider catalog must be one existing regular file" project_a="$(canonical_directory "${project_a}")" || fail "project A must be one existing directory" project_b="$(canonical_directory "${project_b}")" || fail "project B must be one existing directory" [[ -x "${binary}" ]] || fail "binary is not executable" [[ "${project_a}" != "${project_b}" ]] || fail "project clones must be distinct" [[ "${project_a}" != "${REPO_ROOT}" && "${project_b}" != "${REPO_ROOT}" ]] || fail "field projects must not be the harness source checkout" [[ "${output}" == /* && -d "$(dirname "${output}")" ]] || fail "output must be an absolute path under an existing parent directory" source_head="$(git -C "${REPO_ROOT}" rev-parse HEAD)" source_tree="$(git -C "${REPO_ROOT}" rev-parse 'HEAD^{tree}')" schema_digest="$(sha256_file "${MANIFEST_SCHEMA}")" [[ "${source_head}" == "${expected_head}" ]] || fail "source HEAD differs from --expected-head" [[ -z "$(git -C "${REPO_ROOT}" status --porcelain)" ]] || fail "source checkout is not clean" for project in "${project_a}" "${project_b}"; do [[ "$(git -C "${project}" rev-parse --show-toplevel)" == "${project}" ]] || fail "field project is not a repository root" [[ "$(git -C "${project}" rev-parse HEAD)" == "${expected_head}" ]] || fail "field project revision differs from --expected-head" [[ -z "$(git -C "${project}" status --porcelain)" ]] || fail "field project is not clean" done seed_field_task "${project_a}" a seed_field_task "${project_b}" b state_root="$(awk '$1 == "state_root:" {sub(/^[^:]+:[[:space:]]*/, ""); gsub(/^["'\'']|["'\'']$/, ""); print; exit}' "${local_config}")" [[ "${state_root}" == /* && -d "${state_root}" ]] || fail "local config state_root must be a simple existing absolute scalar" [[ "$(stat -f '%Lp' "${state_root}")" == "700" ]] || fail "state_root mode must be 0700" [[ "$(stat -f '%u' "${state_root}")" == "$(id -u)" ]] || fail "state_root must be owned by the current user" overlay_root="$(awk '$1 == "overlay_root:" {sub(/^[^:]+:[[:space:]]*/, ""); gsub(/^["'\'']|["'\'']$/, ""); print; exit}' "${local_config}")" [[ "${overlay_root}" == /* && -d "${overlay_root}" ]] || fail "local config overlay_root must be a simple existing absolute scalar" log_root="$(awk '$1 == "log_root:" {sub(/^[^:]+:[[:space:]]*/, ""); gsub(/^["'\'']|["'\'']$/, ""); print; exit}' "${local_config}")" [[ "${log_root}" == /* ]] || fail "local config log_root must be a simple absolute scalar" state_file="${state_root}/state.json" daemon_socket="${state_root}/iop-agent.sock" [[ ! -e "${daemon_socket}" ]] || fail "daemon socket already exists" if pgrep -f "${binary} serve" >/dev/null 2>&1; then fail "a matching daemon process already exists" fi [[ ! -e "${output}" ]] || fail "output directory already exists" mkdir -m 0700 "${output}" incoming_binary_digest="$(sha256_file "${binary}")" rebuilt_binary="${output}/iop-agent-exact" provider_smoke_binary="${output}/iop-provider-smoke-exact" go build -trimpath -o "${rebuilt_binary}" ./apps/agent/cmd/agent go build -trimpath -o "${provider_smoke_binary}" ./cmd/iop-provider-smoke rebuilt_binary_digest="$(sha256_file "${rebuilt_binary}")" [[ "${incoming_binary_digest}" == "${rebuilt_binary_digest}" ]] || fail "declared binary digest differs from a fresh exact-HEAD rebuild" binary="${rebuilt_binary}" jq -n \ --arg head "${source_head}" \ --arg source_tree "${source_tree}" \ --arg declared_binary_sha256 "${incoming_binary_digest}" \ --arg rebuilt_binary_sha256 "${rebuilt_binary_digest}" \ --arg provider_smoke_sha256 "$(sha256_file "${provider_smoke_binary}")" \ --arg schema_sha256 "${schema_digest}" \ '{ head:$head, source_tree:$source_tree, declared_binary_sha256:$declared_binary_sha256, rebuilt_binary_sha256:$rebuilt_binary_sha256, provider_smoke_sha256:$provider_smoke_sha256, schema_sha256:$schema_sha256 }' >"${output}/build-evidence.json" "${binary}" validate \ --repo-config "${repo_config}" \ --local-config "${local_config}" \ --provider-catalog "${provider_catalog}" \ --output json >"${output}/validate.json" "${binary}" provider list \ --provider-catalog "${provider_catalog}" \ --output json >"${output}/providers.json" profile_list="${output}/profile-list.txt" awk ' $1 == "profiles:" { in_profiles = 1; next } in_profiles && $1 == "-" && $2 == "id:" { print $3 } ' "${provider_catalog}" >"${profile_list}" [[ "$(wc -l <"${profile_list}" | tr -d ' ')" == "$(jq -er '.profiles' "${output}/validate.json")" ]] || fail "could not resolve every catalog profile for runtime discovery" discovery_records="${output}/discovery-records.jsonl" : >"${discovery_records}" profile_sequence=0 while IFS= read -r profile_id; do [[ "${profile_id}" =~ ^[A-Za-z0-9._-]+$ ]] || fail "catalog profile identity is not shell-safe" profile_sequence=$((profile_sequence + 1)) profile_log="${output}/provider-profile-${profile_sequence}.log" "${provider_smoke_binary}" \ -config "${provider_catalog}" \ -profile "${profile_id}" \ -operations status \ -redact >"${profile_log}" 2>&1 grep -E '^preflight .* state=ready .* redacted=true$' "${profile_log}" >/dev/null || fail "profile ${profile_sequence} did not produce ready discovery evidence" grep -E '^operation=status .* readiness=ready terminal=complete$' "${profile_log}" >/dev/null || fail "profile ${profile_sequence} did not produce ready quota/status evidence" jq -n \ --arg profile_id_hash "$(sha256_text "${profile_id}")" \ --arg output_sha256 "$(sha256_file "${profile_log}")" \ '{profile_id_hash:$profile_id_hash,discovery:"ready",quota_status:"observed",output_sha256:$output_sha256}' \ >>"${discovery_records}" done <"${profile_list}" jq -s 'select(length > 0) | {profiles:.}' "${discovery_records}" \ >"${output}/discovery-quota.json" || fail "runtime discovery produced no profile evidence" run_agent "${output}/projects.json" project list project_a_id="$(jq -er --arg workspace "${project_a}" '[.projects[] | select(.workspace == $workspace) | .id] | if length == 1 then .[0] else error("project A registration mismatch") end' "${output}/projects.json")" project_b_id="$(jq -er --arg workspace "${project_b}" '[.projects[] | select(.workspace == $workspace) | .id] | if length == 1 then .[0] else error("project B registration mismatch") end' "${output}/projects.json")" [[ "${project_a_id}" != "${project_b_id}" ]] || fail "project registrations resolve to one identity" project_a_hash="$(sha256_text "${project_a_id}")" project_b_hash="$(sha256_text "${project_b_id}")" run_agent "${output}/milestones-a.json" milestone list "${project_a_id}" run_agent "${output}/milestones-b.json" milestone list "${project_b_id}" run_agent "${output}/preview-a.json" preview "${project_a_id}" run_agent "${output}/preview-b.json" preview "${project_b_id}" jq -e '.selected == true and (.next_work | length > 0) and (.blockers | length == 0)' "${output}/preview-a.json" >/dev/null jq -e '.selected == true and (.next_work | length > 0) and (.blockers | length == 0)' "${output}/preview-b.json" >/dev/null jq -n \ --arg preview_a_sha256 "$(sha256_file "${output}/preview-a.json")" \ --arg preview_b_sha256 "$(sha256_file "${output}/preview-b.json")" \ '{project_a:$preview_a_sha256,project_b:$preview_b_sha256,selected:true,unblocked:true}' \ >"${output}/preview-evidence.json" run_agent "${output}/start-a.json" start "${project_a_id}" run_agent "${output}/start-b.json" start "${project_b_id}" record_status "${project_a_id}" a 0 record_status "${project_b_id}" b 0 start_daemon wait_for_state "${project_a_id}" a running 100 wait_for_state "${project_b_id}" b running 100 wait_for_live_invocation "${project_a_id}" a 200 wait_for_live_invocation "${project_b_id}" b 200 [[ -f "${state_file}" ]] || fail "manager state was not persisted" cp "${state_file}" "${output}/state-running.json" initial_invocation_a="$(state_invocation_identity "${output}/state-running.json" "${project_a_id}")" initial_invocation_b="$(state_invocation_identity "${output}/state-running.json" "${project_b_id}")" IFS=$'\t' read -r process_a_pid process_a_token < <( state_process_identity "${output}/state-running.json" "${project_a_id}" ) IFS=$'\t' read -r process_b_pid process_b_token < <( state_process_identity "${output}/state-running.json" "${project_b_id}" ) [[ "$(process_start_token "${process_a_pid}")" == "${process_a_token}" ]] || fail "project A did not have one live exact invocation while running" [[ "$(process_start_token "${process_b_pid}")" == "${process_b_token}" ]] || fail "project B did not have one live exact invocation while running" jq -n \ --arg start_a_sha256 "$(sha256_file "${output}/start-a.json")" \ --arg start_b_sha256 "$(sha256_file "${output}/start-b.json")" \ --arg running_state_sha256 "$(sha256_file "${output}/state-running.json")" \ '{start_a:$start_a_sha256,start_b:$start_b_sha256,running_state:$running_state_sha256}' \ >"${output}/start-evidence.json" run_agent "${output}/stop-a.json" stop "${project_a_id}" record_status "${project_a_id}" a 300 record_status "${project_b_id}" b 300 [[ "$(status_value "${output}/status-a-300.json")" == "stopped" ]] || fail "project A did not stop" [[ "$(status_value "${output}/status-b-300.json")" == "running" ]] || fail "project B did not remain running during project A cancellation" cp "${state_file}" "${output}/state-stopped.json" waited=0 while [[ "$(process_start_token "${process_a_pid}" || true)" == "${process_a_token}" ]]; do (( waited < 200 )) || fail "project A exact invocation remained live after cancellation" sleep 0.1 waited=$((waited + 1)) done [[ "$(process_start_token "${process_b_pid}")" == "${process_b_token}" ]] || fail "project A cancellation changed the sibling invocation identity" jq -n \ --arg stopped_state_sha256 "$(sha256_file "${output}/state-stopped.json")" \ --arg cancelled_process_sha256 "$(sha256_text "${process_a_pid}:${process_a_token}")" \ --arg sibling_process_sha256 "$(sha256_text "${process_b_pid}:${process_b_token}")" \ '{ stopped_state_sha256:$stopped_state_sha256, cancelled_process_sha256:$cancelled_process_sha256, sibling_process_sha256:$sibling_process_sha256, cancelled_process_live:false, sibling_process_live:true }' >"${output}/cancellation-evidence.json" wait_for_durable_review_while_live \ "${project_b_id}" b "${process_b_pid}" "${process_b_token}" cp "${state_file}" "${output}/state-before-restart-live.json" IFS=$'\t' read -r \ pre_restart_revision pre_restart_attempt pre_restart_locator \ pre_restart_pid pre_restart_start_identity < <( state_live_invocation_fields \ "${output}/state-before-restart-live.json" "${project_b_id}" ) [[ "$(process_start_token "${pre_restart_pid}")" == "${pre_restart_start_identity}" ]] || fail "project B restart checkpoint was not live before daemon shutdown" crash_owned_daemon_preserving_invocations [[ "$(process_start_token "${pre_restart_pid}" || true)" == "${pre_restart_start_identity}" ]] || fail "daemon crash changed the retained live invocation" record_status "${project_b_id}" b 350 [[ "$(status_value "${output}/status-b-350.json")" == "running" ]] || fail "project B did not remain running between daemon instances" start_daemon wait_for_live_invocation "${project_b_id}" b 400 cp "${state_file}" "${output}/state-after-restart-live.json" IFS=$'\t' read -r \ post_restart_revision post_restart_attempt post_restart_locator \ post_restart_pid post_restart_start_identity < <( state_live_invocation_fields \ "${output}/state-after-restart-live.json" "${project_b_id}" ) [[ "$(process_start_token "${post_restart_pid}")" == "${post_restart_start_identity}" ]] || fail "project B restart checkpoint was not live after daemon recovery" same_attempt=false same_process_locator=false same_process_identity=false duplicate_invocation=true [[ "${pre_restart_attempt}" == "${post_restart_attempt}" ]] && same_attempt=true [[ "${pre_restart_locator}" == "${post_restart_locator}" ]] && same_process_locator=true if [[ "${pre_restart_pid}" == "${post_restart_pid}" && "${pre_restart_start_identity}" == "${post_restart_start_identity}" ]]; then same_process_identity=true fi if [[ "${same_attempt}" == true && "${same_process_locator}" == true && "${same_process_identity}" == true ]]; then duplicate_invocation=false fi (( post_restart_revision > pre_restart_revision )) || fail "daemon recovery did not advance the persisted state revision" [[ "${duplicate_invocation}" == false ]] || fail "daemon recovery changed the exact live invocation identity" post_restart_invocation_b="$(state_invocation_identity \ "${output}/state-after-restart-live.json" "${project_b_id}")" [[ "${initial_invocation_b}" == "${post_restart_invocation_b}" ]] || fail "project B restart recovery changed its retained invocation identity" jq -n \ --arg project_id_hash "${project_b_hash}" \ --arg pre_revision "${pre_restart_revision}" \ --arg post_revision "${post_restart_revision}" \ --arg pre_attempt "$(sha256_text "${pre_restart_attempt}")" \ --arg post_attempt "$(sha256_text "${post_restart_attempt}")" \ --arg pre_locator "${pre_restart_locator}" \ --arg post_locator "${post_restart_locator}" \ --arg pre_process "$(sha256_text "${pre_restart_pid}:${pre_restart_start_identity}")" \ --arg post_process "$(sha256_text "${post_restart_pid}:${post_restart_start_identity}")" \ --argjson same_attempt "${same_attempt}" \ --argjson same_process_locator "${same_process_locator}" \ --argjson same_process_identity "${same_process_identity}" \ --argjson duplicate_invocation "${duplicate_invocation}" \ '{ project_id_hash:$project_id_hash, pre:{ state_revision:$pre_revision, attempt_id_sha256:$pre_attempt, process_locator_revision:$pre_locator, process_identity_sha256:$pre_process }, post:{ state_revision:$post_revision, attempt_id_sha256:$post_attempt, process_locator_revision:$post_locator, process_identity_sha256:$post_process }, pre_process_live:true, post_process_live:true, same_attempt:$same_attempt, same_process_locator:$same_process_locator, same_process_identity:$same_process_identity, duplicate_invocation:$duplicate_invocation }' >"${output}/restart-evidence.json" run_agent "${output}/resume-a.json" resume "${project_a_id}" record_status "${project_a_id}" a 500 wait_for_state "${project_b_id}" b completed 600 1800 wait_for_state "${project_a_id}" a completed 800 1800 cleanup_owned_daemon record_status "${project_a_id}" a 1100 record_status "${project_b_id}" b 1100 cp "${state_file}" "${output}/state-terminal.json" post_restart_invocation_a="$(state_invocation_identity "${output}/state-terminal.json" "${project_a_id}")" [[ "${initial_invocation_a}" != "${post_restart_invocation_a}" ]] || fail "project A resume reused the cancelled invocation identity" capture_locator_summary \ "${output}/state-running.json" "${project_a_id}" "${project_a_hash}" \ "${output}/locator-a-initial.json" capture_locator_summary \ "${output}/state-terminal.json" "${project_a_id}" "${project_a_hash}" \ "${output}/locator-a-terminal.json" jq -s '{snapshots:.}' \ "${output}/locator-a-initial.json" "${output}/locator-a-terminal.json" \ >"${output}/locator-a.json" capture_locator_summary \ "${output}/state-running.json" "${project_b_id}" "${project_b_hash}" \ "${output}/locator-b-initial.json" capture_locator_summary \ "${output}/state-after-restart-live.json" "${project_b_id}" "${project_b_hash}" \ "${output}/locator-b-restarted.json" capture_locator_summary \ "${output}/state-terminal.json" "${project_b_id}" "${project_b_hash}" \ "${output}/locator-b-terminal.json" jq -s '{snapshots:.}' \ "${output}/locator-b-initial.json" \ "${output}/locator-b-restarted.json" \ "${output}/locator-b-terminal.json" \ >"${output}/locator-b.json" trace_a="$(status_trace_json a)" trace_b="$(status_trace_json b)" binary_digest="$(sha256_file "${binary}")" repo_digest="$(sha256_file "${repo_config}")" local_digest="$(sha256_file "${local_config}")" catalog_digest="$(sha256_file "${provider_catalog}")" project_a_revision="$(git -C "${project_a}" rev-parse HEAD)" project_b_revision="$(git -C "${project_b}" rev-parse HEAD)" capture_archive_summary \ "${project_a_id}" "${project_a_hash}" a "${output}/archive-a.json" capture_archive_summary \ "${project_b_id}" "${project_b_hash}" b "${output}/archive-b.json" jq '{project_id_hash,logs:[.archives[] | { archive_ordinal,event_log_sha256,timeline_sha256 }]}' "${output}/archive-a.json" >"${output}/project-log-a.json" jq '{project_id_hash,logs:[.archives[] | { archive_ordinal,event_log_sha256,timeline_sha256 }]}' "${output}/archive-b.json" >"${output}/project-log-b.json" jq -n \ --arg status_a_sha256 "$(sha256_file "${output}/status-a-1100.json")" \ --arg status_b_sha256 "$(sha256_file "${output}/status-b-1100.json")" \ --arg archive_a_sha256 "$(sha256_file "${output}/archive-a.json")" \ --arg archive_b_sha256 "$(sha256_file "${output}/archive-b.json")" \ '{ status_a_sha256:$status_a_sha256, status_b_sha256:$status_b_sha256, archive_a_sha256:$archive_a_sha256, archive_b_sha256:$archive_b_sha256, terminal_status:"completed" }' >"${output}/terminal-evidence.json" run_id="$(sha256_text "${source_head}:${binary_digest}:${project_a_hash}:${project_b_hash}")" arch="$(uname -m)" case "${arch}" in arm64|x86_64) ;; *) fail "unsupported Darwin architecture ${arch}" ;; esac [[ "${arch}" == "x86_64" ]] && arch="amd64" jq -n \ --arg run_id "${run_id}" \ --arg head "${source_head}" \ --arg source_tree "${source_tree}" \ --arg binary_sha256 "${binary_digest}" \ --arg rebuilt_binary_sha256 "${rebuilt_binary_digest}" \ --arg binary_build_head "${source_head}" \ --arg repo_config_sha256 "${repo_digest}" \ --arg local_config_sha256 "${local_digest}" \ --arg provider_catalog_sha256 "${catalog_digest}" \ --arg build_evidence_sha256 "$(sha256_file "${output}/build-evidence.json")" \ --arg arch "${arch}" \ --arg project_a_hash "${project_a_hash}" \ --arg project_b_hash "${project_b_hash}" \ --arg project_a_revision "${project_a_revision}" \ --arg project_b_revision "${project_b_revision}" \ --argjson trace_a "${trace_a}" \ --argjson trace_b "${trace_b}" \ --arg initial_invocation_a "${initial_invocation_a}" \ --arg initial_invocation_b "${initial_invocation_b}" \ --arg post_restart_invocation_a "${post_restart_invocation_a}" \ --arg post_restart_invocation_b "${post_restart_invocation_b}" \ --arg locator_a_sha256 "$(sha256_file "${output}/locator-a.json")" \ --arg locator_b_sha256 "$(sha256_file "${output}/locator-b.json")" \ --arg project_log_a_sha256 "$(sha256_file "${output}/project-log-a.json")" \ --arg project_log_b_sha256 "$(sha256_file "${output}/project-log-b.json")" \ --arg archive_a_sha256 "$(sha256_file "${output}/archive-a.json")" \ --arg archive_b_sha256 "$(sha256_file "${output}/archive-b.json")" \ --arg discovery_sha256 "$(sha256_file "${output}/discovery-quota.json")" \ --arg preview_sha256 "$(sha256_file "${output}/preview-evidence.json")" \ --arg start_sha256 "$(sha256_file "${output}/start-evidence.json")" \ --arg cancellation_sha256 "$(sha256_file "${output}/cancellation-evidence.json")" \ --arg restart_sha256 "$(sha256_file "${output}/restart-evidence.json")" \ --arg terminal_sha256 "$(sha256_file "${output}/terminal-evidence.json")" \ --arg schema_digest "${schema_digest}" \ '{ schema_version:"2", run_id:$run_id, source:{ head:$head, source_tree:$source_tree, binary_sha256:$binary_sha256, rebuilt_binary_sha256:$rebuilt_binary_sha256, binary_build_head:$binary_build_head, repo_config_sha256:$repo_config_sha256, local_config_sha256:$local_config_sha256, provider_catalog_sha256:$provider_catalog_sha256, build_evidence_sha256:$build_evidence_sha256, schema_sha256:$schema_digest }, environment:{os:"darwin",arch:$arch}, lifecycle:{ discovery:{status:"verified",evidence_sha256:$discovery_sha256}, preview:{status:"verified",evidence_sha256:$preview_sha256}, manual_start:{status:"verified",evidence_sha256:$start_sha256}, cancellation:{status:"verified",evidence_sha256:$cancellation_sha256}, sibling_continuation:{status:"verified",evidence_sha256:$cancellation_sha256}, new_invocation:{status:"verified",evidence_sha256:$locator_a_sha256}, restart_recovery:{status:"verified",evidence_sha256:$restart_sha256}, terminal_completion:{status:"verified",evidence_sha256:$terminal_sha256} }, projects:[ { slot:"a", project_id_hash:$project_a_hash, workspace_revision:$project_a_revision, status_trace:$trace_a, terminal_status:"completed", initial_invocation_sha256:$initial_invocation_a, post_restart_invocation_sha256:$post_restart_invocation_a, locator_evidence_sha256:$locator_a_sha256, project_log_sha256:$project_log_a_sha256, terminal_archive_sha256:$archive_a_sha256 }, { slot:"b", project_id_hash:$project_b_hash, workspace_revision:$project_b_revision, status_trace:$trace_b, terminal_status:"completed", initial_invocation_sha256:$initial_invocation_b, post_restart_invocation_sha256:$post_restart_invocation_b, locator_evidence_sha256:$locator_b_sha256, project_log_sha256:$project_log_b_sha256, terminal_archive_sha256:$archive_b_sha256 } ], evidence:{ manifest_locator:"manifest.json", schema_sha256:$schema_digest, records:[ {kind:"source_build",locator:"build-evidence.json",sha256:$build_evidence_sha256}, {kind:"discovery_quota",locator:"discovery-quota.json",sha256:$discovery_sha256}, {kind:"preview",locator:"preview-evidence.json",sha256:$preview_sha256}, {kind:"state_transition",locator:"start-evidence.json",sha256:$start_sha256}, {kind:"cancellation",locator:"cancellation-evidence.json",sha256:$cancellation_sha256}, {kind:"invocation_locator",locator:"locator-a.json",sha256:$locator_a_sha256}, {kind:"invocation_locator",locator:"locator-b.json",sha256:$locator_b_sha256}, {kind:"restart",locator:"restart-evidence.json",sha256:$restart_sha256}, {kind:"project_log",locator:"project-log-a.json",sha256:$project_log_a_sha256}, {kind:"project_log",locator:"project-log-b.json",sha256:$project_log_b_sha256}, {kind:"terminal_archive",locator:"archive-a.json",sha256:$archive_a_sha256}, {kind:"terminal_archive",locator:"archive-b.json",sha256:$archive_b_sha256}, {kind:"terminal_archive",locator:"terminal-evidence.json",sha256:$terminal_sha256} ] } }' >"${output}/manifest.json" validate_manifest "${output}/manifest.json" trap - EXIT printf 'logged-smoke: PASS manifest=%s\n' "${output}/manifest.json"