cat / goq
clone

turns a linux box into a private steam streaming appliance: plug in, tap your key, play from another computer

release/portal-packaging
goq / scripts / run-bazzite-hardware-uat.sh
run-bazzite-hardware-uat.sh 39.3K executable
1#!/usr/bin/env bash23set -euo pipefail45GST_INSPECT_MAX_BYTES=10485766ENCODER_PREFLIGHT_MAX_BYTES=104857678run_bounded_gst_inspect() {9 local timeout_path="$1"10 local gst_inspect_path="$2"11 local output_path="$3"12 local output_size13 shift 31415 if ! "$timeout_path" --signal=TERM --kill-after=2s 5s \16 "$gst_inspect_path" "$@" 2>/dev/null \17 | head -c "$((GST_INSPECT_MAX_BYTES + 1))" >"$output_path"18 then19 return 120 fi21 output_size="$(wc -c <"$output_path" | tr -d '[:space:]')"22 [[ "$output_size" =~ ^[0-9]+$ && "$output_size" -le "$GST_INSPECT_MAX_BYTES" ]]23}2425run_bounded_encoder_preflight() {26 local timeout_path="$1"27 local sigil_path="$2"28 local output_path="$3"29 local factory="$4"30 local render_node="$5"31 local output_size3233 if ! "$timeout_path" --signal=TERM --kill-after=2s 5s \34 "$sigil_path" capture encoder-preflight \35 --vaapi-encoder "$factory" \36 --vaapi-render-node "$render_node" \37 --rate-control cbr --rate-control cqp 2>&1 \38 | head -c "$((ENCODER_PREFLIGHT_MAX_BYTES + 1))" >"$output_path"39 then40 return 141 fi42 output_size="$(wc -c <"$output_path" | tr -d '[:space:]')"43 [[ "$output_size" =~ ^[0-9]+$ \44 && "$output_size" -le "$ENCODER_PREFLIGHT_MAX_BYTES" ]]45}4647classify_native_uat_mode() {48 local native_size="$1"49 local fixed_size="$2"5051 native_mode_relation=''52 [[ "$native_size" =~ ^[1-9][0-9]*x[1-9][0-9]*$ ]] || return 153 [[ "$fixed_size" =~ ^[1-9][0-9]*x[1-9][0-9]*$ ]] || return 154 if [[ "$native_size" == "$fixed_size" ]]; then55 native_mode_relation=identical-to-fixed56 else57 native_mode_relation=distinct-from-fixed58 fi59}6061select_gstva_h264_encoder() {62 local sigil_path="$1"63 local gst_inspect_path="$2"64 local device_root="${3:-/dev/dri}"65 local require_character="${4:-true}"66 local override_node="${5:-}"67 local override_factory="${6:-}"68 local timeout_path="${7:?timeout command is required}"69 local output_root="${8:?bounded inspection output directory is required}"70 local factory71 local factories72 local probe_output73 local candidate74 local pair75 local candidate_count=07677 render_node=''78 va_encoder=''79 va_candidates=''80 [[ -d "$output_root" && ! -L "$output_root" ]] || return 181 if ! LC_ALL=C run_bounded_gst_inspect \82 "$timeout_path" "$gst_inspect_path" "$output_root/gst-inspect.registry";83 then84 return 185 fi86 factories="$(awk '87 $2 ~ /^va(renderD[0-9]+)?h264(lp)?enc:$/ {88 sub(/:$/, "", $2)89 print $290 }91 ' "$output_root/gst-inspect.registry" | sort -u)" || return 19293 for candidate in "$device_root"/renderD*; do94 [[ "$(basename "$candidate")" =~ ^renderD[0-9]+$ ]] || continue95 [[ -e "$candidate" && ! -L "$candidate" && -r "$candidate" && -w "$candidate" ]] \96 || continue97 if [[ "$require_character" == true && ! -c "$candidate" ]]; then98 continue99 fi100101 while IFS= read -r factory; do102 [[ -n "$factory" ]] || continue103 probe_output="$output_root/encoder-preflight.$(basename "$candidate").$factory"104 if ! LC_ALL=C run_bounded_encoder_preflight \105 "$timeout_path" "$sigil_path" "$probe_output" "$factory" "$candidate";106 then107 continue108 fi109 pair="$candidate $factory"110 if [[ -n "$va_candidates" ]]; then111 va_candidates="$va_candidates112$pair"113 else114 va_candidates="$pair"115 fi116 candidate_count=$((candidate_count + 1))117 if [[ -n "$override_node" && "$candidate" == "$override_node" \118 && "$factory" == "$override_factory" ]]; then119 render_node="$candidate"120 va_encoder="$factory"121 elif [[ -z "$override_node" && "$candidate_count" -eq 1 ]]; then122 render_node="$candidate"123 va_encoder="$factory"124 fi125 done <<<"$factories"126 done127128 if [[ -n "$override_node" ]]; then129 [[ -n "$render_node" && -n "$va_encoder" ]]130 return131 fi132 if [[ "$candidate_count" -eq 1 ]]; then133 return 0134 fi135 render_node=''136 va_encoder=''137 [[ "$candidate_count" -eq 0 ]] && return 1138 return 2139}140141if [[ "${SIGIL_HARDWARE_UAT_SOURCE_ONLY:-}" == 1 ]]; then142 if [[ "${BASH_SOURCE[0]}" != "$0" ]]; then143 return 0144 fi145 exit 0146fi147148usage() {149 echo "usage: $0 UAT_ROOT UAT_COMMIT WORKFLOW_RUN_ID [--render-node PATH --va-encoder FACTORY] [--multi-viewer-evidence FILE]" >&2150}151152if [[ $# -lt 3 ]]; then153 usage154 exit 64155fi156157uat_root="$1"158uat_commit="$2"159workflow_run_id="$3"160shift 3161render_node_override=''162va_encoder_override=''163render_node_override_set=false164va_encoder_override_set=false165multi_viewer_evidence=''166while [[ $# -gt 0 ]]; do167 case "$1" in168 --render-node)169 [[ $# -ge 2 && "$render_node_override_set" == false && -n "$2" ]] \170 || { usage; exit 64; }171 render_node_override="$2"172 render_node_override_set=true173 shift 2174 ;;175 --va-encoder)176 [[ $# -ge 2 && "$va_encoder_override_set" == false && -n "$2" ]] \177 || { usage; exit 64; }178 va_encoder_override="$2"179 va_encoder_override_set=true180 shift 2181 ;;182 --multi-viewer-evidence)183 [[ $# -ge 2 && -z "$multi_viewer_evidence" && "$2" == /* ]] \184 || { usage; exit 64; }185 multi_viewer_evidence="$2"186 shift 2187 ;;188 *)189 usage190 exit 64191 ;;192 esac193done194195multi_viewer_value() {196 local key="$1"197 awk -F= -v wanted="$key" '198 $1 == wanted { count += 1; value = substr($0, index($0, "=") + 1) }199 END { if (count != 1 || value == "") exit 1; print value }200 ' "$multi_viewer_evidence"201}202203validate_multi_viewer_evidence() {204 local size mode key value205 [[ -f "$multi_viewer_evidence" && ! -L "$multi_viewer_evidence" ]]206 size="$(stat -c '%s' "$multi_viewer_evidence")"207 mode="$(stat -c '%a' "$multi_viewer_evidence")"208 [[ "$size" =~ ^[0-9]+$ && "$size" -le 65536 ]]209 [[ "$mode" =~ ^[0-7]{3,4}$ && $((8#$mode & 8#022)) -eq 0 ]]210 awk -F= '211 NF != 2 || $1 !~ /^[a-z0-9_]+$/ || $2 !~ /^[A-Za-z0-9.,_:+-]+$/ { exit 1 }212 seen[$1]++ { if (seen[$1] > 1) exit 1 }213 ' "$multi_viewer_evidence"214 [[ "$(multi_viewer_value source_commit)" == "$uat_commit" ]]215 [[ "$(multi_viewer_value protocol_mode)" == native-moq-v2 ]]216 [[ "$(multi_viewer_value concurrent_viewers)" -eq 3 ]]217 for key in shared_video_sources shared_video_encoders shared_audio_sources \218 shared_audio_encoders shared_publishers shared_adaptive_actuators slot_0_holders_max; do219 [[ "$(multi_viewer_value "$key")" -eq 1 ]]220 done221 value="$(multi_viewer_value focus_handoff_p95_ms)"222 [[ "$value" =~ ^[0-9]+$ && "$value" -le 1000 ]]223 value="$(multi_viewer_value reconnect_cycles)"224 [[ "$value" =~ ^[0-9]+$ && "$value" -ge 3 ]]225 value="$(multi_viewer_value rss_p95_mib)"226 [[ "$value" =~ ^[0-9]+$ && "$value" -le 2048 ]]227 for key in media_progress audio_progress slow_viewer_isolation same_peer_replacement \228 live_view_revocation survivors_uninterrupted bounded_queues final_cleanup \229 ordinary_service_restored; do230 [[ "$(multi_viewer_value "$key")" == pass ]]231 done232}233if [[ "$render_node_override_set" == true || "$va_encoder_override_set" == true ]]; then234 [[ "$render_node_override_set" == true && "$va_encoder_override_set" == true ]] || {235 echo "--render-node and --va-encoder must be provided together" >&2236 exit 64237 }238fi239short_commit="${uat_commit:0:12}"240241[[ "$uat_commit" =~ ^[0-9a-f]{40}$ ]] || {242 echo "invalid UAT commit" >&2243 exit 64244}245[[ "$workflow_run_id" =~ ^[0-9]+$ ]] || {246 echo "invalid workflow run ID" >&2247 exit 64248}249case "$uat_root" in250 "$HOME"/.local/state/goq-hardware-uat."$short_commit".??????) ;;251 *) echo "unsafe UAT root: $uat_root" >&2; exit 64 ;;252esac253for trusted_path in "$HOME/.local" "$HOME/.local/state" "$uat_root" "$uat_root/incoming"; do254 [[ -d "$trusted_path" && ! -L "$trusted_path" ]] || {255 echo "UAT path is missing or symlinked: $trusted_path" >&2256 exit 64257 }258done259[[ "$(stat -c '%a:%U' "$uat_root")" == "700:$(id -un)" ]] || {260 echo "UAT root is not owner-only" >&2261 exit 64262}263264umask 077265XDG_RUNTIME_DIR="/run/user/$(id -u)"266export XDG_RUNTIME_DIR267268lock_file="$HOME/.local/state/goq-hardware-uat.lock"269[[ ! -L "$lock_file" ]] || {270 echo "unsafe UAT lock path: $lock_file" >&2271 exit 64272}273exec {uat_lock_fd}>"$lock_file"274if ! flock -n "$uat_lock_fd"; then275 echo "another hardware UAT owns the host-wide lock" >&2276 exit 75277fi278279root_suffix="${uat_root##*.}"280[[ "$root_suffix" =~ ^[[:alnum:]]{6}$ ]] || {281 echo "invalid UAT root suffix" >&2282 exit 64283}284invocation_id="$short_commit-$workflow_run_id-$root_suffix"285286incoming="$uat_root/incoming"287archive="$incoming/sigil-hardware-uat-$short_commit-bazzite-x86_64.tar.gz"288payload_root="$uat_root/candidate"289private="$uat_root/private"290evidence="$uat_root/evidence"291raw="$private/raw"292for new_path in "$payload_root" "$private" "$evidence"; do293 [[ ! -e "$new_path" && ! -L "$new_path" ]] || {294 echo "UAT output path already exists: $new_path" >&2295 exit 64296 }297done298install -d -m 0700 "$payload_root" "$private" "$evidence" "$raw"299for new_path in "$payload_root" "$private" "$evidence" "$raw"; do300 [[ -d "$new_path" && ! -L "$new_path" ]]301 [[ "$(stat -c '%a:%U' "$new_path")" == "700:$(id -un)" ]]302done303304real_config="$HOME/.config/sigil-spark/host.toml"305real_sigil="$HOME/.local/libexec/sigil-spark/current/sigil"306real_identity="$(sed -n 's/^identity_path = "\(.*\)"$/\1/p' "$real_config")"307uinput_block="$(awk '308 /^\[uinput\]$/ { emit = 1 }309 emit && /^\[/ && $0 != "[uinput]" { exit }310 emit { print }311' "$real_config")"312[[ -n "$real_identity" && -f "$real_identity" ]]313[[ "$uinput_block" == '[uinput]'$'\n'* ]]314[[ -x "$real_sigil" ]]315original_ready() {316 local invocation pid317 systemctl --user is-active --quiet sigil-host.service || return 1318 pid="$(systemctl --user show -p MainPID --value sigil-host.service)"319 invocation="$(systemctl --user show -p InvocationID --value sigil-host.service)"320 [[ "$pid" =~ ^[0-9]+$ && "$pid" -gt 1 ]] || return 1321 [[ "$invocation" =~ ^[0-9a-f]{32}$ ]] || return 1322 cmp -s "$real_sigil" "/proc/$pid/exe" || return 1323 journalctl --user _SYSTEMD_INVOCATION_ID="$invocation" \324 _SYSTEMD_USER_UNIT=sigil-host.service _PID="$pid" --no-pager -o cat \325 | grep -Fx status=ready >/dev/null326}327original_ready328original_enabled="$(systemctl --user is-enabled sigil-host.service)"329original_main_pid="$(systemctl --user show -p MainPID --value sigil-host.service)"330[[ "$original_main_pid" =~ ^[0-9]+$ && "$original_main_pid" -gt 1 ]]331sha256sum "$real_config" >"$private/original-config.sha256"332readlink "$HOME/.local/libexec/sigil-spark/current" >"$private/original-current-link"333stat -c '%i:%s:%Y:%a:%U:%G' "$real_identity" >"$private/original-identity.stat"334sha256sum "$real_identity" >"$private/original-identity.sha256"335336[[ -f "$archive" && -f "$archive.sha256" ]]337[[ -f "$incoming/release-manifest.json" && -f "$incoming/uat-provenance.txt" ]]338(339 cd "$incoming"340 sha256sum -c "$(basename -- "$archive.sha256")"341)342grep -Fxq "source_commit=$uat_commit" "$incoming/uat-provenance.txt"343grep -Fxq "workflow_run_id=$workflow_run_id" "$incoming/uat-provenance.txt"344345tar -tzf "$archive" >"$private/archive-members.txt"346if grep -Eq '(^/|(^|/)\.\.(/|$))' "$private/archive-members.txt"; then347 echo "archive contains an unsafe member" >&2348 exit 1349fi350tar --no-same-owner -xzf "$archive" -C "$payload_root"351(352 cd "$payload_root/payload"353 sha256sum -c PACKAGE-SHA256SUMS354)355(356 cd "$payload_root/payload/release"357 sha256sum -c SHA256SUMS358)359360sigil="$payload_root/payload/release/sigil"361probe="$payload_root/payload/release/sigil-probe"362manifest="$payload_root/payload/release/release-manifest.json"363cmp "$manifest" "$incoming/release-manifest.json"364jq -e --arg commit "$uat_commit" '365 .git_commit == $commit366 and .git_dirty == false367 and .binary_provenance == "self-built-clean-head"368 and .binary_provenance_verified == true369 and .release_kind == "development"370 and .release_tag == "development"371 and .target == "x86_64-unknown-linux-gnu.2.17"372 and .features == ["in-process-gstreamer"]373 and .demo_auth_bypass == false374 and .demo_direct_node == false375' "$manifest" >/dev/null376[[ -x "$sigil" && -x "$probe" ]]377378readelf -d "$sigil" >"$private/sigil.dynamic.txt"379if grep -Eq '\((RPATH|RUNPATH)\)' "$private/sigil.dynamic.txt"; then380 echo "candidate contains an unexpected runtime library path" >&2381 exit 1382fi383for soname in \384 libgstreamer-1.0.so \385 libgstapp-1.0.so \386 libgstbase-1.0.so \387 libgstvideo-1.0.so \388 libgobject-2.0.so \389 libglib-2.0.so390do391 grep -Fq "Shared library: [$soname" "$private/sigil.dynamic.txt"392done393readelf --version-info "$sigil" >"$private/sigil.versions.txt"394max_glibc="$({395 sed -n 's/.*Name: GLIBC_\([0-9][0-9.]*\).*/\1/p' "$private/sigil.versions.txt"396 echo 0397} | sort -V | tail -n 1)"398[[ "$(printf '%s\n' "$max_glibc" 2.17 | sort -V | tail -n 1)" == 2.17 ]] || {399 echo "candidate requires GLIBC_$max_glibc, above 2.17" >&2400 exit 1401}402ldd "$sigil" >"$private/sigil.ldd"403ldd "$probe" >"$private/sigil-probe.ldd"404if grep -q 'not found' "$private/sigil.ldd"; then405 echo "candidate host has an unresolved dynamic library" >&2406 exit 1407fi408if grep -q 'not found' "$private/sigil-probe.ldd"; then409 echo "candidate probe has an unresolved dynamic library" >&2410 exit 1411fi412timeout --signal=TERM --kill-after=2s 5s "$sigil" --version413timeout --signal=TERM --kill-after=2s 5s "$probe" --version414415host_identity="$private/host.key"416probe_identity="$private/probe.key"417state="$private/state"418external_cqp_state="$private/external-cqp-state"419fixed_config="$private/host-1280x800.toml"420external_cqp_config="$private/host-1280x800-external-cqp.toml"421native_config="$private/host-native.toml"422invitation="$private/probe.goq-invite"423424"$sigil" identity init --output "$host_identity" >"$private/host-identity.log"425"$sigil" identity init --output "$probe_identity" >"$private/probe-identity.log"426host_node_id="$(sed -n 's/^node_id=//p' "$private/host-identity.log")"427probe_node_id="$(sed -n 's/^node_id=//p' "$private/probe-identity.log")"428[[ -n "$host_node_id" && -n "$probe_node_id" ]]429430pw_dump="$(command -v pw-dump)"431pw_link="$(command -v pw-link)"432xprop="$(command -v xprop)"433gst_launch="$(command -v gst-launch-1.0)"434gst_inspect="$(command -v gst-inspect-1.0)"435timeout_command="$(command -v timeout)"436ffmpeg="$(command -v ffmpeg)"437[[ -S "$XDG_RUNTIME_DIR/pipewire-0" ]]438selection_status=0439select_gstva_h264_encoder \440 "$sigil" "$gst_inspect" /dev/dri true \441 "$render_node_override" "$va_encoder_override" \442 "$timeout_command" "$private" || selection_status=$?443if [[ "$selection_status" -ne 0 ]]; then444 if [[ -n "$render_node_override" ]]; then445 echo "the requested render-node and VA-encoder pair is not eligible for this UAT" >&2446 elif [[ "$selection_status" -eq 2 ]]; then447 echo "multiple GstVA H.264 encoder pairs are eligible; select one exact pair with --render-node and --va-encoder" >&2448 else449 echo "no accessible render node has a GstVA H.264 factory satisfying the CBR and CQP UAT contract" >&2450 fi451 if [[ -n "$va_candidates" ]]; then452 while IFS= read -r pair; do453 printf 'eligible_pair=%s\n' "$pair" >&2454 done <<<"$va_candidates"455 fi456 exit 1457fi458if [[ -n "$render_node_override" ]]; then459 vaapi_selection_mode=explicit460else461 vaapi_selection_mode=automatic-unique462fi463{464 echo "vaapi_render_node=$render_node"465 echo "vaapi_encoder=$va_encoder"466 echo "vaapi_selection_mode=$vaapi_selection_mode"467} >"$private/vaapi-selection.env"468469cat >"$fixed_config" <<EOF470identity_path = "$host_identity"471state_path = "$state"472source = "gamescope-pipewire"473width = 1280474height = 800475framerate = 60476codec = "h264"477input_mode = "uinput"478ffmpeg_path = "$ffmpeg"479480$uinput_block481482[gamescope_pipewire]483node_name = "gamescope"484media_class = "Video/Source"485xwayland_display = ":0"486pw_dump_path = "$pw_dump"487gst_launch_path = "$gst_launch"488gst_inspect_path = "$gst_inspect"489encoder_backend = "in-process-gstreamer"490vaapi_encoder = "$va_encoder"491vaapi_render_node = "$render_node"492rate_control = "cbr"493bitrate_kbps = 12000494EOF495496cat >"$native_config" <<EOF497identity_path = "$host_identity"498state_path = "$state"499source = "gamescope-pipewire"500framerate = 60501codec = "h264"502input_mode = "uinput"503ffmpeg_path = "$ffmpeg"504505$uinput_block506507[gamescope_pipewire]508node_name = "gamescope"509media_class = "Video/Source"510xwayland_display = ":0"511pw_dump_path = "$pw_dump"512gst_launch_path = "$gst_launch"513gst_inspect_path = "$gst_inspect"514encoder_backend = "in-process-gstreamer"515vaapi_encoder = "$va_encoder"516vaapi_render_node = "$render_node"517rate_control = "cbr"518bitrate_kbps = 12000519EOF520521cat >"$external_cqp_config" <<EOF522identity_path = "$host_identity"523state_path = "$external_cqp_state"524source = "gamescope-pipewire"525width = 1280526height = 800527framerate = 60528codec = "h264"529input_mode = "log"530ffmpeg_path = "$ffmpeg"531532[gamescope_pipewire]533node_name = "gamescope"534media_class = "Video/Source"535xwayland_display = ":0"536pw_dump_path = "$pw_dump"537gst_launch_path = "$gst_launch"538gst_inspect_path = "$gst_inspect"539encoder_backend = "external-gst-launch"540vaapi_encoder = "$va_encoder"541vaapi_render_node = "$render_node"542rate_control = "cqp"543quantizer = 24544EOF545chmod 0600 "$fixed_config" "$external_cqp_config" "$native_config"546547"$sigil" config check --config "$fixed_config" >"$private/fixed-config-check.log"548"$sigil" config check --config "$external_cqp_config" \549 >"$private/external-cqp-config-check.log"550"$sigil" config check --config "$native_config" >"$private/native-config-check.log"551grep -Fxq config=ok "$private/fixed-config-check.log"552grep -Fxq capture_preflight=ok "$private/fixed-config-check.log"553grep -Fxq encoded_mode=1280x800@60 "$private/fixed-config-check.log"554grep -Fxq config=ok "$private/external-cqp-config-check.log"555grep -Fxq capture_preflight=ok "$private/external-cqp-config-check.log"556grep -Fxq encoded_mode=1280x800@60 "$private/external-cqp-config-check.log"557grep -Fxq config=ok "$private/native-config-check.log"558grep -Fxq capture_preflight=ok "$private/native-config-check.log"559native_size="$(sed -n 's/^encoded_mode=\([0-9][0-9]*x[0-9][0-9]*\)@60$/\1/p' "$private/native-config-check.log")"560classify_native_uat_mode "$native_size" 1280x800561562"$sigil" invitation create \563 --config "$fixed_config" \564 --peer "$probe_node_id" \565 --expires-in-seconds 900 \566 --pointer-keyboard \567 --output "$invitation" \568 >"$private/invitation-create.log"569[[ "$(stat -c %a "$invitation")" == 600 ]]570571restore_required=false572capture_probe_pid=''573rollback_units=()574rollback_helper="$private/rollback-original.sh"575fixed_candidate_unit="goq-uat-fixed-candidate-$invocation_id"576native_candidate_unit="goq-uat-native-candidate-$invocation_id"577candidate_units=("$fixed_candidate_unit" "$native_candidate_unit")578active_candidate_unit=""579580cat >"$rollback_helper" <<EOF581#!/usr/bin/env bash582set -u583XDG_RUNTIME_DIR='$XDG_RUNTIME_DIR'584export XDG_RUNTIME_DIR585real_sigil='$real_sigil'586real_config='$real_config'587for candidate_unit in '$fixed_candidate_unit' '$native_candidate_unit'; do588 systemctl --user stop "\$candidate_unit.service" >/dev/null 2>&1 || true589 systemctl --user reset-failed "\$candidate_unit.service" >/dev/null 2>&1 || true590done591original_ready() {592 local invocation pid593 systemctl --user is-active --quiet sigil-host.service || return 1594 pid="\$(systemctl --user show -p MainPID --value sigil-host.service)"595 invocation="\$(systemctl --user show -p InvocationID --value sigil-host.service)"596 [[ "\$pid" =~ ^[0-9]+\$ && "\$pid" -gt 1 ]] || return 1597 [[ "\$invocation" =~ ^[0-9a-f]{32}\$ ]] || return 1598 cmp -s "\$real_sigil" "/proc/\$pid/exe" || return 1599 journalctl --user _SYSTEMD_INVOCATION_ID="\$invocation" \600 _SYSTEMD_USER_UNIT=sigil-host.service _PID="\$pid" --no-pager -o cat \601 | grep -Fx status=ready >/dev/null602}603for _ in \$(seq 1 60); do604 systemctl --user reset-failed sigil-host.service >/dev/null 2>&1 || true605 systemctl --user start sigil-host.service >/dev/null 2>&1 || true606 sleep 5607 if systemctl --user is-active --quiet sigil-host.service && original_ready; then608 exit 0609 fi610done611exit 1612EOF613chmod 0700 "$rollback_helper"614615stop_candidate() {616 local candidate_unit617 for candidate_unit in "${candidate_units[@]}"; do618 systemctl --user stop "$candidate_unit.service" >/dev/null 2>&1 || true619 systemctl --user reset-failed "$candidate_unit.service" >/dev/null 2>&1 || true620 done621 active_candidate_unit=""622}623624disarm_rollbacks() {625 local unit626 for unit in "${rollback_units[@]}"; do627 systemctl --user stop "$unit.timer" "$unit.service" 2>/dev/null || true628 done629}630631restore_original() {632 stop_candidate633 if $restore_required || ! systemctl --user is-active --quiet sigil-host.service; then634 systemctl --user reset-failed sigil-host.service || true635 systemctl --user start sigil-host.service || return 1636 for _ in $(seq 1 60); do637 if systemctl --user is-active --quiet sigil-host.service && original_ready; then638 restore_required=false639 disarm_rollbacks640 return 0641 fi642 sleep 0.5643 done644 return 1645 fi646 disarm_rollbacks647}648649cleanup() {650 local status=$?651 trap - EXIT652 trap '' INT TERM HUP653 if [[ -n "$capture_probe_pid" ]] && kill -0 "$capture_probe_pid" 2>/dev/null; then654 kill "$capture_probe_pid" 2>/dev/null || true655 wait "$capture_probe_pid" 2>/dev/null || true656 capture_probe_pid=''657 fi658 if ! restore_original; then659 echo "CRITICAL: original Sigil service did not return ready; rollback timer remains armed" >&2660 status=1661 fi662 exit "$status"663}664trap cleanup EXIT665trap 'exit 130' INT666trap 'exit 143' TERM667trap 'exit 129' HUP668669arm_rollback() {670 local unit="$1"671 systemd-run --user --unit="$unit" --on-active=20m "$rollback_helper" >/dev/null672 systemctl --user is-active --quiet "$unit.timer"673 rollback_units+=("$unit")674}675676start_candidate() {677 local mode="$1"678 local config="$2"679 host_log="$raw/$mode-host.log"680 case "$mode" in681 fixed) active_candidate_unit="$fixed_candidate_unit" ;;682 native) active_candidate_unit="$native_candidate_unit" ;;683 *) echo "unknown candidate mode: $mode" >&2; return 1 ;;684 esac685 : >"$host_log"686 systemd-run --user --quiet --collect \687 --unit="$active_candidate_unit" \688 --service-type=exec \689 --property="StandardOutput=append:$host_log" \690 --property="StandardError=append:$host_log" \691 --property=TimeoutStopSec=10s \692 --setenv=RUST_LOG=info,sigil::server=debug \693 "$sigil" serve --config "$config" --max-runtime-seconds 1080694 for _ in $(seq 1 300); do695 grep -Fxq status=ready "$host_log" 2>/dev/null && break696 systemctl --user is-active --quiet "$active_candidate_unit.service"697 sleep 0.1698 done699 grep -Fxq status=ready "$host_log"700 grep -Fxq "node_id=$host_node_id" "$host_log"701}702703validate_probe() {704 local log="$1"705 local transport="$2"706 local size="$3"707 local request_id="$4"708 local slow_consumer_ms="${5:-}"709 local expected_path_mode="${6:-direct}"710 grep -Fxq probe=ok "$log"711 grep -Fxq "dimensions=$size" "$log"712 grep -Fxq "transport=$transport" "$log"713 grep -Fxq sequence_gaps=0 "$log"714 grep -Fxq first_configured_idr=ok "$log"715 grep -Fxq frame_sequence=monotonic "$log"716 grep -Fxq recovery_barrier=configured-idr "$log"717 grep -Fxq keyframe_recovery=ok "$log"718 grep -Fxq "keyframe_request_id=$request_id" "$log"719 grep -Fxq "path_mode=$expected_path_mode" "$log"720 grep -Fxq "media_path_mode=$expected_path_mode" "$log"721 grep -Fxq "input_path_mode=$expected_path_mode" "$log"722 grep -Fxq "forced_relay=$([[ "$expected_path_mode" == relay ]] && printf ok || printf not-requested)" "$log"723 awk -F= '$1 == "keyframe_recovery_micros" && $2 ~ /^[0-9]+$/ && $2 <= 2000000 { ok=1 } END { exit !ok }' "$log"724 awk -F= '$1 == "input_ack_micros" && $2 ~ /^[0-9]+$/ { ok=1 } END { exit !ok }' "$log"725 if [[ "$transport" == iroh-moq ]]; then726 grep -Fxq "control_path_mode=$expected_path_mode" "$log"727 grep -Fxq control_alpn=sigil/control/1 "$log"728 grep -Fxq moq_group_capacity=1 "$log"729 grep -Fxq moq_unrecovered_group_gaps=0 "$log"730 grep -Fxq moq_historical_suffix_frames=0 "$log"731 fi732 if [[ -n "$slow_consumer_ms" ]]; then733 grep -Fxq slow_consumer=ok "$log"734 grep -Fxq "slow_consumer_stall_ms=$slow_consumer_ms" "$log"735 grep -Fxq slow_consumer_first_post_stall=configured-idr "$log"736 grep -Fxq slow_consumer_historical_suffix_frames=0 "$log"737 awk -F= '$1 == "slow_consumer_recovery_micros" && $2 ~ /^[0-9]+$/ && $2 <= 2000000 { ok=1 } END { exit !ok }' "$log"738 awk -F= '$1 == "slow_consumer_cancellation_delta" && $2 ~ /^[0-9]+$/ && $2 > 0 { ok=1 } END { exit !ok }' "$log"739 awk -F= '$1 == "slow_consumer_group_advance" && $2 ~ /^[0-9]+$/ && $2 > 0 { ok=1 } END { exit !ok }' "$log"740 awk -F= '$1 == "slow_consumer_sequence_advance" && $2 ~ /^[0-9]+$/ && $2 > 0 { ok=1 } END { exit !ok }' "$log"741 awk -F= -v minimum="$((slow_consumer_ms * 500))" '$1 == "slow_consumer_capture_advance_micros" && $2 ~ /^[0-9]+$/ && $2 >= minimum { ok=1 } END { exit !ok }' "$log"742 awk -F= '$1 == "slow_consumer_input_ack_micros" && $2 ~ /^[0-9]+$/ && $2 <= 2000000 { ok=1 } END { exit !ok }' "$log"743 else744 grep -Fxq slow_consumer=not-requested "$log"745 fi746}747748wait_for_count() {749 local log="$1"750 local text="$2"751 local expected="$3"752 local deadline=$((SECONDS + 20))753 local count754 while ((SECONDS < deadline)); do755 count="$(grep -Fc -- "$text" "$log" 2>/dev/null || true)"756 [[ "$count" -ge "$expected" ]] && return 0757 [[ -n "$active_candidate_unit" ]] \758 && systemctl --user is-active --quiet "$active_candidate_unit.service" \759 || return 1760 sleep 0.1761 done762 return 1763}764765run_probe_cycle() {766 local mode="$1"767 local transport="$2"768 local size="$3"769 local request_id="$4"770 local invitation_path="${5:-}"771 local slow_consumer_ms="${6:-}"772 local expected_path_mode="${7:-direct}"773 local pointer_feedback_smoke="${8:-false}"774 local log="$raw/$mode-$transport-$request_id.log"775 local command=(776 "$probe"777 --node-id "$host_node_id"778 --identity "$probe_identity"779 --frames 30780 --timeout-seconds 20781 --expect-size "$size"782 --keyframe-smoke783 --keyframe-request-id "$request_id"784 )785 [[ "$transport" == grouped-v3 ]] && command+=(--media-v3)786 [[ -n "$invitation_path" ]] && command+=(--invitation "$invitation_path")787 [[ -n "$slow_consumer_ms" ]] && command+=(--slow-consumer-ms "$slow_consumer_ms")788 [[ "$expected_path_mode" != relay ]] || command+=(--relay-only)789 if [[ "$pointer_feedback_smoke" == true ]]; then790 command+=(--pointer-smoke --pointer-feedback-smoke)791 fi792 timeout --signal=TERM --kill-after=5s 45s "${command[@]}" >"$log" 2>&1793 validate_probe "$log" "$transport" "$size" "$request_id" "$slow_consumer_ms" "$expected_path_mode"794 if [[ "$pointer_feedback_smoke" == true ]]; then795 grep -Fxq pointer_smoke=ok "$log"796 grep -Fxq pointer_feedback_smoke=ok "$log"797 grep -Eq '^pointer_sync_position=[0-9]+,[0-9]+$' "$log"798 grep -Eq '^pointer_motion_position=[0-9]+,[0-9]+$' "$log"799 fi800}801802validate_host_recovery() {803 local log="$1"804 local plain805 plain="$private/$(basename -- "$log").plain"806 wait_for_count "$log" 'MoQ control client released' 10807 wait_for_count "$log" 'media v3 client released' 10808 wait_for_count "$log" 'input client released' 20809 sed -E $'s/\x1B\\[[0-9;]*[[:alpha:]]//g' "$log" >"$plain"810 [[ "$(grep -Fc 'MoQ control client released' "$plain")" -eq 10 ]]811 [[ "$(grep -Fc 'media v3 client released' "$plain")" -eq 10 ]]812 [[ "$(grep -Fc 'input client released' "$plain")" -eq 20 ]]813 [[ "$(grep -Fc 'forced-IDR recovery acknowledged' "$plain")" -eq 20 ]]814 [[ "$(grep -Fc 'forced_idr_disposition=Requested' "$plain")" -eq 20 ]]815 [[ "$(grep -Fc 'coalesced=false' "$plain")" -ge 20 ]]816 ! grep -Eq 'forced-IDR request failed|forced-IDR recovery was not acknowledged|forced-IDR acknowledgement task failed|forced-IDR acknowledgement task ended without a result' "$plain"817}818819capture_inventory() {820 local phase="$1"821 ps -u "$(id -u)" -o pid=,comm=,args= >"$evidence/$phase-processes.txt"822 awk -v own_pid="$$" '823 $1 == own_pid { next }824 ($2 == "sigil" || $2 == "sigil-host") &&825 ($0 ~ / serve( |$)/ || $0 ~ / capture probe( |$)/) { print; next }826 $2 ~ /^gst-launch/ && $0 ~ /pipewiresrc/ { print }827 ' "$evidence/$phase-processes.txt" >"$evidence/$phase-conflicts.txt"828 if [[ -s "$evidence/$phase-conflicts.txt" ]]; then829 echo "a Sigil or GStreamer PipeWire capture process survived $phase" >&2830 cat "$evidence/$phase-conflicts.txt" >&2831 return 1832 fi833 "$pw_link" -l >"$evidence/$phase-pipewire-links.txt"834 if grep -Fiq gamescope "$evidence/$phase-pipewire-links.txt"; then835 echo "a Gamescope PipeWire consumer link survived $phase" >&2836 cat "$evidence/$phase-pipewire-links.txt" >&2837 return 1838 fi839 "$pw_dump" >"$evidence/$phase-pipewire.json"840}841842validate_capture_log() {843 local log="$1"844 grep -Fxq probe=ok "$log"845 grep -Fxq dropped_after_encode_before_probe_consumer=0 "$log"846}847848run_capture_probe() {849 local name="$1"850 local config="$2"851 local size="$3"852 local minimum_fps="${4:-}"853 local command=(854 "$sigil" capture probe --source gamescope-pipewire855 --config "$config" --frames 300 --expect-size "$size"856 )857 [[ -z "$minimum_fps" ]] || command+=(--minimum-fps "$minimum_fps")858 capture_inventory "pre-$name"859 timeout --signal=TERM --kill-after=5s 90s "${command[@]}" \860 >"$evidence/$name-capture.log"861 validate_capture_log "$evidence/$name-capture.log"862 capture_inventory "post-$name"863}864865fixed_timer="goq-uat-fixed-$invocation_id"866arm_rollback "$fixed_timer"867restore_required=true868systemctl --user stop sigil-host.service869if systemctl --user is-active --quiet sigil-host.service; then870 echo "original Sigil service did not stop" >&2871 exit 1872fi873[[ "$(systemctl --user show -p MainPID --value sigil-host.service)" -eq 0 ]]874875run_capture_probe fixed "$fixed_config" 1280x800 55876877capture_inventory pre-external-cqp878timeout --signal=TERM --kill-after=5s 90s \879 "$sigil" capture probe --source gamescope-pipewire \880 --config "$external_cqp_config" --frames 300 --expect-size 1280x800 \881 --minimum-fps 55 >"$evidence/external-cqp-capture.log" &882capture_probe_pid=$!883external_link_ready=false884for _ in $(seq 1 200); do885 if ! kill -0 "$capture_probe_pid" 2>/dev/null; then886 break887 fi888 if "$pw_link" -l | grep -Fiq gamescope; then889 external_link_ready=true890 break891 fi892 sleep 0.05893done894if [[ "$external_link_ready" != true ]]; then895 echo "external CQP probe never opened a Gamescope PipeWire consumer link" >&2896 exit 1897fi898if timeout --signal=TERM --kill-after=2s 10s \899 "$sigil" capture probe --source gamescope-pipewire \900 --config "$fixed_config" --frames 1 --expect-size 1280x800 \901 >"$evidence/contended-capture.out" 2>"$evidence/contended-capture.error"; then902 echo "a second Gamescope capture probe overlapped external CQP capture" >&2903 exit 1904fi905grep -Fq 'another Sigil daemon or capture probe already owns this lifecycle scope' \906 "$evidence/contended-capture.error"907wait "$capture_probe_pid"908capture_probe_pid=''909validate_capture_log "$evidence/external-cqp-capture.log"910capture_inventory post-external-cqp911912start_candidate fixed "$fixed_config"913run_probe_cycle fixed iroh-moq 1280x800 1001 "$invitation" 1500 relay914wait_for_count "$raw/fixed-host.log" 'MoQ control client released' 1915wait_for_count "$raw/fixed-host.log" 'input client released' 1916enrollment="$("$sigil" enrollment show --config "$fixed_config")"917grep -Fxq enrollment=active <<<"$enrollment"918grep -Fxq "peer_node_id=$probe_node_id" <<<"$enrollment"919grep -Fxq grants=view,pointer-keyboard <<<"$enrollment"920if timeout --signal=TERM --kill-after=5s 20s \921 "$probe" --node-id "$host_node_id" --identity "$probe_identity" \922 --invitation "$invitation" --frames 4 >"$raw/invitation-replay.log" 2>&1; then923 echo "redeemed invitation replay was accepted" >&2924 exit 1925fi926grep -Fq 'host rejected control stream: Portal peer is not authorized' \927 "$raw/invitation-replay.log"928mv "$invitation" "$private/redeemed-probe.goq-invite"929for cycle in $(seq 2 10); do930 run_probe_cycle fixed iroh-moq 1280x800 "$((1000 + cycle))"931done932for cycle in $(seq 1 10); do933 run_probe_cycle fixed grouped-v3 1280x800 "$((1100 + cycle))"934done935validate_host_recovery "$raw/fixed-host.log"936937fixed_candidate_pid="$(systemctl --user show -p MainPID --value \938 "$fixed_candidate_unit.service")"939[[ "$fixed_candidate_pid" =~ ^[0-9]+$ && "$fixed_candidate_pid" -gt 1 ]]940pointer_reconnects_before="$(grep -Fc \941 'Gamescope Xwayland pointer feedback reconnected' "$raw/fixed-host.log" || true)"942for cycle in 1 2; do943 systemctl --user restart gamescope-session-plus@steam.service944 gamescope_ready=false945 for _ in $(seq 1 600); do946 if systemctl --user is-active --quiet gamescope-session-plus@steam.service \947 && DISPLAY=:0 "$xprop" -root GAMESCOPE_MOUSE_FOCUS_DISPLAY >/dev/null 2>&1 \948 && "$pw_dump" | jq -e '949 .[]950 | select(.type == "PipeWire:Interface:Node")951 | select(.info.props["node.name"] == "gamescope")952 | select(.info.props["media.class"] == "Video/Source")953 ' >/dev/null954 then955 gamescope_ready=true956 break957 fi958 sleep 0.1959 done960 [[ "$gamescope_ready" == true ]]961 [[ "$(systemctl --user show -p MainPID --value \962 "$fixed_candidate_unit.service")" == "$fixed_candidate_pid" ]]963 expected_reconnects="$((pointer_reconnects_before + cycle))"964 for _ in $(seq 1 100); do965 reconnects="$(grep -Fc 'Gamescope Xwayland pointer feedback reconnected' \966 "$raw/fixed-host.log" || true)"967 [[ "$reconnects" -ge "$expected_reconnects" ]] && break968 sleep 0.1969 done970 [[ "$reconnects" -ge "$expected_reconnects" ]]971 run_probe_cycle "pointer-recovery-$cycle" iroh-moq 1280x800 \972 "$((1200 + cycle))" "" "" direct true973 sed -E $'s/\x1B\\[[0-9;]*[[:alpha:]]//g' \974 "$raw/pointer-recovery-$cycle-iroh-moq-$((1200 + cycle)).log" \975 >"$evidence/pointer-recovery-$cycle.log"976done977stop_candidate978979native_timer="goq-uat-native-$invocation_id"980arm_rollback "$native_timer"981systemctl --user stop "$fixed_timer.timer" "$fixed_timer.service" 2>/dev/null || true982run_capture_probe native "$native_config" "$native_size"983984start_candidate native "$native_config"985run_probe_cycle native iroh-moq "$native_size" 2001 "" 1500986for cycle in $(seq 2 10); do987 run_probe_cycle native iroh-moq "$native_size" "$((2000 + cycle))"988done989for cycle in $(seq 1 10); do990 run_probe_cycle native grouped-v3 "$native_size" "$((2100 + cycle))"991done992validate_host_recovery "$raw/native-host.log"993994install -d -m 0700 "$evidence/probes"995for log in \996 "$raw"/fixed-iroh-moq-*.log \997 "$raw"/fixed-grouped-v3-*.log \998 "$raw"/native-iroh-moq-*.log \999 "$raw"/native-grouped-v3-*.log1000do1001 grep -vE '^session_id=' "$log" >"$evidence/probes/$(basename -- "$log")"1002done10031004summarize_group() {1005 local prefix="$1"1006 local values="$private/$prefix.recovery-values"1007 local files=("$evidence/probes/$prefix"-*.log)1008 local raw_files=("$raw/$prefix"-*.log)1009 local count unique_sessions p50_index p95_index p50 p95 maximum gaps1010 [[ "${#files[@]}" -eq 10 ]]1011 [[ "${#raw_files[@]}" -eq 10 ]]1012 awk -F= '$1 == "keyframe_recovery_micros" { print $2 }' "${files[@]}" | sort -n >"$values"1013 count="$(wc -l <"$values" | tr -d ' ')"1014 [[ "$count" -eq 10 ]]1015 unique_sessions="$(awk -F= '$1 == "session_id" { print $2 }' "${raw_files[@]}" | sort -u | wc -l | tr -d ' ')"1016 [[ "$unique_sessions" -eq 10 ]]1017 p50_index=$(((count * 50 + 99) / 100))1018 p95_index=$(((count * 95 + 99) / 100))1019 p50="$(sed -n "${p50_index}p" "$values")"1020 p95="$(sed -n "${p95_index}p" "$values")"1021 maximum="$(tail -n 1 "$values")"1022 gaps="$(awk -F= '$1 == "sequence_gaps" { sum += $2 } END { print sum + 0 }' "${files[@]}")"1023 [[ "$gaps" -eq 0 && "$p95" -le 2000000 ]]1024 printf '%s_cycles=%s\n' "$prefix" "$count"1025 printf '%s_unique_sessions=%s\n' "$prefix" "$unique_sessions"1026 printf '%s_sequence_gaps=%s\n' "$prefix" "$gaps"1027 printf '%s_recovery_p50_micros=%s\n' "$prefix" "$p50"1028 printf '%s_recovery_p95_micros=%s\n' "$prefix" "$p95"1029 printf '%s_recovery_max_micros=%s\n' "$prefix" "$maximum"1030}10311032all_raw_probes=(1033 "$raw"/fixed-iroh-moq-*.log1034 "$raw"/fixed-grouped-v3-*.log1035 "$raw"/native-iroh-moq-*.log1036 "$raw"/native-grouped-v3-*.log1037)1038[[ "${#all_raw_probes[@]}" -eq 40 ]]1039fixed_unique_sessions="$(awk -F= '$1 == "session_id" { print $2 }' \1040 "$raw"/fixed-iroh-moq-*.log "$raw"/fixed-grouped-v3-*.log \1041 | sort -u | wc -l | tr -d ' ')"1042native_unique_sessions="$(awk -F= '$1 == "session_id" { print $2 }' \1043 "$raw"/native-iroh-moq-*.log "$raw"/native-grouped-v3-*.log \1044 | sort -u | wc -l | tr -d ' ')"1045[[ "$fixed_unique_sessions" -eq 20 && "$native_unique_sessions" -eq 20 ]]1046total_sessions="${#all_raw_probes[@]}"1047fixed_observed_fps="$(sed -n 's/^observed_fps=//p' "$evidence/fixed-capture.log")"1048external_cqp_observed_fps="$(sed -n 's/^observed_fps=//p' \1049 "$evidence/external-cqp-capture.log")"1050native_observed_fps="$(sed -n 's/^observed_fps=//p' "$evidence/native-capture.log")"1051[[ -n "$fixed_observed_fps" && -n "$external_cqp_observed_fps" \1052 && -n "$native_observed_fps" ]]1053if awk -v fps="$native_observed_fps" 'BEGIN { exit !(fps >= 55) }'; then1054 native_55fps_status=met1055else1056 native_55fps_status=below1057fi10581059{1060 echo candidate_kind=unsigned-development-hardware-uat1061 echo "source_commit=$uat_commit"1062 echo "workflow_run_id=$workflow_run_id"1063 echo fixed_size=1280x8001064 echo fixed_minimum_fps=551065 echo "fixed_observed_fps=$fixed_observed_fps"1066 echo external_cqp_minimum_fps=551067 echo "external_cqp_observed_fps=$external_cqp_observed_fps"1068 echo "vaapi_render_node=$render_node"1069 echo "vaapi_encoder=$va_encoder"1070 echo "vaapi_selection_mode=$vaapi_selection_mode"1071 echo cross_state_capture_contention=rejected1072 echo gamescope_pointer_restart_cycles=21073 echo gamescope_pointer_feedback_recovery=pass1074 echo gamescope_pointer_daemon_pid_stable=pass1075 echo "native_size=$native_size"1076 echo "native_mode_relation=$native_mode_relation"1077 echo native_performance_gate=observational1078 echo "native_observed_fps=$native_observed_fps"1079 echo "native_55fps_status=$native_55fps_status"1080 echo persistent_authenticated_identity=pass1081 echo invitation_redemption=pass1082 echo invitation_replay=rejected1083 echo "maximum_required_glibc=$max_glibc"1084 echo session_id_scope=daemon-invocation1085 echo "fixed_daemon_unique_sessions=$fixed_unique_sessions"1086 echo "native_daemon_unique_sessions=$native_unique_sessions"1087 echo "total_sessions=$total_sessions"1088 echo slow_media_consumer=pass1089 echo slow_media_consumer_resolutions=1280x800,"$native_size"1090 echo forced_relay=pass1091 echo forced_relay_scope=probe-control-media-input1092 summarize_group fixed-iroh-moq1093 summarize_group fixed-grouped-v31094 summarize_group native-iroh-moq1095 summarize_group native-grouped-v31096} >"$private/summary.pending"10971098restore_original1099[[ "$(systemctl --user is-active sigil-host.service)" == active ]]1100[[ "$(systemctl --user is-enabled sigil-host.service)" == "$original_enabled" ]]1101sha256sum -c "$private/original-config.sha256"1102sha256sum -c "$private/original-identity.sha256"1103[[ "$(readlink "$HOME/.local/libexec/sigil-spark/current")" == "$(cat "$private/original-current-link")" ]]1104[[ "$(stat -c '%i:%s:%Y:%a:%U:%G' "$real_identity")" == "$(cat "$private/original-identity.stat")" ]]11051106multi_viewer_status=not-run1107if [[ -n "$multi_viewer_evidence" ]]; then1108 validate_multi_viewer_evidence1109 multi_viewer_status=pass1110fi11111112{1113 echo hardware_uat=pass1114 cat "$private/summary.pending"1115 echo "multi_viewer_uat=$multi_viewer_status"1116 if [[ "$multi_viewer_status" == pass ]]; then1117 for key in protocol_mode concurrent_viewers shared_video_sources shared_video_encoders \1118 shared_audio_sources shared_audio_encoders shared_publishers shared_adaptive_actuators \1119 slot_0_holders_max focus_handoff_p95_ms reconnect_cycles rss_p95_mib \1120 slow_viewer_isolation same_peer_replacement live_view_revocation survivors_uninterrupted \1121 bounded_queues final_cleanup; do1122 printf 'multi_viewer_%s=%s\n' "$key" "$(multi_viewer_value "$key")"1123 done1124 fi1125 echo original_service_restored=pass1126 echo original_config_preserved=pass1127 echo original_identity_preserved=pass1128 echo original_release_preserved=pass1129} >"$evidence/summary.env"1130chmod 0600 \1131 "$evidence/summary.env" \1132 "$evidence"/probes/*.log \1133 "$evidence"/*-capture.log \1134 "$evidence"/pointer-recovery-*.log1135cat "$evidence/summary.env"