Compare commits

...

6 commits

Author SHA1 Message Date
codex-bot-andresmgsl
e1e4fc77db fix: install pinned drill inputs by commit 2026-08-18 07:14:26 +00:00
codex-bot-andresmgsl
45c29d6dbc fix: record installed candidate version 2026-08-18 07:05:50 +00:00
codex-bot-andresmgsl
3e3d8bbd5a fix: preserve evidence across drill failures 2026-08-18 07:04:33 +00:00
codex-bot-andresmgsl
6ab69d9bd7 fix: match installer ref precedence 2026-08-18 07:00:36 +00:00
codex-bot-andresmgsl
30e47836ba feat: emit drill record drafts 2026-08-18 06:56:56 +00:00
codex-bot-andresmgsl
54e82e82cc test: specify drill record emission 2026-08-18 06:51:04 +00:00
14 changed files with 372 additions and 54 deletions

View file

@ -15,6 +15,7 @@ which records not just what changed but what each drill run proved.
### Added ### Added
- The hardware drill can pin inputs and emit an uncoloured release-record draft with a shared run ID (#152)
- `kimi-box` template — the Moonshot Kimi CLI agent seed (#158; rig#109's tenant) - `kimi-box` template — the Moonshot Kimi CLI agent seed (#158; rig#109's tenant)
## 0.9.0 — 2026-07-21 ## 0.9.0 — 2026-07-21

View file

@ -49,13 +49,15 @@ By default that installs the **latest release** — the installer resolves the
release tag off GitHub's `releases/latest` redirect (no API, no token) and release tag off GitHub's `releases/latest` redirect (no API, no token) and
downloads exactly that tree, so two operators running it get the same box. downloads exactly that tree, so two operators running it get the same box.
If the resolution fails it says so and stops — it never silently hands out If the resolution fails it says so and stops — it never silently hands out
`main`. `BOX_REF` picks another channel (a set ref is tried as a tag first, `main`. `BOX_REF` picks another channel: a named ref is tried as a tag first,
then as a branch — [#83](https://github.com/heavy-duty/box/issues/83)): then as a branch ([#83](https://github.com/heavy-duty/box/issues/83)), while a
full 40-hex commit SHA selects GitHub's immutable commit archive:
```sh ```sh
curl -fsSL .../install.sh | bash # the latest release (default) curl -fsSL .../install.sh | bash # the latest release (default)
curl -fsSL .../install.sh | BOX_REF=0.6.0 bash # pin a release curl -fsSL .../install.sh | BOX_REF=0.6.0 bash # pin a release
curl -fsSL .../install.sh | BOX_REF=main bash # the development tip curl -fsSL .../install.sh | BOX_REF=main bash # the development tip
curl -fsSL .../install.sh | BOX_REF=<full-commit-sha> bash # exact tree
``` ```
(A dev tree's `VERSION` carries a `-dev` suffix, so it lands beside your (A dev tree's `VERSION` carries a `-dev` suffix, so it lands beside your

32
bin/box
View file

@ -1096,36 +1096,48 @@ load_template() {
T_DISK="${disk:-${BOX_DISK:-${T_DISK:-60GiB}}}" T_DISK="${disk:-${BOX_DISK:-${T_DISK:-60GiB}}}"
} }
# The ONE substitution a template gets — user-data.yaml is otherwise passed to # The ONE substitution a template gets — @RIG_INSTALL@ — while user-data.yaml
# Incus verbatim. The tenant seeds preinstall rig, which inverts the rig→box # is otherwise passed to Incus verbatim. The tenant seeds preinstall rig, which
# install edge (rig#28: rig installs box on hosts; now box guests install rig), # inverts the rig→box install edge (rig#28: rig installs box on hosts; now box
# and that edge needs a pin point (#81): the seed carries @RIG_REPO@ / # guests install rig), and that edge needs a pin point (#81). The renderer
# @RIG_REF@ tokens, resolved here from the mint environment — RIG_REPO # resolves the mint environment's RIG_REPO (default heavy-duty/rig) and RIG_REF
# (default heavy-duty/rig) and RIG_REF (default main). Both directions track # (default main) into the whole installer command. Named refs keep the normal
# main unpinned today, said honestly (the same treatment rig#29 gave box's own # tag/branch channel; a drill-pinned commit carries its immutable archive as
# unpinned install) until a release flow exists (rig#32 / #83). The values are # RIG_INSTALL_SOURCE (#152). Both values are
# allowlist-validated BEFORE touching the YAML: they land inside a runcmd # allowlist-validated BEFORE touching the YAML: they land inside a runcmd
# shell line, so a quote, a space or a newline smuggled through the # shell line, so a quote, a space or a newline smuggled through the
# environment must die on the host, never execute in the guest. bash's =~ # environment must die on the host, never execute in the guest. bash's =~
# anchors to the whole string — a multi-line value cannot sneak one clean # anchors to the whole string — a multi-line value cannot sneak one clean
# line past it the way a line-oriented grep would. # line past it the way a line-oriented grep would.
# The rig pin, resolved from the mint environment, in ONE place: render_userdata # The rig pin, resolved from the mint environment, in ONE place: render_userdata
# substitutes it into the seed, and the mint stamp (#103) records it onto the # builds it into the seed, and the mint stamp (#103) records it onto the
# instance. Two spellings of the same default would eventually disagree, and a # instance. Two spellings of the same default would eventually disagree, and a
# stamp that disagrees with the seed is worse than no stamp at all. # stamp that disagrees with the seed is worse than no stamp at all.
rig_repo() { printf '%s\n' "${RIG_REPO:-heavy-duty/rig}"; } rig_repo() { printf '%s\n' "${RIG_REPO:-heavy-duty/rig}"; }
rig_ref() { printf '%s\n' "${RIG_REF:-main}"; } rig_ref() { printf '%s\n' "${RIG_REF:-main}"; }
render_userdata() { render_userdata() {
local f="$1" repo data ref local f="$1" repo data ref install
repo="$(rig_repo)"; ref="$(rig_ref)" repo="$(rig_repo)"; ref="$(rig_ref)"
[[ "$repo" =~ ^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$ ]] \ [[ "$repo" =~ ^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$ ]] \
|| die "RIG_REPO must look like owner/repo: $repo" || die "RIG_REPO must look like owner/repo: $repo"
[[ "$ref" =~ ^[A-Za-z0-9._/-]+$ ]] \ [[ "$ref" =~ ^[A-Za-z0-9._/-]+$ ]] \
|| die "RIG_REF must be a plain ref name (letters, digits, . _ / -): $ref" || die "RIG_REF must be a plain ref name (letters, digits, . _ / -): $ref"
if [[ "$ref" =~ ^[0-9a-f]{40}$ ]]; then
# rig's installer currently resolves named tags/branches. A release drill
# pins the moving ref before mint, so hand that installer the immutable
# commit archive as a local source instead of asking it to resolve again
# inside the guest (#152).
printf -v install '%s' \
"rig_source=/tmp/rig-$ref.tar.gz; rig_installer=/tmp/rig-install-$ref.sh; curl -fsSL https://github.com/$repo/archive/$ref.tar.gz -o \"\$rig_source\" && curl -fsSL https://raw.githubusercontent.com/$repo/$ref/install.sh -o \"\$rig_installer\" && HOME=/root RIG_REPO=\"$repo\" RIG_REF=\"$ref\" RIG_INSTALL_SOURCE=\"\$rig_source\" bash \"\$rig_installer\"; rig_rc=\$?; rm -f \"\$rig_source\" \"\$rig_installer\"; exit \"\$rig_rc\""
else
printf -v install 'curl -fsSL https://raw.githubusercontent.com/%s/%s/install.sh | HOME=/root RIG_REPO="%s" RIG_REF="%s" bash' \
"$repo" "$ref" "$repo" "$ref"
fi
data="$(cat "$f")" data="$(cat "$f")"
data="${data//@RIG_REPO@/$repo}" data="${data//@RIG_REPO@/$repo}"
data="${data//@RIG_REF@/$ref}" data="${data//@RIG_REF@/$ref}"
data="${data//@RIG_INSTALL@/$install}"
printf '%s\n' "$data" printf '%s\n' "$data"
} }

View file

@ -10,6 +10,8 @@
# bash drill/drill.sh --yes # no prompt (CI, or you've read it) # bash drill/drill.sh --yes # no prompt (CI, or you've read it)
# bash drill/drill.sh --ref main # drill a different branch of the repo # bash drill/drill.sh --ref main # drill a different branch of the repo
# bash drill/drill.sh --keep-boxes # leave the boxes up to poke at # bash drill/drill.sh --keep-boxes # leave the boxes up to poke at
# --emit-record <path> write an editable Markdown record draft
# --run-id <id> share one ID across the family drills
# #
# Four phases: # Four phases:
# A. Incus semantics — the assumptions box is built on, probed directly. # A. Incus semantics — the assumptions box is built on, probed directly.
@ -36,6 +38,17 @@ REPO="${BOX_REPO:-heavy-duty/box}"
REF="${BOX_REF:-main}" REF="${BOX_REF:-main}"
YES=0; KEEP=0 YES=0; KEEP=0
SELF="$(readlink -f "$0")" SELF="$(readlink -f "$0")"
EMIT_RECORD="${DRILL_EMIT_RECORD:-}"
RUN_ID="${DRILL_RUN_ID:-}"
DRILL_STARTED_EPOCH="${DRILL_STARTED_EPOCH:-$(date +%s)}"
DRILL_DATE="${DRILL_DATE:-$(date -u +%F)}"
if [ -z "${DRILL_INVOCATION:-}" ]; then
printf -v DRILL_INVOCATION 'bash %q' "$0"
printf -v _drill_args ' %q' "$@"
DRILL_INVOCATION+="$_drill_args"
fi
# shellcheck source=drill/record.sh
. "$(dirname "$SELF")/record.sh"
while [ $# -gt 0 ]; do while [ $# -gt 0 ]; do
case "$1" in case "$1" in
@ -43,11 +56,19 @@ while [ $# -gt 0 ]; do
--keep-boxes) KEEP=1; shift ;; --keep-boxes) KEEP=1; shift ;;
--repo) REPO="$2"; shift 2 ;; --repo) REPO="$2"; shift 2 ;;
--ref) REF="$2"; shift 2 ;; --ref) REF="$2"; shift 2 ;;
--emit-record)
[ "$#" -ge 2 ] || { echo "drill: --emit-record needs a value" >&2; exit 2; }
EMIT_RECORD="$2"; shift 2 ;;
--run-id)
[ "$#" -ge 2 ] || { echo "drill: --run-id needs a value" >&2; exit 2; }
RUN_ID="$2"; shift 2 ;;
--in-group) shift; break ;; # internal: see below --in-group) shift; break ;; # internal: see below
-h|--help) sed -n '2,18p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; -h|--help) sed -n '2,20p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
*) echo "drill: unknown option: $1" >&2; exit 2 ;; *) echo "drill: unknown option: $1" >&2; exit 2 ;;
esac esac
done done
export DRILL_EMIT_RECORD="$EMIT_RECORD" DRILL_RUN_ID="$RUN_ID"
export DRILL_STARTED_EPOCH DRILL_DATE DRILL_INVOCATION
pass=0; fail=0; findings=(); audit=() pass=0; fail=0; findings=(); audit=()
ok() { printf ' \033[32mPASS\033[0m %s\n' "$*"; pass=$((pass + 1)); } ok() { printf ' \033[32mPASS\033[0m %s\n' "$*"; pass=$((pass + 1)); }
@ -57,6 +78,50 @@ inf() { printf ' %s\n' "$*"; }
phase(){ printf '\n\033[1m══ %s\033[0m\n' "$*"; } phase(){ printf '\n\033[1m══ %s\033[0m\n' "$*"; }
aud() { audit+=("$*"); } # an answer for the #15 audit aud() { audit+=("$*"); } # an answer for the #15 audit
record_version="$(cat "$(dirname "$SELF")/../VERSION" 2>/dev/null || echo unknown)"
record_box_sha="${DRILL_BOX_SHA:-<unresolved>}"
record_rig_repo="${DRILL_RIG_REPO:-<not recorded>}"
record_rig_ref="${DRILL_RIG_REF:-<not recorded>}"
record_rig_sha="${DRILL_RIG_SHA:-<unresolved>}"
INSTALL_REF="${DRILL_INSTALL_REF:-$REF}"
finish_drill() {
local rc="$1" record_os record_host record_elapsed
trap - EXIT
if [ "$rc" -ne 0 ] && [ "$fail" -eq 0 ]; then
fail=1
findings+=("FAIL: drill exited early with status $rc; inspect the terminal output")
fi
[ -n "$RUN_ID" ] || RUN_ID="$(drill_default_run_id "$record_version" "$DRILL_DATE")"
# Standard host metadata, optional by design when a preflight fails before
# Incus exists.
# shellcheck disable=SC1091
record_os="$(. /etc/os-release 2>/dev/null; printf '%s' "${PRETTY_NAME:-unknown OS}")"
record_host="$(hostname) / $record_os, $(uname -srmo), Incus $(incus --version 2>/dev/null || echo unknown)"
record_elapsed=$(($(date +%s) - DRILL_STARTED_EPOCH))
if drill_write_record "$EMIT_RECORD" "$record_version" "$RUN_ID" "$record_host" \
"$DRILL_DATE" "$REPO" "$REF" "$record_box_sha" \
"$record_rig_repo" "$record_rig_ref" "$record_rig_sha" \
"$DRILL_INVOCATION" "$pass" "$fail" "$record_elapsed" findings; then
inf "wrote editable release-record draft: $EMIT_RECORD"
else
rc=1
fi
exit "$rc"
}
if [ -n "$EMIT_RECORD" ]; then
trap 'finish_drill "$?"' EXIT
if [ "$record_box_sha" = '<unresolved>' ]; then
if ! record_box_sha="$(drill_resolve_ref_sha "$REPO" "$REF")"; then
echo "drill: cannot resolve exact box SHA for $REPO@$REF — refusing to drill an unpinned source" >&2
exit 1
fi
INSTALL_REF="$record_box_sha"
fi
export DRILL_BOX_SHA="$record_box_sha" DRILL_INSTALL_REF="$INSTALL_REF"
fi
wait_box() { # poll until exec answers (the VM agent can take a while), ~4 min wait_box() { # poll until exec answers (the VM agent can take a while), ~4 min
# 2 min was too short: run 17's legacy box came up AFTER the window closed — # 2 min was too short: run 17's legacy box came up AFTER the window closed —
# the drill called it dead and then every migration check on it passed. # the drill called it dead and then every migration check on it passed.
@ -217,8 +282,8 @@ EOF
# OWNS still suppresses the setup prompt via BOX_SKIP_SETUP_HOST above. # OWNS still suppresses the setup prompt via BOX_SKIP_SETUP_HOST above.
export BOX_YES=1 export BOX_YES=1
BOX_REPO="$REPO" BOX_REF="$REF" \ BOX_REPO="$REPO" BOX_REF="$INSTALL_REF" \
bash -c "$(curl -fsSL "https://raw.githubusercontent.com/$REPO/$REF/install.sh")" \ bash -c "$(curl -fsSL "https://raw.githubusercontent.com/$REPO/$INSTALL_REF/install.sh")" \
|| { echo "install failed"; exit 1; } || { echo "install failed"; exit 1; }
export PATH="$HOME/.local/bin:$PATH" export PATH="$HOME/.local/bin:$PATH"
@ -229,15 +294,15 @@ EOF
# the wrong tree while reporting success. A drill that silently drills the # the wrong tree while reporting success. A drill that silently drills the
# wrong code is worse than one that fails. # wrong code is worse than one that fails.
got="$(cat "$HOME/.local/share/box/current/INSTALLED_FROM" 2>/dev/null || echo '<unknown>')" got="$(cat "$HOME/.local/share/box/current/INSTALLED_FROM" 2>/dev/null || echo '<unknown>')"
if [ "$got" != "$REPO@$REF" ]; then if [ "$got" != "$REPO@$INSTALL_REF" ]; then
echo "drill: FATAL — asked to install $REPO@$REF, but the tree says '$got'." >&2 echo "drill: FATAL — asked to install $REPO@$INSTALL_REF, but the tree says '$got'." >&2
echo " Your local drill.sh is probably STALE (pre-0.5.0 it passed CLAUDEBOX_*," >&2 echo " Your local drill.sh is probably STALE (pre-0.5.0 it passed CLAUDEBOX_*," >&2
echo " which today's install.sh ignores, so it fell back to main). Fix:" >&2 echo " which today's install.sh ignores, so it fell back to main). Fix:" >&2
echo " git fetch origin && git checkout <the branch you mean> && git pull" >&2 echo " git fetch origin && git checkout <the branch you mean> && git pull" >&2
echo " then re-run this drill." >&2 echo " then re-run this drill." >&2
exit 1 exit 1
fi fi
inf "installed tree confirms: $got" inf "installed tree confirms: $got (requested as $REPO@$REF)"
phase "Host setup (Incus, boxnet, ACL, profile, firewall)" phase "Host setup (Incus, boxnet, ACL, profile, firewall)"
if [ "$fw_absent_pre" = 1 ]; then if [ "$fw_absent_pre" = 1 ]; then
@ -280,11 +345,28 @@ EOF
# credentials are untouched, so we still have to enter the group ourselves — # credentials are untouched, so we still have to enter the group ourselves —
# once, for the remainder of the drill. # once, for the remainder of the drill.
inf "re-entering inside the incus-admin group…" inf "re-entering inside the incus-admin group…"
exec sg incus-admin -c "IN_GROUP=1 DRILL_OWNS_SETUP='$OWNS' BOX_REPO='$REPO' BOX_REF='$REF' KEEP=$KEEP bash '$SELF' --in-group" printf -v reexec '%q ' env IN_GROUP=1 "DRILL_OWNS_SETUP=$OWNS" \
"BOX_REPO=$REPO" "BOX_REF=$REF" "KEEP=$KEEP" \
"DRILL_EMIT_RECORD=$EMIT_RECORD" "DRILL_RUN_ID=$RUN_ID" \
"DRILL_STARTED_EPOCH=$DRILL_STARTED_EPOCH" "DRILL_DATE=$DRILL_DATE" \
"DRILL_INVOCATION=$DRILL_INVOCATION" "DRILL_BOX_SHA=$record_box_sha" \
"DRILL_INSTALL_REF=$INSTALL_REF" bash "$SELF" --in-group
exec sg incus-admin -c "$reexec"
fi fi
export PATH="$HOME/.local/bin:$PATH" export PATH="$HOME/.local/bin:$PATH"
KEEP="${KEEP:-0}" KEEP="${KEEP:-0}"
record_version="$(cat "$HOME/.local/share/box/current/VERSION" 2>/dev/null || printf '%s' "$record_version")"
if [ -n "$EMIT_RECORD" ] && [ "$record_rig_sha" = '<unresolved>' ]; then
record_rig_repo="${RIG_REPO:-heavy-duty/rig}"
record_rig_ref="${RIG_REF:-main}"
if ! record_rig_sha="$(drill_resolve_ref_sha "$record_rig_repo" "$record_rig_ref")"; then
echo "drill: cannot resolve exact rig SHA for $record_rig_repo@$record_rig_ref — refusing to mint from an unpinned source" >&2
exit 1
fi
export RIG_REPO="$record_rig_repo" RIG_REF="$record_rig_sha"
export DRILL_RIG_REPO="$record_rig_repo" DRILL_RIG_REF="$record_rig_ref" DRILL_RIG_SHA="$record_rig_sha"
fi
# PROVE THE INSTALLER'S CONTRACT (#64) — first, before the clean or anything # PROVE THE INSTALLER'S CONTRACT (#64) — first, before the clean or anything
# else on this host mutates the stack, and before the drill runs setup-host # else on this host mutates the stack, and before the drill runs setup-host
@ -590,6 +672,13 @@ printf '\n minting a claude-box box (cold, ~10 min)…\n'
t0=$SECONDS t0=$SECONDS
if mint_box /tmp/mint-drill.log --name drill --template claude-box; then if mint_box /tmp/mint-drill.log --name drill --template claude-box; then
ok "box new --name drill --template claude-box ($((SECONDS - t0))s)" ok "box new --name drill --template claude-box ($((SECONDS - t0))s)"
if [ -n "$EMIT_RECORD" ]; then
minted_rig_repo="$(incus config get drill user.box.rig.repo 2>/dev/null)"
minted_rig_ref="$(incus config get drill user.box.rig.ref 2>/dev/null)"
if [ "$minted_rig_repo" != "$record_rig_repo" ] || [ "$minted_rig_ref" != "$record_rig_sha" ]; then
no "minted box records $minted_rig_repo@$minted_rig_ref, expected pinned $record_rig_repo@$record_rig_sha"
fi
fi
else else
no "box new FAILED — tail: $(tail -3 /tmp/mint-drill.log | tr '\n' ' ')" no "box new FAILED — tail: $(tail -3 /tmp/mint-drill.log | tr '\n' ' ')"
timeout -k 5 60 incus delete -f drill >/dev/null 2>&1 timeout -k 5 60 incus delete -f drill >/dev/null 2>&1

77
drill/record.sh Normal file
View file

@ -0,0 +1,77 @@
#!/usr/bin/env bash
# Pure helpers for drill/drill.sh's generated release-record draft (#152).
drill_default_run_id() {
local version="$1" run_date="$2"
printf 'drill-%s-%s-01\n' "$version" "${run_date//-/}"
}
drill_resolve_ref_sha() {
local repo="$1" ref="$2" remote refs sha
case "$ref" in
[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f])
printf '%s\n' "$ref"
return
;;
esac
case "$repo" in
/*|./*|../*|http://*|https://*|git@*) remote="$repo" ;;
*) remote="https://github.com/$repo.git" ;;
esac
refs="$(git ls-remote "$remote" \
"refs/heads/$ref" "refs/tags/$ref" "refs/tags/$ref^{}" 2>/dev/null)" || return 1
sha="$(printf '%s\n' "$refs" | awk -v ref="$ref" '
$2 == "refs/tags/" ref "^{}" { peeled=$1 }
$2 == "refs/heads/" ref { head=$1 }
$2 == "refs/tags/" ref { tag=$1 }
END { if (peeled) print peeled; else if (tag) print tag; else if (head) print head }
')"
[ -n "$sha" ] || return 1
printf '%s\n' "$sha"
}
drill_write_record() {
local path="$1" version="$2" run_id="$3" host="$4" run_date="$5"
local box_repo="$6" box_ref="$7" box_sha="$8" rig_repo="$9"
shift 9
local rig_ref="$1" rig_sha="$2" invocation="$3" passed="$4" failed="$5"
local elapsed="$6" findings_name="$7" tmp finding plain total minutes
local -n record_findings="$findings_name"
if [ -e "$path" ]; then
echo "drill: record path already exists; refusing to overwrite evidence: $path" >&2
return 1
fi
[ -d "$(dirname "$path")" ] || {
echo "drill: record directory does not exist: $(dirname "$path")" >&2
return 1
}
total=$((passed + failed))
minutes=$((elapsed / 60))
tmp="$(mktemp "$path.tmp.XXXXXX")" || return 1
{
printf '# Release drill — %s\n\n' "$version"
printf '> Generated draft: verify the facts and add operator judgement before commit.\n\n'
printf -- "- **Run ID:** \`%s\`\n" "$run_id"
printf -- '- **Host:** %s\n' "$host"
printf -- '- **Date:** %s\n' "$run_date"
printf -- '- **Candidate refs:**\n'
printf " - box \`%s\` @ \`%s\` (\`%s\`)\n" "$box_ref" "$box_sha" "$box_repo"
printf " - rig \`%s\` @ \`%s\` (\`%s\`)\n" "$rig_ref" "$rig_sha" "$rig_repo"
printf '\n## What ran\n\n'
printf "\`%s\` — phases A, B, C, E, D, M.\n" "$invocation"
printf '\n## Result\n\n'
printf '**%s/%s passed, %s failed.** %s minutes wall clock.\n\n' \
"$passed" "$total" "$failed" "$minutes"
if [ "${#record_findings[@]}" -eq 0 ]; then
printf -- '- No failures or notes recorded by the harness.\n'
else
for finding in "${record_findings[@]}"; do
plain="$(printf '%s' "$finding" | sed $'s/\033\\[[0-9;]*m//g')"
printf -- '- %s\n' "$plain"
done
fi
} > "$tmp" || { rm -f "$tmp"; return 1; }
mv "$tmp" "$path"
}

View file

@ -47,6 +47,33 @@ is a good record. So is a maintainer's written waiver explaining why this
release shipped without a full drill. What the gate refuses is silence — #95, release shipped without a full drill. What the gate refuses is silence — #95,
#114 and #148 all shipped unproven because a skip left no trace. #114 and #148 all shipped unproven because a skip left no trace.
## Generate the factual draft
The harness can write the facts it already knows directly into a new record:
```sh
bash drill/drill.sh --ref release/0.9.1 \
--run-id drill-0.9.1-20260818-01 \
--emit-record drills/0.9.1.md
```
`--run-id` wins over `DRILL_RUN_ID`; if neither is set, the harness generates
`drill-<version>-<UTC date>-01`. Supply the same ID explicitly to each family
drill so their records reconcile. The emitter refuses to overwrite an existing
file, and its Markdown contains no terminal colour escapes.
When record emission is requested, the harness resolves the requested box and
rig refs before their installers run and feeds those installers the resulting
full commit SHAs. The draft keeps both the human-facing refs and the immutable
SHAs. If either ref cannot be pinned, the drill refuses before mutating the host
and still writes the failed preflight record.
The generated file is a **draft, not finished release evidence**. It records
the host, invocation, elapsed time, findings, and the exact box and rig refs and
SHAs the run selected. Before committing it, verify those facts and add the
operator judgement that a script cannot make: hardware detail, the meaning of
any failures or skips, and whether they block release.
## Worked example ## Worked example
The version below is a **placeholder that can never be a real release**. The version below is a **placeholder that can never be a real release**.

View file

@ -24,10 +24,11 @@ set -euo pipefail
# downloading — for CI and the drill, so what lands is the code under review. # downloading — for CI and the drill, so what lands is the code under review.
REPO="${BOX_REPO:-heavy-duty/box}" REPO="${BOX_REPO:-heavy-duty/box}"
# Three install channels, one knob (#83): BOX_REF unset installs the LATEST # Four install channels, one knob (#83, #152): BOX_REF unset installs the LATEST
# RELEASE (the tag resolved from GitHub's releases/latest redirect, below); # RELEASE (the tag resolved from GitHub's releases/latest redirect, below);
# BOX_REF=<tag> pins a release; BOX_REF=<branch> (say, main) is the dev # BOX_REF=<tag> pins a release; BOX_REF=<branch> (say, main) is the dev
# channel. A set ref is tried as a tag first, then as a branch. # channel; and a full 40-hex commit SHA is fetched immutably. A named ref is
# tried as a tag first, then as a branch.
REF="${BOX_REF:-}" REF="${BOX_REF:-}"
# Root installs GLOBALLY, non-root installs per-user. box's install tree is # Root installs GLOBALLY, non-root installs per-user. box's install tree is
# EXECUTED by other users (the multi-user host path: rig installs box once, every # EXECUTED by other users (the multi-user host path: rig installs box once, every
@ -232,17 +233,27 @@ else
fi fi
INSTALLED_FROM="$REPO@$REF" INSTALLED_FROM="$REPO@$REF"
log "installing box from $REPO@$REF" log "installing box from $REPO@$REF"
# A ref is a TAG first (the pinned-release channel), a branch second (the if [[ "$REF" =~ ^[0-9a-f]{40}$ ]]; then
# dev channel, BOX_REF=main) — and the fallback only exists for a ref the # The drill resolves moving candidate refs before it mutates a host, then
# OPERATOR named: a resolved latest tag has no branch to fall through to. # installs the exact object it measured (#152). GitHub's commit archive is
URL="https://github.com/$REPO/archive/refs/tags/$REF.tar.gz" # outside refs/{tags,heads}; guessing either namespace makes every SHA 404.
log "downloading $URL" URL="https://github.com/$REPO/archive/$REF.tar.gz"
if ! curl -fsSL "$URL" -o "$TMPDIR/box.tar.gz"; then log "downloading immutable commit $URL"
[ -n "${BOX_REF:-}" ] || die "failed to download $URL"
URL="https://github.com/$REPO/archive/refs/heads/$REF.tar.gz"
log "no tag '$REF' — trying it as a branch: $URL"
curl -fsSL "$URL" -o "$TMPDIR/box.tar.gz" \ curl -fsSL "$URL" -o "$TMPDIR/box.tar.gz" \
|| die "failed to download it as either — '$REF' is neither a tag nor a branch of $REPO" || die "failed to download commit $REF from $REPO"
else
# A named ref is a TAG first (the pinned-release channel), a branch second
# (the dev channel, BOX_REF=main). The fallback only exists for a ref the
# OPERATOR named: a resolved latest tag has no branch to fall through to.
URL="https://github.com/$REPO/archive/refs/tags/$REF.tar.gz"
log "downloading $URL"
if ! curl -fsSL "$URL" -o "$TMPDIR/box.tar.gz"; then
[ -n "${BOX_REF:-}" ] || die "failed to download $URL"
URL="https://github.com/$REPO/archive/refs/heads/$REF.tar.gz"
log "no tag '$REF' — trying it as a branch: $URL"
curl -fsSL "$URL" -o "$TMPDIR/box.tar.gz" \
|| die "failed to download it as either — '$REF' is neither a tag nor a branch of $REPO"
fi
fi fi
log "extracting archive" log "extracting archive"

View file

@ -30,4 +30,4 @@ runcmd:
# HOME=/root: cloud-init runs runcmd as root but with NO $HOME in the # HOME=/root: cloud-init runs runcmd as root but with NO $HOME in the
# environment, and the rig installer (set -u) reads $HOME for its DEST — # environment, and the rig installer (set -u) reads $HOME for its DEST —
# measured live: the mint died with "HOME: unbound variable" without it. # measured live: the mint died with "HOME: unbound variable" without it.
- curl -fsSL https://raw.githubusercontent.com/@RIG_REPO@/@RIG_REF@/install.sh | HOME=/root RIG_REPO="@RIG_REPO@" RIG_REF="@RIG_REF@" bash - '@RIG_INSTALL@'

View file

@ -30,4 +30,4 @@ runcmd:
# HOME=/root: cloud-init runs runcmd as root but with NO $HOME in the # HOME=/root: cloud-init runs runcmd as root but with NO $HOME in the
# environment, and the rig installer (set -u) reads $HOME for its DEST — # environment, and the rig installer (set -u) reads $HOME for its DEST —
# measured live: the mint died with "HOME: unbound variable" without it. # measured live: the mint died with "HOME: unbound variable" without it.
- curl -fsSL https://raw.githubusercontent.com/@RIG_REPO@/@RIG_REF@/install.sh | HOME=/root RIG_REPO="@RIG_REPO@" RIG_REF="@RIG_REF@" bash - '@RIG_INSTALL@'

View file

@ -30,4 +30,4 @@ runcmd:
# HOME=/root: cloud-init runs runcmd as root but with NO $HOME in the # HOME=/root: cloud-init runs runcmd as root but with NO $HOME in the
# environment, and the rig installer (set -u) reads $HOME for its DEST — # environment, and the rig installer (set -u) reads $HOME for its DEST —
# measured live: the mint died with "HOME: unbound variable" without it. # measured live: the mint died with "HOME: unbound variable" without it.
- curl -fsSL https://raw.githubusercontent.com/@RIG_REPO@/@RIG_REF@/install.sh | HOME=/root RIG_REPO="@RIG_REPO@" RIG_REF="@RIG_REF@" bash - '@RIG_INSTALL@'

View file

@ -30,4 +30,4 @@ runcmd:
# HOME=/root: cloud-init runs runcmd as root but with NO $HOME in the # HOME=/root: cloud-init runs runcmd as root but with NO $HOME in the
# environment, and the rig installer (set -u) reads $HOME for its DEST — # environment, and the rig installer (set -u) reads $HOME for its DEST —
# measured live: the mint died with "HOME: unbound variable" without it. # measured live: the mint died with "HOME: unbound variable" without it.
- curl -fsSL https://raw.githubusercontent.com/@RIG_REPO@/@RIG_REF@/install.sh | HOME=/root RIG_REPO="@RIG_REPO@" RIG_REF="@RIG_REF@" bash - '@RIG_INSTALL@'

View file

@ -31,4 +31,4 @@ runcmd:
# HOME=/root: cloud-init runs runcmd as root but with NO $HOME in the # HOME=/root: cloud-init runs runcmd as root but with NO $HOME in the
# environment, and the rig installer (set -u) reads $HOME for its DEST — # environment, and the rig installer (set -u) reads $HOME for its DEST —
# measured live: the mint died with "HOME: unbound variable" without it. # measured live: the mint died with "HOME: unbound variable" without it.
- curl -fsSL https://raw.githubusercontent.com/@RIG_REPO@/@RIG_REF@/install.sh | HOME=/root RIG_REPO="@RIG_REPO@" RIG_REF="@RIG_REF@" bash - '@RIG_INSTALL@'

View file

@ -266,15 +266,28 @@ check "render_userdata: the pin's defaults came with it (guards the grep)" 0 "he
check "render_userdata: the extracted function is valid bash" 0 "" bash -n "$RUFN" check "render_userdata: the extracted function is valid bash" 0 "" bash -n "$RUFN"
SEED="$(mktemp)" SEED="$(mktemp)"
printf '#cloud-config\nruncmd:\n - curl -fsSL https://raw.githubusercontent.com/@RIG_REPO@/@RIG_REF@/install.sh | RIG_REPO="@RIG_REPO@" RIG_REF="@RIG_REF@" bash\n' > "$SEED" printf "#cloud-config\nruncmd:\n - '@RIG_INSTALL@'\n" > "$SEED"
# shellcheck disable=SC2016 # $0/$1 expand in the child shell, by design # shellcheck disable=SC2016 # $0/$1 expand in the child shell, by design
rud() { # rud [VAR=val ...] — render the fixture seed through the real function rud() { # rud [VAR=val ...] — render the fixture seed through the real function
env "$@" bash -c 'die() { echo "box: $*" >&2; exit 1; }; . "$0"; render_userdata "$1"' "$RUFN" "$SEED" env "$@" bash -c 'die() { echo "box: $*" >&2; exit 1; }; . "$0"; render_userdata "$1"' "$RUFN" "$SEED"
} }
check "render_userdata: defaults pin heavy-duty/rig" 0 "githubusercontent.com/heavy-duty/rig/main/install.sh" rud check "render_userdata: defaults pin heavy-duty/rig" 0 "githubusercontent.com/heavy-duty/rig/main/install.sh" rud
check "render_userdata: defaults feed the installer's own env too" 0 'RIG_REPO="heavy-duty/rig" RIG_REF="main"' rud check "render_userdata: defaults feed the installer's own env too" 0 'RIG_REPO="heavy-duty/rig" RIG_REF="main"' rud
check "render_userdata: the rig install pins HOME=/root (runcmd has no \$HOME)" 0 "HOME=/root " rud
check "render_userdata: RIG_REPO/RIG_REF override at mint" 0 "dan-claude-bot/rig/feat/bootstrap-roles/install.sh" \ check "render_userdata: RIG_REPO/RIG_REF override at mint" 0 "dan-claude-bot/rig/feat/bootstrap-roles/install.sh" \
rud RIG_REPO=dan-claude-bot/rig RIG_REF=feat/bootstrap-roles rud RIG_REPO=dan-claude-bot/rig RIG_REF=feat/bootstrap-roles
RIG_COMMIT=89abcdef0123456789abcdef0123456789abcdef
check "render_userdata: a rig commit downloads its immutable archive" 0 "archive/$RIG_COMMIT.tar.gz" \
rud RIG_REPO=heavy-duty/rig RIG_REF="$RIG_COMMIT"
# $rig_source belongs to the rendered guest shell.
# shellcheck disable=SC2016
check "render_userdata: a rig commit feeds the archive to the installer" 0 'RIG_INSTALL_SOURCE="$rig_source"' \
rud RIG_REPO=heavy-duty/rig RIG_REF="$RIG_COMMIT"
# Expansion belongs to the bash -c fixture.
# shellcheck disable=SC2016
check "render_userdata: a named rig ref keeps the normal channel" 1 "" \
bash -c 'die() { exit 1; }; RIG_REPO=heavy-duty/rig RIG_REF=main; . "$1"; render_userdata "$2" | grep -q RIG_INSTALL_SOURCE' \
_ "$RUFN" "$SEED"
# shellcheck disable=SC2016 # $0/$1 expand in the child shells, by design # shellcheck disable=SC2016 # $0/$1 expand in the child shells, by design
check "render_userdata: no token survives the render" 1 "" \ check "render_userdata: no token survives the render" 1 "" \
bash -c 'env bash -c "die() { echo box: \$*; exit 1; }; . \"\$0\"; render_userdata \"\$1\"" "$1" "$2" | grep -q @RIG_' _ "$RUFN" "$SEED" bash -c 'env bash -c "die() { echo box: \$*; exit 1; }; . \"\$0\"; render_userdata \"\$1\"" "$1" "$2" | grep -q @RIG_' _ "$RUFN" "$SEED"
@ -326,26 +339,13 @@ for d in "$ROOT"/templates/*/; do
# The thin-template contract (#81), both halves per template: # The thin-template contract (#81), both halves per template:
# #
# THE SEED — a template that names a tenant role (BOX_BOOTSTRAP_ROLE) must # THE SEED — a template that names a tenant role (BOX_BOOTSTRAP_ROLE) must
# preinstall rig carrying BOTH pin tokens, on the installer URL and on the # delegate its rig install to render_userdata. That one renderer owns both
# installer's own env, or the pin is a half-truth: a mint would fetch one # the named-ref channel and the immutable-commit archive channel.
# ref's installer and install another ref's tree.
# ------------------------------------------------------------------------ # ------------------------------------------------------------------------
trole="$(tpl "$ROOT" "$t" | sed -n 's/.*ROLE=\([^ ]*\).*/\1/p')" trole="$(tpl "$ROOT" "$t" | sed -n 's/.*ROLE=\([^ ]*\).*/\1/p')"
if [ -n "$trole" ]; then if [ -n "$trole" ]; then
check "template '$t': the seed installs rig (role '$trole')" 0 "" \ check "template '$t': the seed installs rig through the renderer (role '$trole')" 0 "" \
grep -q 'install.sh' "$d/user-data.yaml" grep -q "^[[:space:]]*- '@RIG_INSTALL@'\$" "$d/user-data.yaml"
# shellcheck disable=SC2016 # $1 expands in the child shell, by design
check "template '$t': the rig install carries the @RIG_REPO@ pin token" 0 "" \
bash -c 'grep "install.sh" "$1" | grep -q "@RIG_REPO@/@RIG_REF@"' _ "$d/user-data.yaml"
# shellcheck disable=SC2016
check "template '$t': the pin reaches the installer's env too" 0 "" \
bash -c 'grep "install.sh" "$1" | grep -q "RIG_REPO=\"@RIG_REPO@\" RIG_REF=\"@RIG_REF@\""' _ "$d/user-data.yaml"
# HOME=/root: a scar found live — cloud-init's runcmd has no $HOME and
# rig's installer (set -u) dies on it (rig#39). The pin must survive
# every seed rewrite.
# shellcheck disable=SC2016
check "template '$t': the rig install pins HOME=/root (runcmd has no \$HOME)" 0 "" \
bash -c 'grep "install.sh" "$1" | grep -q "HOME=/root "' _ "$d/user-data.yaml"
fi fi
# ------------------------------------------------------------------------ # ------------------------------------------------------------------------
# THE ABSENCE — no tenant content in ANY template, ever again. Everything a # THE ABSENCE — no tenant content in ANY template, ever again. Everything a
@ -3093,6 +3093,94 @@ check "revoke-user: the cert leftover check matches the capture, not a pipe" 0 "
check "drill: reads the installed tree through current/" 0 "" \ check "drill: reads the installed tree through current/" 0 "" \
grep -qF '.local/share/box/current/VERSION' "$ROOT/drill/drill.sh" grep -qF '.local/share/box/current/VERSION' "$ROOT/drill/drill.sh"
# ---------------------------------------------------------------------------
# Drill record emission (#152). Feed the pure renderer facts captured by the
# destructive harness and inspect the Markdown it writes; no fake Incus run is
# mistaken for evidence from real hardware.
# ---------------------------------------------------------------------------
DRILL_RECORD_DIR="$(mktemp -d)"
DRILL_RECORD="$DRILL_RECORD_DIR/0.9.1.md"
drill_record_fixture() {
bash -c '
set -u
. "$1/drill/record.sh"
findings=("FAIL: sibling isolation regressed" $'"'"'NOTE: \033[33mcontainer fallback\033[0m'"'"')
drill_write_record "$2" \
"0.9.1" "drill-0.9.1-20260818-01" "builder / Debian 13, Incus 6.0.2" \
"2026-08-18" "heavy-duty/box" "release/0.9.1" \
"0123456789abcdef0123456789abcdef01234567" \
"heavy-duty/rig" "release/0.4.0" \
"89abcdef0123456789abcdef0123456789abcdef" \
"bash drill/drill.sh --ref release/0.9.1 --run-id drill-0.9.1-20260818-01" \
84 1 2460 findings
' _ "$ROOT" "$DRILL_RECORD"
}
check "drill record: renderer writes a Markdown draft" 0 "" drill_record_fixture
check "drill record: names the release version" 0 "# Release drill — 0.9.1" cat "$DRILL_RECORD"
# Backticks are literal Markdown delimiters.
# shellcheck disable=SC2016
check "drill record: carries the shared run ID" 0 '**Run ID:** `drill-0.9.1-20260818-01`' cat "$DRILL_RECORD"
# shellcheck disable=SC2016
check "drill record: pins the box ref and full SHA" 0 'box `release/0.9.1` @ `0123456789abcdef0123456789abcdef01234567`' cat "$DRILL_RECORD"
# shellcheck disable=SC2016
check "drill record: pins the rig ref and full SHA" 0 'rig `release/0.4.0` @ `89abcdef0123456789abcdef0123456789abcdef`' cat "$DRILL_RECORD"
# shellcheck disable=SC2016
check "drill record: records the invocation" 0 '`bash drill/drill.sh --ref release/0.9.1 --run-id drill-0.9.1-20260818-01`' cat "$DRILL_RECORD"
check "drill record: renders pass/fail counts and wall clock" 0 '**84/85 passed, 1 failed.** 41 minutes wall clock.' cat "$DRILL_RECORD"
check "drill record: carries plain findings" 0 '- FAIL: sibling isolation regressed' cat "$DRILL_RECORD"
check "drill record: strips terminal colour" 1 "" grep -q $'\033' "$DRILL_RECORD"
printf 'operator evidence\n' > "$DRILL_RECORD_DIR/existing.md"
# Expansion belongs to the bash -c fixture.
# shellcheck disable=SC2016
check "drill record: refuses to overwrite evidence" 1 "already exists" \
bash -c '. "$1/drill/record.sh"; findings=(); drill_write_record "$2" v id host date repo ref sha rig rref rsha invocation 0 0 0 findings' \
_ "$ROOT" "$DRILL_RECORD_DIR/existing.md"
check "drill record: an overwrite refusal preserves the file" 0 "operator evidence" cat "$DRILL_RECORD_DIR/existing.md"
# shellcheck disable=SC2016
check "drill record: default run ID joins version and UTC date" 0 "drill-0.9.1-20260818-01" \
bash -c '. "$1/drill/record.sh"; drill_default_run_id 0.9.1 2026-08-18' _ "$ROOT"
# shellcheck disable=SC2016
check "drill record: an already exact ref stays exact" 0 "0123456789abcdef0123456789abcdef01234567" \
bash -c '. "$1/drill/record.sh"; drill_resolve_ref_sha repo 0123456789abcdef0123456789abcdef01234567' _ "$ROOT"
REF_FIXTURE="$DRILL_RECORD_DIR/ref-fixture"
git init -q "$REF_FIXTURE"
git -C "$REF_FIXTURE" config user.name fixture
git -C "$REF_FIXTURE" config user.email fixture@example.invalid
printf 'branch\n' > "$REF_FIXTURE/value"
git -C "$REF_FIXTURE" add value
git -C "$REF_FIXTURE" commit -qm branch
git -C "$REF_FIXTURE" branch candidate
printf 'tag\n' > "$REF_FIXTURE/value"
git -C "$REF_FIXTURE" commit -qam tag
git -C "$REF_FIXTURE" tag candidate
TAG_SHA="$(git -C "$REF_FIXTURE" rev-parse 'refs/tags/candidate^{commit}')"
# The installer tries a tag before a branch when both share a name, so the
# record resolver must pin the same object rather than merely finding a ref.
# shellcheck disable=SC2016
check "drill record: tag resolution matches the installer's tag-first channel" 0 "$TAG_SHA" \
bash -c '. "$1/drill/record.sh"; drill_resolve_ref_sha "$2" candidate' _ "$ROOT" "$REF_FIXTURE"
EMPTY_REMOTE="$DRILL_RECORD_DIR/empty.git"
git init -q --bare "$EMPTY_REMOTE"
EARLY_RECORD="$DRILL_RECORD_DIR/early-failure.md"
check "drill record: an unresolved source fails before consent or mutation" 1 "cannot resolve exact box SHA" \
bash "$ROOT/drill/drill.sh" --repo "$EMPTY_REMOTE" --ref absent --emit-record "$EARLY_RECORD"
check "drill record: an early preflight failure still emits evidence" 0 "drill exited early" \
cat "$EARLY_RECORD"
check "drill: help names --emit-record" 0 "--emit-record <path>" bash "$ROOT/drill/drill.sh" --help
check "drill: help names --run-id" 0 "--run-id <id>" bash "$ROOT/drill/drill.sh" --help
check "drill: --emit-record needs a value before any mutation" 2 "--emit-record needs a value" \
bash "$ROOT/drill/drill.sh" --emit-record
check "drill: --run-id needs a value before any mutation" 2 "--run-id needs a value" \
bash "$ROOT/drill/drill.sh" --run-id
rm -rf "$DRILL_RECORD_DIR"
echo "---" echo "---"
echo "$PASS passed, $FAIL failed" echo "$PASS passed, $FAIL failed"
rm -rf "$SHIMDIR" "$WORK" rm -rf "$SHIMDIR" "$WORK"

View file

@ -109,6 +109,16 @@ check "dev channel: tries the tag first" 0 "refs/tags/main.tar.gz" head -1 "$L3"
check "dev channel: then downloads the branch" 0 "" \ check "dev channel: then downloads the branch" 0 "" \
grep -qF "archive/refs/heads/main.tar.gz" "$L3" grep -qF "archive/refs/heads/main.tar.gz" "$L3"
COMMIT_REF=0123456789abcdef0123456789abcdef01234567
H5="$WORK/h5"; B5="$WORK/b5"; L5="$WORK/c5.log"
check "commit channel: installs an immutable full SHA" 0 "installing box from heavy-duty/box@$COMMIT_REF" \
ninst "$H5" "$B5" BOX_REF="$COMMIT_REF" FAKE_CURL_LOG="$L5" \
FAKE_SERVE_URL="https://github.com/heavy-duty/box/archive/$COMMIT_REF.tar.gz"
check "commit channel: downloads the commit archive directly" 0 "archive/$COMMIT_REF.tar.gz" cat "$L5"
check "commit channel: never guesses tag or branch namespaces" 1 "" grep -Eq 'refs/(tags|heads)' "$L5"
check "commit channel: records the immutable source" 0 "heavy-duty/box@$COMMIT_REF" \
cat "$H5/versions/9.9.9/INSTALLED_FROM"
H4="$WORK/h4"; B4="$WORK/b4"; L4="$WORK/c4.log" H4="$WORK/h4"; B4="$WORK/b4"; L4="$WORK/c4.log"
check "resolution failure: names the latest-release probe" 1 "could not resolve the latest release" \ check "resolution failure: names the latest-release probe" 1 "could not resolve the latest release" \
ninst "$H4" "$B4" FAKE_CURL_RC=6 FAKE_CURL_LOG="$L4" ninst "$H4" "$B4" FAKE_CURL_RC=6 FAKE_CURL_LOG="$L4"
@ -122,6 +132,7 @@ check "unknown ref names both attempted channels" 1 "neither a tag nor a branch"
check "README documents the latest-release channel" 0 "" grep -qF 'latest release' "$ROOT/README.md" check "README documents the latest-release channel" 0 "" grep -qF 'latest release' "$ROOT/README.md"
check "README documents the pinned channel" 0 "" grep -qF 'BOX_REF=0.6.0' "$ROOT/README.md" check "README documents the pinned channel" 0 "" grep -qF 'BOX_REF=0.6.0' "$ROOT/README.md"
check "README documents the dev channel" 0 "" grep -qF 'BOX_REF=main' "$ROOT/README.md" check "README documents the dev channel" 0 "" grep -qF 'BOX_REF=main' "$ROOT/README.md"
check "README documents the immutable commit channel" 0 "" grep -qF 'BOX_REF=<full-commit-sha>' "$ROOT/README.md"
echo "---" echo "---"
echo "$PASS passed, $FAIL failed" echo "$PASS passed, $FAIL failed"