yog 0.0.3

yog: a balls-oriented session manager for lernie loops (egui frontend)
Documentation
#!/usr/bin/env bash
# Remote-build driver for the bl-24e7 merge queue (bl-1a5b): makes
# `bl-speculate run` build on GitHub Actions instead of this machine.
# bl-speculate run checks each candidate commit out into a detached build dir
# and runs its --gate command there, only the exit code speaking; THIS is
# that command. The remote builder is pure gate policy — balls is untouched.
#
#   1. push the candidate (this build dir's HEAD) to speculation/<sha>;
#   2. .github/workflows/speculate.yml runs the stock gate on it and uploads
#      the runner's verdict store as artifact `verdicts`;
#   3. wait on that exact run (gh run watch), download + bl-speculate import;
#   4. sweep the branch (the TTL sweep is us; a crash leaves the branch —
#      sweep by hand: git push origin --delete speculation/<sha>);
#   5. answer with `bl-speculate check`: exit 0 only if the imported verdict
#      is a PASS under the LOCAL key (tree + local gate files + local
#      `rustc -V`). A toolchain mismatch is therefore an honest miss —
#      reported as failure here so the chain stops instead of vouching blind
#      (rust-toolchain.toml pins both sides; see the workflow header).
#
# Usage:    bl-speculate run --gate scripts/speculate-gate [--builds N]
# Requires: gh (authenticated) and push access to origin.

set -euo pipefail
cd "$(git rev-parse --show-toplevel)" # the build dir is its own toplevel

sha="$(git rev-parse HEAD)"
branch="speculation/$sha"

echo "speculate-gate: pushing candidate $sha to $branch" >&2
git push --force origin "HEAD:refs/heads/$branch" >&2

# THE BRANCH IS OWNED BY THE TRAP FROM HERE ON (bl-1ea9). It is the only thing
# this script puts on the PUBLIC remote, and it used to be deleted by two
# hand-rolled calls on two of the paths out — so a SIGINT, a dropped network or
# any `exit` added later between the push above and the delete below stranded
# `speculation/<sha>` there. The header of this file conceded it: "sweep by
# hand". A trap set on the line after the push cannot be outrun by a path
# nobody thought of, which is the whole class the hand-rolled calls missed.
#
# `verdicts` is folded in here rather than trapped separately, because two traps
# on EXIT would mean the second replacing the first — it is initialised empty so
# the cleanup is valid from this line, before the mktemp far below.
#
# SIGKILL and a lost machine are NOT covered and cannot be: nothing runs. The
# remote-side sweep in release-plz.yml (`prune stale branches`) is what collects
# those, and it deliberately skips `speculation/**` so it cannot delete a branch
# out from under a gate that is still running.
verdicts=""
cleanup() {
  git push origin --delete "refs/heads/$branch" >&2 || true
  [ -n "$verdicts" ] && rm -rf "$verdicts"
  return 0
}
trap cleanup EXIT INT TERM HUP

# The push just created the branch, so the run keyed to it is ours alone —
# speculate.yml is the only workflow triggering on speculation/**. Keyed by
# branch, not --workflow: gh cannot resolve a workflow by name until it has
# registered, which the first-ever push is still causing. Bounded wait for
# GitHub to mint the run.
run_id=""
for _ in $(seq 30); do
  run_id="$(gh run list --branch "$branch" \
    --json databaseId --jq '.[0].databaseId' 2>/dev/null || true)"
  [ -n "$run_id" ] && break
  sleep 5
done
if [ -z "$run_id" ]; then
  echo "speculate-gate: no workflow run appeared for $branch" >&2
  exit 1
fi

echo "speculate-gate: watching run $run_id" >&2
gh run watch "$run_id" -i 30 >/dev/null || true # the conclusion travels in the verdict

verdicts="$(mktemp -d)"
if gh run download "$run_id" -n verdicts -D "$verdicts" >&2; then
  bl-speculate import "$verdicts"/*.toml >&2 || true
else
  echo "speculate-gate: no verdicts artifact on run $run_id" >&2
fi

# The one answer: did this exact tree pass this exact gate, per the local key?
# NOT `exec`: exec REPLACES this shell, and a replaced shell runs no EXIT trap —
# so the success path, the one that runs every time, would be the one path that
# leaked the branch. Called plainly, the trap fires and the status is still the
# check's, because `set -e` carries a failure straight out.
bl-speculate check