#!/usr/bin/env bash # wave-chain.sh : [: ...] # e.g. wave-chain.sh 9:K-manual 10:L-search # # Runs waves IN SEQUENCE with no orchestrator attached (2026-09-13: the user asked for # wave 10 to start straight after wave 9 without waiting on them). Between waves it # runs a mechanical acceptance gate, because starting a worker on a red tree wastes a # whole session. This gate is NOT the orchestrator's review — it is the floor below it. # # Per wave: # 1. run-task.sh in the FOREGROUND (quota-aware, resumable) # 2. gate: assembleDebug, testDebugUnitTest, verifyPaparazziDebug, test count UP from # the XML, 0 failures, no build files touched, no "always 'false'" warning # 3. gate red -> ONE fresh fix worker (-fix) pointed at the gate log, re-gate # 4. green -> commit + push; red -> write sentinel as FAILED and STOP the chain # # Holds the sprite lease itself (HAZARD #5). wave-guard.sh cannot be used: its # workers_running() sees nothing during the 5-10 min gate between workers and would # release the lease mid-chain. Liveness here is `kill -0` on this script's own PID, # never pgrep -f (HAZARD #6/#10). set -u cd "$HOME/bookshelf" || exit 1 L="$HOME/bookshelf/logs"; mkdir -p "$L" CLOG="$L/wave-chain.log"; LEASE="bookshelf-wave" RESULTS="app/app/build/test-results/testDebugUnitTest" log() { echo "[$(date -Is)] $*" >> "$CLOG"; } lease_present() { sprite-env curl /v1/tasks 2>/dev/null | grep -q "\"$LEASE\""; } lease_acquire() { local a for a in 1 2 3; do sprite-env curl -X DELETE "/v1/tasks/$LEASE" >/dev/null 2>&1 sprite-env curl -X POST /v1/tasks -H 'Content-Type: application/json' \ -d "{\"name\":\"$LEASE\",\"expire\":\"3600s\"}" >/dev/null 2>&1 lease_present && return 0 sleep 5 done return 1 } lease_release() { sprite-env curl -X DELETE "/v1/tasks/$LEASE" >/dev/null 2>&1; } CHAIN_PID=$$ ( last=$(date +%s) lease_acquire && log "lease acquired and VERIFIED" || log "FATAL: lease acquire failed" while kill -0 "$CHAIN_PID" 2>/dev/null; do sleep 30 now=$(date +%s) if [ $((now - last)) -ge 900 ] || ! lease_present; then lease_acquire && log "lease renewed" || log "ERROR: lease renewal FAILED" last=$now fi done ) & RENEWER=$! trap 'kill $RENEWER 2>/dev/null; lease_release; log "chain exiting, lease released"' EXIT count_tests() { python3 - "$RESULTS" <<'PY' import glob, sys, xml.etree.ElementTree as ET t = s = f = 0 for p in glob.glob(sys.argv[1] + "/TEST-*.xml"): r = ET.parse(p).getroot() t += int(r.get("tests")); s += int(r.get("skipped")); f += int(r.get("failures")) + int(r.get("errors")) print(t, s, f) PY } # gate