#!/usr/bin/env bash # Live A/B: the Python Blaze responder vs the Rust sidecar, over real sockets. # # ./check-live-parity.sh [outdir] # # Replays the recorded conversations against BOTH endpoints and diffs the # normalized traces. Session keys and server timestamps are masked, so anything # that differs is a real behavioural difference. # # IMPORTANT: the diff is the test, not the probe's exit code. The probe only # detects structural anomalies it can see live (missing frames, early close); a # same-length content change deep inside a notification body shows up ONLY as a # digest difference in the trace. Verified by mutation. # # Read-only against both backends: it opens client connections and sends # recorded requests. Safe to run while the Python backend is serving. # # For the comparison to mean anything, BOTH servers must run the same config — # same OPENFUT_ADVERTISE, OPENFUT_BIND, POW_CONTENT_HOST and active persona. # Otherwise legitimate config differences read as parity failures. set -uo pipefail PY="${1:-}" RS="${2:-}" OUT="${3:-$(mktemp -d)}" if [[ -z "$PY" || -z "$RS" ]]; then echo "usage: $0 [outdir]" >&2 echo "example: $0 127.0.0.1:42130 127.0.0.1:42230" >&2 exit 2 fi cd "$(dirname "$(readlink -f "$0")")/.." PROBE="./target/debug/blaze-probe" [[ -x "$PROBE" ]] || PROBE="./target/release/blaze-probe" if [[ ! -x "$PROBE" ]]; then echo "blaze-probe not built; run: cargo build -p openfut-blaze-host" >&2 exit 2 fi # This script produces the artefact a migration decision is made from, so it # refuses to run against a tree that does not correspond to a commit. The # compiled-in build stamp cannot be trusted for this (cargo will not re-run # build.rs for another crate's edit), so the check happens here, now. if git rev-parse --git-dir >/dev/null 2>&1; then DIRT="$(git status --porcelain --untracked-files=no -- \ openfut-blaze-host openfut-adapter-fifa17 openfut-protocol-blaze 2>/dev/null)" if [[ -n "$DIRT" && "${ALLOW_DIRTY:-}" != "1" ]]; then echo "REFUSING: migration crates have uncommitted changes:" >&2 echo "$DIRT" | sed 's/^/ /' >&2 echo >&2 echo "A parity result from an unidentifiable build is not evidence." >&2 echo "Commit first, or re-run with ALLOW_DIRTY=1 for a throwaway check." >&2 exit 2 fi fi mkdir -p "$OUT" fail=0 frames=0 for sess in main fallbacks locale; do "$PROBE" "$PY" --session "$sess" > "$OUT/python-$sess.trace" 2>"$OUT/python-$sess.err" || true "$PROBE" "$RS" --session "$sess" > "$OUT/rust-$sess.trace" 2>"$OUT/rust-$sess.err" || true n=$(grep -c '^conn-' "$OUT/python-$sess.trace" || true) if diff -u "$OUT/python-$sess.trace" "$OUT/rust-$sess.trace" > "$OUT/diff-$sess.txt"; then echo " $sess: IDENTICAL ($n frames)" frames=$((frames + n)) else echo " $sess: DIFFERS -> $OUT/diff-$sess.txt" head -30 "$OUT/diff-$sess.txt" fail=1 fi done echo if [[ $fail -eq 0 ]]; then echo "LIVE PARITY OK — $frames frames, identical normalized traces." echo "traces: $OUT" else echo "LIVE PARITY FAILED — see $OUT" fi exit $fail