1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#!/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.
sha=""
branch="speculation/"
# 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=""
# 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; do
run_id=""
[ && break
done
if [; then
fi
||
verdicts=""
if ; then
||
else
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.