#!/usr/bin/env bash # Row 82 (#1551): compare `list` and `show` from two trees, byte for byte. # Usage: byte-check.sh . # Part 1 seeds a scratch data root with well-formed records only; part 2 # reads the data root of the caller's own config (read-only: list and show # never write). Each command's stdout, stderr and exit status must match. # Part 3 seeds damaged records and only records both outputs, because the # base crashes there and the candidate shouldn't. set -euo pipefail BASE="$(cd "${1:?usage}" && pwd)"; CAND="$(cd "${2:?usage}" && pwd)"; OUT="${3:?usage}" mkdir -p "$OUT" S="$(mktemp -d)"; trap 'rm -rf "$S"' EXIT fail=0 run() { # run TREE TAG CONFIG ARGS... -> $OUT/TAG.{out,err,rc} local tree=$1 tag=$2 config=$3; shift 3 set +e if [ -n "$config" ]; then MOSAIC_CONFIG="$config" node "$tree/scripts/mosaic-task.mjs" "$@" >"$OUT/$tag.out" 2>"$OUT/$tag.err" else node "$tree/scripts/mosaic-task.mjs" "$@" >"$OUT/$tag.out" 2>"$OUT/$tag.err"; fi echo $? >"$OUT/$tag.rc" set -e } same() { # same NAME CONFIG ARGS... local name=$1 config=$2; shift 2 run "$BASE" "$name.base" "$config" "$@"; run "$CAND" "$name.cand" "$config" "$@" local x for x in out err rc; do if ! cmp -s "$OUT/$name.base.$x" "$OUT/$name.cand.$x"; then echo "DIFFERS $name ($x)"; fail=1; return; fi done echo "same $name (rc $(cat "$OUT/$name.base.rc"), $(wc -l <"$OUT/$name.base.out") lines)" } seed() { # seed DIR RUNID FILE JSON mkdir -p "$1/runs/$2"; printf '%s\n' "$4" >"$1/runs/$2/$3" } config() { # config DATAROOT -> path of a scratch config for it printf '{"configVersion":1,"environment":"development","dataRoot":"%s","execution":{"backend":"docker","provider":"zai","model":"m"}}\n' "$1" >"$1.config.json" echo "$1.config.json" } # Part 1: well-formed records. D="$S/good"; mkdir -p "$D" R='"runVersion":1,"missionId":null,"reason":null,"request":"hi","response":"hi","expectedExact":null,"tools":null,"provider":"zai","model":"m","startedAt":"2026-01-01T00:00:00.000Z","finishedAt":"2026-01-01T00:00:01.000Z","durationMs":1000,"exitCode":0,"signal":null,"sessionForkFrom":null' seed "$D" r-20260101T000000Z-aaaaaa result.json "{$R,\"runId\":\"r-20260101T000000Z-aaaaaa\",\"taskId\":\"t-one\",\"status\":\"succeeded\",\"workspace\":\"ws1\",\"session\":\"s1\"}" seed "$D" r-20260101T000100Z-bbbbbb result.json "{$R,\"runId\":\"r-20260101T000100Z-bbbbbb\",\"taskId\":\"t-a-very-long-task-id-over-18\",\"status\":\"failed\",\"workspace\":null,\"session\":null}" seed "$D" r-20260101T000200Z-cccccc result.json "{$R,\"runId\":\"r-20260101T000200Z-cccccc\",\"taskId\":\"t-three\",\"status\":\"succeeded\",\"workspace\":\":run\",\"session\":null}" seed "$D" r-20260101T000300Z-dddddd result.json "{$R,\"runId\":\"r-20260101T000300Z-dddddd\",\"taskId\":\"t-four\",\"status\":\"timeout\"}" seed "$D" r-20260101T000400Z-eeeeee task.json '{"id":"t-five"}' seed "$D" r-.bad result.json "{$R,\"taskId\":\"t-bad\",\"status\":\"succeeded\"}" C="$(config "$D")" same good-list "$C" list for r in r-20260101T000000Z-aaaaaa r-20260101T000100Z-bbbbbb r-20260101T000200Z-cccccc r-20260101T000300Z-dddddd r-20260101T000400Z-eeeeee; do same "good-show-$r" "$C" show "$r" done # Part 2: the caller's real data root, through its own config. same real-list "" list for r in $(awk '{print $1}' "$OUT/real-list.base.out"); do same "real-show-$r" "" show "$r"; done # Part 3: damaged records, recorded only. D="$S/damaged"; mkdir -p "$D" seed "$D" r-20260101T000000Z-aaaaaa result.json "{$R,\"taskId\":\"t-one\",\"status\":\"succeeded\",\"workspace\":\"ws1\",\"session\":\"s1\"}" seed "$D" r-20260101T000050Z-mmmmmm result.json '{}' seed "$D" r-20260101T000100Z-bbbbbb result.json "{$R,\"taskId\":\"t-two\",\"status\":\"succeeded\",\"workspace\":7,\"session\":{\"name\":\"s\"}}" C="$(config "$D")" run "$BASE" damaged-list.base "$C" list; run "$CAND" damaged-list.cand "$C" list echo "damaged list: base rc $(cat "$OUT/damaged-list.base.rc"), $(wc -l <"$OUT/damaged-list.base.out") lines; candidate rc $(cat "$OUT/damaged-list.cand.rc"), $(wc -l <"$OUT/damaged-list.cand.out") lines" exit "$fail"