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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#!/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).
#
# AN EMPTY ARTIFACT IS A REBUILD, NOT AN ANSWER (bl-673a). A run whose gate
# died of something outside the tree — five sightings of a runner signalling
# tarpaulin mid-suite — records no verdict at all, by the workflow's own guard.
# That is the one case worth spending a second build on, because THIS SCRIPT'S
# EXIT CODE IS BINARY AND THE CALLER WRITES A VERDICT FROM IT EITHER WAY: balls
# `speculate_run::build` takes `status.success()` and stores `pass = false` on
# anything else, and a stored FAIL stops the candidate chain forever without
# ever rebuilding (`speculate_run`: "fail <id> <tree> — chain stops"). There is
# no third answer to give it, so the only lever this repo owns is to not answer
# until a build has actually judged the tree. Two attempts; if the second also
# comes home empty the poison is unavoidable and upstream's to fix — a gate exit
# code meaning "no verdict, record nothing" is what balls would need.
#
# 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 below and the delete stranded
# `speculation/<sha>` there. The header of this file conceded it: "sweep by
# hand". A trap set BEFORE 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=""
verdicts=""
# The newest run on our branch that is not `$1`. speculate.yml is the only
# workflow triggering on speculation/**, and the push that precedes each call
# mints a run, so "newest, and not the one we already watched" identifies ours
# on a retry as well as on the first push. Keyed by branch rather than
# --workflow: gh cannot resolve a workflow by name until it has registered,
# which the first-ever push is still causing. Bounded wait.
# One remote build: push, watch, fetch its verdict store. 0 iff a verdict for
# some (tree, gate) came home — the workflow records exactly one on any run that
# judged the tree, and nothing at all on a run that did not.
watched=""
attempts=2
for; do
&& break
done
if ; then
||
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.