#!/usr/bin/env bash
# validate-973.sh — #973 validation driver. One run produces the complete
# evidence chain for the 261-site conversion:
#
#   0. instrument self-test (microtest) — no validate evidence is trusted
#      before the instrument itself has been proven, including its abort arms.
#   1. expected set: 261 coordinates from the FROZEN artifact (+7 header
#      shift: 3 converter lines + 4 #984 guard lines), count asserted against the number declared below BEFORE any
#      suite runs.
#   2. static inventory: converted call sites re-derived from SOURCE TEXT,
#      must equal the expected set exactly (amendment ONE, leg 1 — the
#      inventory comes from the text, never from the ledger).
#   3. green instrumented run: all ten suites with WAKE_ASSERT_LEDGER; each
#      must exit 0 AND emit its own sentinel (per-suite formats differ and are
#      pinned here — a suite that died early must never pass on another
#      suite's output).
#   4. trace arithmetic on coordinate SETS (loops re-execute sites and the
#      multi-grep lines append twice per pass, so counts are meaningless;
#      sets are not):
#        trace − expected  MUST be empty (a helper ran at a coordinate the
#                          denominator never measured);
#        expected − trace  = converted-but-never-executed: enumerated, and
#                          every entry must carry a disposition in the
#                          committed unexecuted-sites-dispositions.txt, with
#                          no stale dispositions the other way (amendment
#                          ONE, legs 2+3 — the ledger is an execution trace,
#                          not an inventory; the difference is enumerated and
#                          individually dispositioned, never silently absent).
#   5. forced-error arms: the 19 denominator canaries plus one E-form and one
#      F-form site, each run with WAKE_ASSERT_FORCE_GREP_ERROR_AT: the suite
#      must emit the ARMED line (the arm proved it fired), the ABORT line
#      naming the site, exit non-zero, emit NO sentinel, and the aborting
#      site's ledger row must already be present (the append lands before the
#      grep).
#   6. residual sweep: the denominator's own classifier finds zero unconverted
#      verdict greps in the suites — and eight plants (six per-form + two
#      absorb-branch probes, #985) in the same run.
#
# Output discipline (A10): every line that reports on a suite names the file
# under test; exit codes are reported before failure counts.
set -uo pipefail

HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WAKE="$(cd "$HERE/.." && pwd)"
CHECK="$HERE/check-973.py"
DISPO="$HERE/unexecuted-sites-dispositions.txt"
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT

# Declared BEFORE any suite runs (A2): the run must produce THESE numbers,
# not be described by whatever numbers it produced.
EXPECTED_SUITES=10
EXPECTED_SITES=261
EXPECTED_ARMS=21

fails=0
flag() {
  printf 'FAIL %s\n' "$*"
  fails=$((fails + 1))
}

SUITES=(
  test-wake-beacon.sh
  test-wake-detector.sh
  test-wake-digest-hmac.sh
  test-wake-digest-quarantine.sh
  test-wake-fn-oracle.sh
  test-wake-install.sh
  test-wake-preimage.sh
  test-wake-reconcile.sh
  test-wake-store-ack.sh
  test-wake-store-enqueue-race.sh
)
[ "${#SUITES[@]}" -eq "$EXPECTED_SUITES" ] ||
  flag "suite list has ${#SUITES[@]} entries, declared $EXPECTED_SUITES"

# Per-suite sentinel patterns, pinned: nine suites share the harness template
# (enqueue-race appends a tail after it); preimage uses its own format.
sentinel_for() {
  case "$1" in
  test-wake-preimage.sh) printf '%s' '^== test-wake-preimage: 17/17 passed ==$' ;;
  *) printf '%s' 'harness: all invariants passed' ;;
  esac
}

# --- 0: instrument self-test ------------------------------------------------
if bash "$HERE/microtest-wake-assert.sh" >"$TMP/microtest.out" 2>&1; then
  echo "MICROTEST microtest-wake-assert.sh exit=0 (instrument proven)"
else
  rc=$?
  echo "MICROTEST microtest-wake-assert.sh exit=$rc"
  sed 's/^/  /' "$TMP/microtest.out" | tail -n 15
  flag "instrument self-test failed — no validate evidence below is trustworthy"
fi

# --- 1+2: expected set (artifact) vs static inventory (source text) ---------
python3 "$CHECK" expected | sort >"$TMP/expected.txt" ||
  flag "check-973.py expected failed"
n_expected="$(grep -c . "$TMP/expected.txt")"
echo "EXPECTED-SET $n_expected coordinates (declared: $EXPECTED_SITES)"
[ "$n_expected" -eq "$EXPECTED_SITES" ] ||
  flag "expected set has $n_expected coordinates, declared $EXPECTED_SITES"

python3 "$CHECK" static | sort >"$TMP/static.txt" ||
  flag "check-973.py static failed"
if cmp -s "$TMP/expected.txt" "$TMP/static.txt"; then
  echo "STATIC-INVENTORY equals expected set ($(grep -c . "$TMP/static.txt") rows from source text)"
else
  flag "static inventory (source text) differs from expected set (artifact):"
  diff "$TMP/expected.txt" "$TMP/static.txt" | sed -n '1,20p' | sed 's/^/  /'
fi

# --- 3: green instrumented run ----------------------------------------------
LEDGER="$TMP/ledger"
: >"$LEDGER"
for s in "${SUITES[@]}"; do
  out="$(WAKE_ASSERT_LEDGER="$LEDGER" bash "$WAKE/$s" 2>&1)"
  rc=$?
  if grep -Eq "$(sentinel_for "$s")" <<<"$out"; then
    sent="present"
  else
    sent="ABSENT"
  fi
  echo "SUITE $s exit=$rc sentinel=$sent"
  [ "$rc" -eq 0 ] || flag "$s exited $rc in the green instrumented run"
  [ "$sent" = "present" ] || flag "$s did not emit its sentinel"
done

# --- 4: trace arithmetic on coordinate sets ---------------------------------
sort -u "$LEDGER" >"$TMP/trace.txt"
echo "TRACE $(grep -c . "$TMP/trace.txt") distinct coordinates from $(grep -c . "$LEDGER") ledger rows"

comm -13 "$TMP/expected.txt" "$TMP/trace.txt" >"$TMP/rogue.txt"
if [ -s "$TMP/rogue.txt" ]; then
  flag "trace contains coordinates OUTSIDE the frozen denominator:"
  sed 's/^/  ROGUE /' "$TMP/rogue.txt"
else
  echo "TRACE-MINUS-EXPECTED empty (no helper ran at an unmeasured coordinate)"
fi

comm -23 "$TMP/expected.txt" "$TMP/trace.txt" >"$TMP/unexec.txt"
n_unexec="$(grep -c . "$TMP/unexec.txt" || true)"
echo "UNEXECUTED $n_unexec of $EXPECTED_SITES converted sites did not execute in the green run"
if [ ! -f "$DISPO" ]; then
  flag "disposition file missing: $DISPO — every unexecuted site must be individually dispositioned"
  sed 's/^/  UNDISPOSITIONED /' "$TMP/unexec.txt"
else
  awk '!/^#/ && NF >= 2 {print $1, $2}' "$DISPO" | sort -u >"$TMP/dispo-keys.txt"
  comm -23 "$TMP/unexec.txt" "$TMP/dispo-keys.txt" >"$TMP/undispo.txt"
  comm -13 "$TMP/unexec.txt" "$TMP/dispo-keys.txt" >"$TMP/stale-dispo.txt"
  if [ -s "$TMP/undispo.txt" ]; then
    flag "unexecuted sites WITHOUT a disposition:"
    sed 's/^/  UNDISPOSITIONED /' "$TMP/undispo.txt"
  fi
  if [ -s "$TMP/stale-dispo.txt" ]; then
    flag "dispositions for sites that DID execute (stale — the file no longer matches the run):"
    sed 's/^/  STALE-DISPO /' "$TMP/stale-dispo.txt"
  fi
  if [ ! -s "$TMP/undispo.txt" ] && [ ! -s "$TMP/stale-dispo.txt" ]; then
    echo "DISPOSITIONS all $n_unexec unexecuted sites individually dispositioned, none stale"
  fi
fi

# --- 5: forced-error arms ---------------------------------------------------
python3 "$CHECK" arms >"$TMP/arms.txt" || flag "check-973.py arms failed"
n_arms="$(grep -c . "$TMP/arms.txt")"
echo "ARMS $n_arms forced-error arms (declared: $EXPECTED_ARMS)"
[ "$n_arms" -eq "$EXPECTED_ARMS" ] ||
  flag "arm list has $n_arms entries, declared $EXPECTED_ARMS"

while read -r helper site form; do
  f="${site%%:*}"
  aled="$TMP/ledger-arm"
  : >"$aled"
  out="$(WAKE_ASSERT_LEDGER="$aled" WAKE_ASSERT_FORCE_GREP_ERROR_AT="$site" \
    bash "$WAKE/$f" 2>&1)"
  rc=$?
  bad=""
  [ "$rc" -ne 0 ] || bad="$bad exit=0"
  grep -q "WAKE-ASSERT ARMED: forcing real grep error at $site"  <<<"$out"||
    bad="$bad no-ARMED-line"
  grep -q "WAKE-ASSERT ABORT: ${helper} at ${site}: grep exit"  <<<"$out"||
    bad="$bad no-ABORT-line"
  # AND-polarity check (a match is the defect): a grep error (rc>=2) must be
  # its own loud arm — it cannot fall through as "no sentinel = pass".
  rc_sent=0
  grep -Eq "$(sentinel_for "$f")"  <<<"$out"|| rc_sent=$?
  case "$rc_sent" in
  0) bad="$bad sentinel-emitted" ;;
  1) : ;;
  *) bad="$bad sentinel-grep-error-rc=$rc_sent" ;;
  esac
  grep -q "^${helper} ${site}\$" "$aled" ||
    bad="$bad no-ledger-row"
  if [ -z "$bad" ]; then
    echo "ARM $site ($form) exit=$rc armed+abort+no-sentinel+ledger-row"
  else
    echo "ARM $site ($form) exit=$rc DEFECTS:$bad"
    flag "arm $site ($form) failed:$bad"
  fi
done <"$TMP/arms.txt"

# --- 6: residual sweep ------------------------------------------------------
if python3 "$CHECK" sweep >"$TMP/sweep.out" 2>&1; then
  echo "SWEEP exit=0"
else
  echo "SWEEP exit=$?"
  flag "residual sweep failed"
fi
sed 's/^/  /' "$TMP/sweep.out"

# --- summary (exit codes above, failure count last — A10) --------------------
echo
if [ "$fails" -gt 0 ]; then
  echo "validate-973: FAILED ($fails failure(s))" >&2
  exit 1
fi
echo "validate-973: OK — $EXPECTED_SUITES suites, $EXPECTED_SITES sites, $EXPECTED_ARMS arms, sweep clean"
