#!/usr/bin/env bash
# run_vindex_concurrency_tests.sh — regression harness for the 2026-08-16 soul crash.
#
# Four halves. The SET is the point: it separates two hazards the original two-half
# version conflated, and which have fixes in different files.
#
#   1. single          ASan+UBSan, one thread. MUST be clean. Hard failure.
#
#   2. readers         TSan, N readers, NO writer. Hazard (a): the visited set used
#                      to live on the index, so two pure READS stamped each other's
#                      epoch. Fixed in engram_vindex.c (frame-owned VVisit +
#                      `const VIndex*` search). MUST be clean. Hard failure.
#
#   3. unsynchronized  TSan, writer + reader on a BARE index. Hazard (b): in-place
#                      HNSW insert rewires existing elements' neighbour lists and
#                      reallocs elems[]. EXPECTED TO RACE, PERMANENTLY. This is not
#                      a bug to fix inside engram_vindex.c — it is the executable
#                      proof that a publication boundary must exist above it.
#                      Not a failure. If it ever goes CLEAN, the test stopped
#                      interleaving and half 4 is no longer meaningful either.
#
#   4. published       TSan, owner + N readers through a publication boundary
#                      (rwlock: readers shared, owner exclusive) mirroring
#                      eg_vindex_view / eg_vindex_maintain in lang/runtime/el_runtime.c.
#                      MUST be clean, and all inserts must land. Hard failure.
#
# See test_vindex_concurrency.c for the full story (SIGSEGV at ASCII address
# "gramNode", heap corruption in xzm_realloc, etc).
#
# usage: run_vindex_concurrency_tests.sh
set -uo pipefail

HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
RUNTIME="$(cd "$HERE/../../lang/runtime" && pwd)"
WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT

SRC="$HERE/test_vindex_concurrency.c"
VINDEX="$RUNTIME/engram_vindex.c"

fail=0

echo "== [1/4] single-threaded control under AddressSanitizer =="
cc -std=c11 -g -O1 -fsanitize=address,undefined -fno-omit-frame-pointer \
   -I"$RUNTIME" -o "$WORK/single" "$SRC" "$VINDEX" -lm || { echo "BUILD FAILED"; exit 2; }
if ASAN_OPTIONS=detect_leaks=0 "$WORK/single" single; then
  echo "   -> OK"
else
  echo "   -> FAIL: the single-threaded control must always be clean."
  echo "      If this fails the bug is NOT (only) concurrency — look for a real"
  echo "      out-of-bounds or lifetime error in engram_vindex.c."
  fail=1
fi

cc -std=c11 -g -O1 -fsanitize=thread -fno-omit-frame-pointer \
   -I"$RUNTIME" -o "$WORK/conc" "$SRC" "$VINDEX" -lm || { echo "BUILD FAILED"; exit 2; }

# run_tsan <mode> <logfile>; echoes nothing, sets $tsan_raced
run_tsan() {
  TSAN_OPTIONS="halt_on_error=0" "$WORK/conc" "$1" >"$2" 2>&1
  tsan_rc=$?
  if grep -q "ThreadSanitizer: data race" "$2"; then tsan_raced=1; else tsan_raced=0; fi
}

echo
echo "== [2/4] concurrent READERS, no writer (visited-set gate) =="
run_tsan readers "$WORK/readers.log"
if [ "$tsan_raced" = "1" ]; then
  echo "   -> REGRESSION: two concurrent reads still race."
  grep -m1 -A6 "ThreadSanitizer: data race" "$WORK/readers.log" | sed 's/^/      /'
  echo "      The visited set was supposed to be owned by the call frame."
  fail=1
else
  echo "   -> clean (concurrent reads are safe)"
fi

echo
echo "== [3/4] writer+reader on a BARE index (expected-race probe) =="
run_tsan unsynchronized "$WORK/unsync.log"
if [ "$tsan_raced" = "1" ]; then
  echo "   -> RACE DETECTED, as expected:"
  grep -m1 -A4 "ThreadSanitizer: data race" "$WORK/unsync.log" | sed 's/^/      /'
  echo "      In-place HNSW insert mutates existing elements. Not fixable inside"
  echo "      engram_vindex.c — this is why the publication boundary exists."
else
  echo "   -> NOTE: no race reported. The probe did not interleave; half 4's"
  echo "      clean result proves less than it should. Investigate."
fi

echo
echo "== [4/4] owner+readers through the publication boundary (boundary gate) =="
run_tsan published "$WORK/pub.log"
if [ "$tsan_raced" = "1" ]; then
  echo "   -> REGRESSION: the publication boundary did not serialize the owner."
  grep -m1 -A6 "ThreadSanitizer: data race" "$WORK/pub.log" | sed 's/^/      /'
  fail=1
elif [ "$tsan_rc" != "0" ]; then
  echo "   -> FAIL: boundary clean under TSan but the run failed:"
  tail -3 "$WORK/pub.log" | sed 's/^/      /'
  fail=1
else
  echo "   -> clean (readers project concurrently; the owner's inserts all landed)"
fi

echo
[ "$fail" -eq 0 ] && echo "RESULT: PASS" || echo "RESULT: FAIL"
exit "$fail"
