#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" echo "[e2e] NOTE: auxiliary smoke only; completion requires scripts/dev/edge.sh + scripts/dev/node.sh user-flow verification." if command -v shellcheck >/dev/null 2>&1; then echo "[e2e] running shellcheck..." shellcheck "$0" "$REPO_ROOT/scripts/dev/edge.sh" "$REPO_ROOT/scripts/dev/node.sh" else echo "[e2e] shellcheck not found, skipping" fi TMP_DIR=$(mktemp -d) kill_process_tree() { local pid="$1" local child if [ -z "$pid" ]; then return 0 fi if command -v pgrep >/dev/null 2>&1; then while IFS= read -r child; do if [ -n "$child" ]; then kill_process_tree "$child" fi done </dev/null || true) EOF_CHILDREN fi kill "$pid" 2>/dev/null || true } cleanup() { set +e exec 3>&- 2>/dev/null || true kill_process_tree "${NODE_PID:-}" kill_process_tree "${EDGE_PID:-}" rm -rf "$TMP_DIR" 2>/dev/null || true } trap cleanup EXIT EDGE_CONFIG="$TMP_DIR/edge.yaml" NODE_CONFIG="$TMP_DIR/node.yaml" PORT=$((30000 + RANDOM % 10000)) BOOTSTRAP_PORT=$((20000 + RANDOM % 10000)) EDGE_METRICS_PORT=$((40000 + RANDOM % 10000)) NODE_METRICS_PORT=$((50000 + RANDOM % 10000)) PROFILE="${IOP_E2E_PROFILE:-mock}" IDLE_SECONDS="${IOP_E2E_IDLE_SECONDS:-0}" REGISTER_TIMEOUT="${IOP_E2E_REGISTER_TIMEOUT:-60}" EVENT_TIMEOUT="${IOP_E2E_EVENT_TIMEOUT:-30}" RUN_TIMEOUT="${IOP_E2E_RUN_TIMEOUT:-120}" STATUS_TIMEOUT="${IOP_E2E_STATUS_TIMEOUT:-$RUN_TIMEOUT}" COMMAND_SETTLE_SECONDS="${IOP_E2E_COMMAND_SETTLE_SECONDS:-0.2}" EDGE_BIND_TIMEOUT="${IOP_E2E_BIND_TIMEOUT:-10}" IS_PERSISTENT=0 IS_APP_SERVER=0 HAS_STATUS=0 EXPECT_TAIL_MESSAGES=0 PROMPT_TEMPLATE_FILE="${IOP_E2E_PROMPT_TEMPLATES:-$REPO_ROOT/scripts/fixtures/user-e2e-prompts.tsv}" if ! [[ "$EDGE_BIND_TIMEOUT" =~ ^[1-9][0-9]*$ ]]; then echo "[e2e] IOP_E2E_BIND_TIMEOUT must be a positive integer, got: $EDGE_BIND_TIMEOUT" >&2 exit 1 fi prompt_template_count() { awk -F'|' 'NF >= 3 && $1 !~ /^#/ { n++ } END { print n + 0 }' "$PROMPT_TEMPLATE_FILE" } select_prompt_template() { local offset="$1" local total="$2" local idx idx=$(( (PROMPT_TEMPLATE_BASE + offset - 1) % total + 1 )) awk -F'|' -v idx="$idx" ' NF >= 3 && $1 !~ /^#/ { n++ if (n == idx) { print $1 "\t" $2 "\t" $3 exit } } ' "$PROMPT_TEMPLATE_FILE" } if [ ! -f "$PROMPT_TEMPLATE_FILE" ]; then echo "[e2e] prompt template file not found: $PROMPT_TEMPLATE_FILE" >&2 exit 1 fi PROMPT_TEMPLATE_TOTAL=$(prompt_template_count) if [ "$PROMPT_TEMPLATE_TOTAL" -lt 3 ]; then echo "[e2e] prompt template file needs at least 3 entries: $PROMPT_TEMPLATE_FILE" >&2 exit 1 fi PROMPT_TEMPLATE_BASE="${IOP_E2E_PROMPT_INDEX:-$((RANDOM % PROMPT_TEMPLATE_TOTAL + 1))}" IFS=$'\t' read -r FIRST_TEMPLATE FIRST_EXPECTED FIRST_PROMPT < "$MOCK_CLI" #!/usr/bin/env sh while IFS= read -r line; do token=$(printf '%s\n' "$line" | sed -nE 's/.*(IOP_E2E_[A-Z0-9_]+).*/\1/p' | head -n 1) if [ -n "$token" ]; then printf '%s\n%s_TAIL\n' "$token" "$token" else printf 'IOP_E2E_UNKNOWN\n' fi done EOF chmod +x "$MOCK_CLI" cat < "$EDGE_CONFIG" server: listen: "127.0.0.1:$PORT" metrics: port: $EDGE_METRICS_PORT bootstrap: listen: "127.0.0.1:$BOOTSTRAP_PORT" artifact_dir: "$TMP_DIR/artifacts" nodes: - id: test-node alias: test-node token: test-token adapters: mock: enabled: true cli: enabled: true profiles: fake-cli: command: "$MOCK_CLI" persistent: true response_idle_timeout_ms: 1000 console: adapter: cli target: fake-cli session_id: default EOF else echo "[e2e] preparing real profile smoke test (profile: $PROFILE)..." TARGET="$PROFILE" PROFILE_BLOCK=$(awk -v profile="$PROFILE" ' $0 ~ "^[[:space:]]+"profile":" { found=1; print $0; indent=index($0, profile) - 1; next; } found { if ($0 ~ "^[[:space:]]*$") { print $0; next; } curr_indent=0; while(curr_indent < length($0) && substr($0, curr_indent+1, 1) == " ") curr_indent++; if(curr_indent > indent) { print $0; } else { exit; } } ' "$REPO_ROOT/configs/edge.yaml") if [ -z "$PROFILE_BLOCK" ]; then echo "[e2e] BLOCKED: Profile '$PROFILE' not found in configs/edge.yaml" exit 1 fi if echo "$PROFILE_BLOCK" | grep -q "persistent:[[:space:]]*true"; then IS_PERSISTENT=1 fi # codex-app-server mode maintains a long-lived process per session — treat as persistent # so /sessions and /terminate-session are verified. if echo "$PROFILE_BLOCK" | grep -q 'mode:[[:space:]]*"codex-app-server"'; then IS_PERSISTENT=1 IS_APP_SERVER=1 fi # Simple check for status support based on command name if echo "$PROFILE_BLOCK" | grep -i -E "antigravity|agy|claude|codex" >/dev/null; then HAS_STATUS=1 fi cat < "$EDGE_CONFIG" server: listen: "127.0.0.1:$PORT" metrics: port: $EDGE_METRICS_PORT bootstrap: listen: "127.0.0.1:$BOOTSTRAP_PORT" artifact_dir: "$TMP_DIR/artifacts" nodes: - id: test-node alias: test-node token: test-token adapters: cli: enabled: true profiles: $PROFILE_BLOCK console: adapter: cli target: $PROFILE session_id: default EOF fi cat < "$NODE_CONFIG" transport: edge_addr: "127.0.0.1:$PORT" token: test-token reconnect: interval_sec: 1 max_attempts: 0 metrics: port: $NODE_METRICS_PORT node: id: test-node alias: test-node EOF echo "[e2e] starting smoke test (profile: $PROFILE, port: $PORT, persistent: $IS_PERSISTENT, has_status: $HAS_STATUS)" EDGE_OUT="$TMP_DIR/edge_out" NODE_OUT="$TMP_DIR/node_out" rm -f "$TMP_DIR/edge_fifo" mkfifo "$TMP_DIR/edge_fifo" IOP_EDGE_CONFIG="$EDGE_CONFIG" "$REPO_ROOT/scripts/dev/edge.sh" < "$TMP_DIR/edge_fifo" > "$EDGE_OUT" 2>&1 & EDGE_PID=$! exec 3> "$TMP_DIR/edge_fifo" deadline=$((SECONDS + EDGE_BIND_TIMEOUT)) while ! timeout 1 bash -c 'cat < /dev/null > /dev/tcp/127.0.0.1/'"$PORT" 2>/dev/null; do if (( SECONDS >= deadline )); then echo "[e2e] edge failed to bind port $PORT" cat "$EDGE_OUT" exit 1 fi sleep 0.5 done IOP_NODE_CONFIG="$NODE_CONFIG" "$REPO_ROOT/scripts/dev/node.sh" > "$NODE_OUT" 2>&1 & NODE_PID=$! LAST_CMD_START_LINE=1 ensure_smoke_processes_running() { local context="$1" if ! kill -0 "$EDGE_PID" 2>/dev/null; then echo "[e2e] edge exited while waiting for $context" echo "=== EDGE OUTPUT ===" cat "$EDGE_OUT" exit 1 fi if ! kill -0 "$NODE_PID" 2>/dev/null; then echo "[e2e] node exited while waiting for $context" echo "=== NODE OUTPUT ===" cat "$NODE_OUT" exit 1 fi } wait_for_node_registration() { local deadline deadline=$((SECONDS + REGISTER_TIMEOUT)) echo "[e2e] waiting for node registration (timeout: ${REGISTER_TIMEOUT}s)" while ! grep -q '\[node0-evt\] connected reason="registered"' "$EDGE_OUT"; do ensure_smoke_processes_running "node registration" if (( SECONDS >= deadline )); then echo "[e2e] node registration timed out" echo "=== EDGE OUTPUT ===" cat "$EDGE_OUT" echo "=== NODE OUTPUT ===" cat "$NODE_OUT" exit 1 fi sleep 0.5 done } wait_for_edge_pattern_since() { local pattern="$1" local start_line="$2" local msg="$3" local timeout="${4:-$EVENT_TIMEOUT}" local deadline local slice deadline=$((SECONDS + timeout)) slice="$TMP_DIR/edge_wait_slice" while true; do tail -n +"$start_line" "$EDGE_OUT" > "$slice" || true if grep -qE "$pattern" "$slice"; then return 0 fi ensure_smoke_processes_running "$msg" if (( SECONDS >= deadline )); then echo "[e2e] FAIL: timed out waiting for $msg (pattern: '$pattern', timeout: ${timeout}s)" echo "=== EDGE OUTPUT ===" cat "$EDGE_OUT" echo "=== NODE OUTPUT ===" cat "$NODE_OUT" exit 1 fi sleep 0.5 done } wait_for_edge_text_since() { local text="$1" local start_line="$2" local msg="$3" local timeout="${4:-$EVENT_TIMEOUT}" local deadline local slice deadline=$((SECONDS + timeout)) slice="$TMP_DIR/edge_wait_slice" while true; do tail -n +"$start_line" "$EDGE_OUT" > "$slice" || true if grep -qF "$text" "$slice"; then return 0 fi ensure_smoke_processes_running "$msg" if (( SECONDS >= deadline )); then echo "[e2e] FAIL: timed out waiting for $msg (text: '$text', timeout: ${timeout}s)" echo "=== EDGE OUTPUT ===" cat "$EDGE_OUT" echo "=== NODE OUTPUT ===" cat "$NODE_OUT" exit 1 fi sleep 0.5 done } send_cmd() { LAST_CMD_START_LINE=$(($(wc -l < "$EDGE_OUT" | tr -d ' ') + 1)) echo "[e2e] > $1" echo "$1" >&3 sleep "$COMMAND_SETTLE_SECONDS" } wait_for_node_registration send_cmd "/nodes" wait_for_edge_text_since "test-node (test-node)" "$LAST_CMD_START_LINE" "/nodes output" send_cmd "/capabilities" wait_for_edge_text_since "[node0-capabilities]" "$LAST_CMD_START_LINE" "/capabilities output" wait_for_edge_text_since "targets = ${TARGET}" "$LAST_CMD_START_LINE" "/capabilities targets" send_cmd "/transport" wait_for_edge_text_since "[node0-transport]" "$LAST_CMD_START_LINE" "/transport output" wait_for_edge_text_since "adapter = cli" "$LAST_CMD_START_LINE" "/transport adapter" wait_for_edge_text_since "connected = true" "$LAST_CMD_START_LINE" "/transport connected status" wait_for_edge_text_since "state = connected" "$LAST_CMD_START_LINE" "/transport state" wait_for_edge_text_since "node_id = test-node" "$LAST_CMD_START_LINE" "/transport node id" wait_for_edge_text_since "session_id = default" "$LAST_CMD_START_LINE" "/transport session id" wait_for_edge_text_since "target = ${TARGET}" "$LAST_CMD_START_LINE" "/transport target" send_cmd "$FIRST_PROMPT" wait_for_edge_pattern_since "\\[node0-evt\\] start run_id=" "$LAST_CMD_START_LINE" "foreground run 1 start" "$RUN_TIMEOUT" wait_for_edge_pattern_since "\\[node0-msg\\].*${FIRST_EXPECTED}" "$LAST_CMD_START_LINE" "foreground run 1 node message" "$RUN_TIMEOUT" wait_for_edge_pattern_since "\\[node0-evt\\] complete run_id=" "$LAST_CMD_START_LINE" "foreground run 1 completion" "$RUN_TIMEOUT" send_cmd "$SECOND_PROMPT" wait_for_edge_pattern_since "\\[node0-evt\\] start run_id=" "$LAST_CMD_START_LINE" "foreground run 2 start" "$RUN_TIMEOUT" wait_for_edge_pattern_since "\\[node0-msg\\].*${SECOND_EXPECTED}" "$LAST_CMD_START_LINE" "foreground run 2 node message" "$RUN_TIMEOUT" wait_for_edge_pattern_since "\\[node0-evt\\] complete run_id=" "$LAST_CMD_START_LINE" "foreground run 2 completion" "$RUN_TIMEOUT" send_cmd "/session session2" wait_for_edge_pattern_since "session .*session2" "$LAST_CMD_START_LINE" "/session acknowledgement" send_cmd "/background on" wait_for_edge_pattern_since "background .*on" "$LAST_CMD_START_LINE" "/background on acknowledgement" send_cmd "$BACKGROUND_PROMPT" wait_for_edge_pattern_since "\\[node0-evt\\] start run_id=.*session=session2.*background=true" "$LAST_CMD_START_LINE" "background run start" "$RUN_TIMEOUT" wait_for_edge_pattern_since "\\[node0-msg\\].*${BACKGROUND_EXPECTED}" "$LAST_CMD_START_LINE" "background run node message" "$RUN_TIMEOUT" wait_for_edge_pattern_since "\\[node0-evt\\] complete run_id=" "$LAST_CMD_START_LINE" "background run completion" "$RUN_TIMEOUT" send_cmd "/background off" wait_for_edge_pattern_since "background .*off" "$LAST_CMD_START_LINE" "/background off acknowledgement" send_cmd "/sessions" wait_for_edge_text_since "[node0-sessions]" "$LAST_CMD_START_LINE" "/sessions output" if [ "$IS_PERSISTENT" -eq 1 ]; then send_cmd "/terminate-session" wait_for_edge_text_since "terminated session" "$LAST_CMD_START_LINE" "/terminate-session acknowledgement" fi if [ "$HAS_STATUS" -eq 1 ]; then send_cmd "/status" wait_for_edge_text_since "[node0-status]" "$LAST_CMD_START_LINE" "/status output" "$STATUS_TIMEOUT" fi if [ "${IOP_E2E_RECONNECT:-0}" -eq 1 ]; then echo "[e2e] reconnect test enabled, stopping node..." # Update start line baseline before stopping node to ignore prior registration LAST_CMD_START_LINE=$(($(wc -l < "$EDGE_OUT" | tr -d ' ') + 1)) kill_process_tree "$NODE_PID" sleep 2 echo "[e2e] restarting node..." echo "=== NODE RESTARTED ===" >> "$NODE_OUT" IOP_NODE_CONFIG="$NODE_CONFIG" "$REPO_ROOT/scripts/dev/node.sh" >> "$NODE_OUT" 2>&1 & NODE_PID=$! wait_for_edge_pattern_since '\[node0-evt\] connected reason="registered"' "$LAST_CMD_START_LINE" "node re-registration" echo "[e2e] sending 4th command after reconnect..." send_cmd "$FOURTH_PROMPT" wait_for_edge_pattern_since "\\[node0-evt\\] start run_id=" "$LAST_CMD_START_LINE" "foreground run 4 start" "$RUN_TIMEOUT" wait_for_edge_pattern_since "\\[node0-msg\\].*${FOURTH_EXPECTED}" "$LAST_CMD_START_LINE" "foreground run 4 node message" "$RUN_TIMEOUT" wait_for_edge_pattern_since "\\[node0-evt\\] complete run_id=" "$LAST_CMD_START_LINE" "foreground run 4 completion" "$RUN_TIMEOUT" fi IDLE_FAIL=0 IDLE_MARKER_HITS="" if [ "$IDLE_SECONDS" -gt 0 ]; then EDGE_BASELINE=$(wc -l < "$EDGE_OUT" | tr -d ' ') NODE_BASELINE=$(wc -l < "$NODE_OUT" | tr -d ' ') echo "[e2e] idle monitor: sleeping ${IDLE_SECONDS}s (edge baseline=${EDGE_BASELINE} node baseline=${NODE_BASELINE})" sleep "$IDLE_SECONDS" IDLE_PATTERN='transport_closed|heartbeat_timeout|node unregistered|disconnected from edge|transport_close_reason' EDGE_IDLE_TAIL="$TMP_DIR/edge_idle_tail" NODE_IDLE_TAIL="$TMP_DIR/node_idle_tail" tail -n +"$((EDGE_BASELINE + 1))" "$EDGE_OUT" > "$EDGE_IDLE_TAIL" || true tail -n +"$((NODE_BASELINE + 1))" "$NODE_OUT" > "$NODE_IDLE_TAIL" || true if grep -E "$IDLE_PATTERN" "$EDGE_IDLE_TAIL" >/dev/null 2>&1; then IDLE_FAIL=1 IDLE_MARKER_HITS+=$'\n--- EDGE idle window markers ---\n' IDLE_MARKER_HITS+=$(grep -E "$IDLE_PATTERN" "$EDGE_IDLE_TAIL") fi if grep -E "$IDLE_PATTERN" "$NODE_IDLE_TAIL" >/dev/null 2>&1; then IDLE_FAIL=1 IDLE_MARKER_HITS+=$'\n--- NODE idle window markers ---\n' IDLE_MARKER_HITS+=$(grep -E "$IDLE_PATTERN" "$NODE_IDLE_TAIL") fi fi send_cmd "/exit" exec 3>&- wait $EDGE_PID || true kill_process_tree "$NODE_PID" echo "=== EDGE OUTPUT ===" cat "$EDGE_OUT" echo "=== NODE OUTPUT ===" cat "$NODE_OUT" echo "===================" FAIL=0 check_grep() { local pattern="$1" local file="$2" local msg="$3" if ! grep -q "$pattern" "$file"; then echo "[e2e] FAIL: $msg (pattern: '$pattern')" FAIL=1 fi } check_no_grep() { local pattern="$1" local file="$2" local msg="$3" if grep -q "$pattern" "$file"; then echo "[e2e] FAIL: $msg (found pattern: '$pattern')" grep "$pattern" "$file" FAIL=1 fi } check_sessions_output() { if grep -q "sessions:" "$EDGE_OUT"; then return fi if grep -q "count = 0" "$EDGE_OUT" && grep -q "sessions = " "$EDGE_OUT"; then return fi echo "[e2e] FAIL: /sessions output did not render grouped sessions or an empty session list" FAIL=1 } check_fail_markers() { local file="$1" # Strict failure detection for system/console errors # We catch panics, unexpected exits, startup failures, and run-level error events. if grep -i -E "panic:|unexpected|startup failed|cancel error|node reported error|does not support|adapter .* not found|no session|error run_id=|\[node-event\] error|\[[^]]+-evt\] error" "$file" >/dev/null; then echo "[e2e] FAIL: detected error marker in $file" grep -i -E "panic:|unexpected|startup failed|cancel error|node reported error|does not support|adapter .* not found|no session|error run_id=|\[node-event\] error|\[[^]]+-evt\] error" "$file" FAIL=1 fi } # collect_node_run_ids # Extracts unique run_ids from [node-event] start lines. # Returns 0 on success; sets FAIL and prints error on empty result. collect_node_run_ids() { local node_out="$1" out_file="$2" awk ' /^\[node-event\] start run_id=/ { run = $0 sub(/^\[node-event\] start run_id=/, "", run) sub(/[[:space:]].*$/, "", run) print run } ' "$node_out" | sort -u > "$out_file" if [ ! -s "$out_file" ]; then echo "[e2e] FAIL: no node run ids found in node output" FAIL=1 return 1 fi return 0 } # extract_node_messages # Extracts message payloads for a single run from node output. # Skips placeholders and non-message lines. extract_node_messages() { local node_out="$1" run_id="$2" out_file="$3" awk -v want="$run_id" ' /^\[node-event\] start run_id=/ { run = $0 sub(/^\[node-event\] start run_id=/, "", run) sub(/[[:space:]].*$/, "", run) in_message = 0 next } /^\[node-event\] complete run_id=/ { in_message = 0 next } /^\[node-message\] / { in_message = (run == want) if (run == want) { line = $0 sub(/^\[node-message\] /, "", line) if (line != "") { print line } } next } in_message && /^\[/ { in_message = 0 } in_message && /^\{/ { in_message = 0 } in_message && $0 != "" { print } ' "$node_out" > "$out_file" } # extract_edge_run_trace # Extracts a structured trace for a single run from edge output. # Output format: \t\t # Event types: START, MSG, COMPLETE, LATE_MSG extract_edge_run_trace() { local edge_out="$1" run_id="$2" out_file="$3" awk -v want="$run_id" ' $0 ~ /\[[^]]+-evt\] start run_id=/ { if (after_want_complete) { after_want_complete = 0 } run = $0 sub(/^.*\[[^]]+-evt\] start run_id=/, "", run) sub(/[[:space:]].*$/, "", run) current = run if (run == want) { print NR "\tSTART\t" } next } $0 ~ /\[[^]]+-evt\] complete run_id=/ { run = $0 sub(/^.*\[[^]]+-evt\] complete run_id=/, "", run) sub(/[[:space:]].*$/, "", run) if (run == want) { print NR "\tCOMPLETE\t" after_want_complete = 1 } if (current == run) { current = "" } next } $0 ~ /\[[^]]+-msg\] / { if (current == want) { line = $0 sub(/^.*\[[^]]+-msg\] /, "", line) if (line != "") { print NR "\tMSG\t" line } } else if (after_want_complete) { line = $0 sub(/^.*\[[^]]+-msg\] /, "", line) print NR "\tLATE_MSG\t" line } } ' "$edge_out" > "$out_file" } # extract_edge_messages # Extracts only MSG payloads from an edge run trace. extract_edge_messages() { local trace_file="$1" out_file="$2" awk -F '\t' '$2 == "MSG" { print $3 }' "$trace_file" > "$out_file" } # assert_relay_ordering # Validates that: # - edge start event exists # - all node messages reached edge before complete # - no message rendered after complete # Sets FAIL on every violation; missing start/complete also returns non-zero. assert_relay_ordering() { local run_id="$1" trace_file="$2" local edge_start_line edge_complete_line edge_last_message_line edge_late_message_line edge_start_line=$(awk -F '\t' '$2 == "START" { print $1; exit }' "$trace_file") edge_complete_line=$(awk -F '\t' '$2 == "COMPLETE" { print $1; exit }' "$trace_file") edge_last_message_line=$(awk -F '\t' '$2 == "MSG" { line = $1 } END { print line }' "$trace_file") edge_late_message_line=$(awk -F '\t' '$2 == "LATE_MSG" { print $1; exit }' "$trace_file") if [ -z "$edge_start_line" ]; then echo "[e2e] FAIL: edge start event not found for run $run_id" FAIL=1 return 1 fi if [ -z "$edge_complete_line" ]; then echo "[e2e] FAIL: edge completion not found for run $run_id" FAIL=1 return 1 fi if [ -z "$edge_last_message_line" ] || [ "$edge_complete_line" -le "$edge_last_message_line" ]; then echo "[e2e] FAIL: edge complete arrived before all rendered messages for run $run_id" echo "[e2e] complete_line=$edge_complete_line last_message_line=${edge_last_message_line:-}" FAIL=1 fi if [ -n "$edge_late_message_line" ]; then echo "[e2e] FAIL: edge rendered a message after complete for run $run_id" awk -F '\t' '$2 == "LATE_MSG" { print "[e2e] late_message_line=" $1 " payload=" $3 }' "$trace_file" FAIL=1 fi return 0 } # assert_message_payloads_match # Validates that node-generated message payloads match edge-rendered payloads exactly. # Returns 0 on success; sets FAIL on mismatch. assert_message_payloads_match() { local run_id="$1" node_msgs="$2" edge_msgs="$3" if [ ! -s "$node_msgs" ]; then echo "[e2e] FAIL: no node-generated message payloads found for run $run_id" FAIL=1 return 1 fi if [ ! -s "$edge_msgs" ]; then echo "[e2e] FAIL: no edge-rendered message payloads found for run $run_id" FAIL=1 return 1 fi if ! diff -u "$node_msgs" "$edge_msgs" > "$TMP_DIR/message_diff"; then echo "[e2e] FAIL: node message payloads differ from edge-rendered payloads for run $run_id" cat "$TMP_DIR/message_diff" FAIL=1 return 1 fi return 0 } # check_node_messages_relayed_to_edge # Top-level orchestration: iterates node runs and validates relay integrity. check_node_messages_relayed_to_edge() { local node_runs="$TMP_DIR/node_runs" local run_id local node_messages="$TMP_DIR/node_run_messages" local edge_messages="$TMP_DIR/edge_run_messages" local edge_trace="$TMP_DIR/edge_run_trace" # Step 1: collect node run ids if ! collect_node_run_ids "$NODE_OUT" "$node_runs"; then return fi # Step 2: for each run, extract and validate relay while IFS= read -r run_id; do if [ -z "$run_id" ]; then continue fi # Step 2a: extract node messages extract_node_messages "$NODE_OUT" "$run_id" "$node_messages" # Step 2b: extract edge run trace extract_edge_run_trace "$EDGE_OUT" "$run_id" "$edge_trace" # Step 2c: extract edge messages from trace extract_edge_messages "$edge_trace" "$edge_messages" # Step 2d: assert ordering (start/complete/message alignment) assert_relay_ordering "$run_id" "$edge_trace" # Step 2e: assert payload identity assert_message_payloads_match "$run_id" "$node_messages" "$edge_messages" done < "$node_runs" } check_grep "test-node" "$EDGE_OUT" "node registration not found" check_grep "start run_id=" "$EDGE_OUT" "run start not found" check_grep "complete run_id=" "$EDGE_OUT" "run completion not found" EXPECTED_RUNS=3 if [ "${IOP_E2E_RECONNECT:-0}" -eq 1 ]; then EXPECTED_RUNS=4 fi if [ "$(grep -c "\\[node0-evt\\] complete run_id=" "$EDGE_OUT" || true)" -lt "$EXPECTED_RUNS" ]; then echo "[e2e] FAIL: expected at least $EXPECTED_RUNS completed runs" FAIL=1 fi check_grep "\\[node0-msg\\].*${FIRST_EXPECTED}" "$EDGE_OUT" "foreground run 1 node message not found" check_grep "\\[node0-msg\\].*${SECOND_EXPECTED}" "$EDGE_OUT" "foreground run 2 node message not found" check_grep "\\[node0-msg\\].*${BACKGROUND_EXPECTED}" "$EDGE_OUT" "background run node message not found" if [ "${IOP_E2E_RECONNECT:-0}" -eq 1 ]; then check_grep "\\[node0-msg\\].*${FOURTH_EXPECTED}" "$EDGE_OUT" "foreground run 4 node message not found" fi if [ "$EXPECT_TAIL_MESSAGES" -eq 1 ]; then check_grep "\\[node0-msg\\].*${FIRST_TAIL_EXPECTED}" "$EDGE_OUT" "foreground run 1 tail node message not found" check_grep "\\[node0-msg\\].*${SECOND_TAIL_EXPECTED}" "$EDGE_OUT" "foreground run 2 tail node message not found" check_grep "\\[node0-msg\\].*${BACKGROUND_TAIL_EXPECTED}" "$EDGE_OUT" "background run tail node message not found" if [ "${IOP_E2E_RECONNECT:-0}" -eq 1 ]; then check_grep "\\[node0-msg\\].*${FOURTH_TAIL_EXPECTED}" "$EDGE_OUT" "foreground run 4 tail node message not found" fi fi check_no_grep "\\[node0-msg\\] " "$EDGE_OUT" "empty node message found" check_node_messages_relayed_to_edge check_grep "\\[node0-capabilities\\]" "$EDGE_OUT" "/capabilities output not found" check_grep "targets = ${TARGET}" "$EDGE_OUT" "/capabilities targets not found" check_grep "\\[node0-transport\\]" "$EDGE_OUT" "/transport output not found" check_grep "adapter = cli" "$EDGE_OUT" "/transport adapter not found" check_grep "connected = true" "$EDGE_OUT" "/transport connected status not found" check_grep "state = connected" "$EDGE_OUT" "/transport state not found" check_grep "node_id = test-node" "$EDGE_OUT" "/transport node id not found" check_grep "session_id = default" "$EDGE_OUT" "/transport session id not found" check_grep "target = ${TARGET}" "$EDGE_OUT" "/transport target not found" check_grep "\\[node0-sessions\\]" "$EDGE_OUT" "/sessions output not found" check_sessions_output if [ "$IS_APP_SERVER" -eq 1 ]; then # codex-app-server sessions are keyed on (target, sessionID); session2 is used by the background run. check_grep "mode=codex-app-server target=${TARGET} session=session2" "$EDGE_OUT" "/sessions entry for session2 not found (codex-app-server)" elif [ "$IS_PERSISTENT" -eq 1 ]; then check_grep "mode=persistent target=${TARGET} session=session2" "$EDGE_OUT" "/sessions entry for session2 not found" fi if [ "$IS_PERSISTENT" -eq 1 ]; then check_grep "terminated session" "$EDGE_OUT" "/terminate-session success not found" fi check_fail_markers "$EDGE_OUT" check_fail_markers "$NODE_OUT" if [ "$IDLE_FAIL" -eq 1 ]; then echo "[e2e] FAIL: idle window transport close markers detected" echo "$IDLE_MARKER_HITS" FAIL=1 fi REAL_PROFILE_STATUS="N/A (mock used)" if [ "$PROFILE" != "mock" ]; then if [ $FAIL -eq 0 ]; then if grep -q -E "command not found|no such file" "$NODE_OUT"; then REAL_PROFILE_STATUS="BLOCKED (command not found)" FAIL=1 elif grep -qE "login|auth|permission|token|failed: EOF" "$NODE_OUT" && ! grep -q "complete run_id=" "$EDGE_OUT"; then REAL_PROFILE_STATUS="BLOCKED (auth/login or startup failure)" FAIL=1 else REAL_PROFILE_STATUS="PASSED" fi else # Try to distinguish between BLOCKED and FAILED for real profile if grep -q -E "command not found|no such file" "$NODE_OUT"; then REAL_PROFILE_STATUS="BLOCKED (command not found)" elif grep -qE "login|auth|permission|token" "$NODE_OUT"; then REAL_PROFILE_STATUS="BLOCKED (auth/login required)" else REAL_PROFILE_STATUS="FAILED" fi fi echo "[e2e] Real profile ($PROFILE) status: $REAL_PROFILE_STATUS" fi if [ $FAIL -eq 1 ]; then echo "[e2e] Auxiliary smoke test FAILED." exit 1 fi echo "[e2e] Auxiliary smoke test PASSED." echo "[e2e] Completion still requires scripts/dev/edge.sh + scripts/dev/node.sh user-flow verification." exit 0