refactor: one drill record per version, in drills/ #104
8 changed files with 344 additions and 276 deletions
138
.github/scripts/drill-recorded.sh
vendored
138
.github/scripts/drill-recorded.sh
vendored
|
|
@ -1,10 +1,10 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# drill-recorded.sh [<runs-file>] [<version-file>] — assert that the version
|
||||
# this tree is about to ship has a DRILL RECORD in drill/RUNS.md.
|
||||
# drill-recorded.sh [<drills-dir>] [<version-file>] — assert that the version
|
||||
# this tree is about to ship has a DRILL RECORD at <drills-dir>/<version>.md.
|
||||
#
|
||||
# defaults: drill/RUNS.md VERSION
|
||||
# defaults: drills VERSION
|
||||
#
|
||||
# CONTRIBUTING ("Releasing") says a release carries a real-hardware drill.
|
||||
# Nothing enforced it, so no release in this family has ever had one: the
|
||||
|
|
@ -15,6 +15,21 @@ set -euo pipefail
|
|||
# interesting, which is never at 0.4.3. A bot finally blocked on it; this is
|
||||
# that block, moved into CI where it does not depend on anyone's attention.
|
||||
#
|
||||
# ONE FILE PER VERSION, which is what this script is now mostly about. The
|
||||
# first cut of this guard kept every record as a section inside one
|
||||
# drill/RUNS.md, and paid for it: it needed an awk extractor that matched a
|
||||
# literal '## Release drill — ' prefix, tolerated an optional ' — DATE' tail,
|
||||
# compared the version WHOLE so that '0.3.0-rc1' could not answer for '0.3.0',
|
||||
# and then separately insisted the extracted body hold a non-blank line. Every
|
||||
# one of those rules existed only because records shared a file. Both sibling
|
||||
# repos shipped a DEFECT out of that complexity during review — a
|
||||
# `sed '/./,$!d'` extractor where `.` matches a space, so a heading plus one
|
||||
# tab satisfied the gate (box#149, cast#138), and heading-grammar drift on the
|
||||
# other side. Splitting the records makes nearly all of it unrepresentable:
|
||||
# `0.3.0.md` and `0.3.0-rc1.md` are simply different files, there is no
|
||||
# heading to parse and no grammar to drift, and the whole-version comparison
|
||||
# is done by the filesystem.
|
||||
#
|
||||
# PER-REPO, and that is the load-bearing design decision. The obvious
|
||||
# alternative — have rig ask box's repo whether the drill ran — cannot fail
|
||||
# safely: the lookup needs a network call, a token, and a checkout that may be
|
||||
|
|
@ -26,13 +41,18 @@ set -euo pipefail
|
|||
# records rig's own legs in rig's own repo, and this script reads a file that
|
||||
# is either in the checkout or is not.
|
||||
#
|
||||
# The directory is `drills/`, NOT `.drills/`. A dot-directory is invisible to
|
||||
# every glob that has not set `dotglob`, which is how #70 here and box#116 /
|
||||
# box#118 all happened: a file that exists but that no sweep can see is worse
|
||||
# than no file, because it reads as covered.
|
||||
#
|
||||
# What it asserts is a RECORD, not a RESULT — and that is deliberate, not a
|
||||
# weakness. A gate that demanded "the drill passed" would have to parse
|
||||
# somebody's prose for a verdict, and would leave a maintainer who consciously
|
||||
# ships without a full drill (a doc-only release, a hardware outage) with no
|
||||
# move except deleting the check. Requiring a record means the waiver is
|
||||
# WRITTEN DOWN, under the version it applies to, in a commit a reviewer sees.
|
||||
# Skipping stays possible; skipping silently does not.
|
||||
# WRITTEN DOWN, in a file named for the version it applies to, in a commit a
|
||||
# reviewer sees. Skipping stays possible; skipping silently does not.
|
||||
#
|
||||
# Vacuous on a `-dev` tree, which is why it needs no trigger scoping in
|
||||
# ci.yml (unlike changelog-monotonic.sh, whose input is a diff): every ordinary
|
||||
|
|
@ -40,9 +60,13 @@ set -euo pipefail
|
|||
# all. The check has something to say on exactly one tree — the release
|
||||
# ceremony PR — and that is the tree it must be impossible to merge without.
|
||||
|
||||
runs="${1:-drill/RUNS.md}"
|
||||
drills="${1:-drills}"
|
||||
version_file="${2:-VERSION}"
|
||||
|
||||
# An unreadable version file is an ERROR, never a silent pass. There is no
|
||||
# version to be lenient about, so leniency here could only mean "ship
|
||||
# unevidenced" — the exact degradation the per-repo decision above exists to
|
||||
# avoid.
|
||||
[ -f "$version_file" ] || {
|
||||
echo "drill-recorded: no such file: $version_file" >&2
|
||||
exit 1
|
||||
|
|
@ -65,88 +89,60 @@ case "$version" in
|
|||
;;
|
||||
esac
|
||||
|
||||
# drill_section <file> <version> — print the BODY under that version's
|
||||
# heading: everything between it and the next '## ' (or EOF), leading blank
|
||||
# lines dropped. Empty output means "no record", which is the failure.
|
||||
#
|
||||
# Modelled on release-lib.sh's changelog_section(), with one difference that
|
||||
# is the whole reason it is not that function: changelog_section() matches on
|
||||
# awk's field $2, which works because a changelog heading is '## <version> …'.
|
||||
# Here the version is the FOURTH field of '## Release drill — X.Y.Z — DATE',
|
||||
# and matching by field number would break the moment the em dashes moved. So
|
||||
# this matches the literal prefix and then compares the version WHOLE.
|
||||
#
|
||||
# Whole is the point. A prefix match makes '0.3.0' satisfied by a record for
|
||||
# '0.3.0-rc1' — a drill run against a release candidate, silently accepted as
|
||||
# evidence for the final — and, in the other direction, makes an '0.3.0'
|
||||
# record satisfy '0.3.0-rc1'. Both are the same defect: the string that
|
||||
# matched is not the artefact that ships. changelog_section()'s exact `$2 ==
|
||||
# ver` is the precedent; this preserves it through a longer heading.
|
||||
drill_section() {
|
||||
awk -v ver="$2" '
|
||||
/^## / {
|
||||
if (found) exit
|
||||
line = $0; sub(/[[:space:]]+$/, "", line)
|
||||
found = 0
|
||||
pfx = "## Release drill — "
|
||||
if (index(line, pfx) == 1) {
|
||||
rest = substr(line, length(pfx) + 1)
|
||||
# Split at the version LENGTH, then demand the remainder be either
|
||||
# nothing or the optional " — <date>". A trailing "-rc1" lands in
|
||||
# tail, fails both, and is correctly not a match. (Double quotes on
|
||||
# purpose: an apostrophe here would close the awk program.)
|
||||
if (substr(rest, 1, length(ver)) == ver) {
|
||||
tail = substr(rest, length(ver) + 1)
|
||||
if (tail == "" || index(tail, " —") == 1) found = 1
|
||||
}
|
||||
}
|
||||
next
|
||||
}
|
||||
found && !body && /^[[:space:]]*$/ { next }
|
||||
found { body = 1; print }
|
||||
' "$1"
|
||||
}
|
||||
record="$drills/$version.md"
|
||||
|
||||
# A missing runs file is not a different failure from a missing section: both
|
||||
# mean "this release has no recorded drill", and both want the same unblock
|
||||
# text. The first release under this gate in a repo with no drill/ directory
|
||||
# yet is the missing-file case, and it must read as a to-do, not as a broken
|
||||
# invocation.
|
||||
record=""
|
||||
if [ -f "$runs" ]; then
|
||||
record="$(drill_section "$runs" "$version")"
|
||||
# WHITESPACE IS NOT A RECORD. This is the one surviving piece of the rule set
|
||||
# the old section-parsing guard needed, and it survives because it is the one
|
||||
# part that splitting the files does not make unrepresentable: an empty file,
|
||||
# or a file holding only spaces, tabs and newlines, exists at the right path
|
||||
# and is still no evidence. It is the same property box#149 and cast#138 both
|
||||
# got wrong with `sed '/./,$!d'` (`.` matches a space), where a record of one
|
||||
# tab shipped an evidence-free release. `grep -q '[^[:space:]]'` is the whole
|
||||
# check now, with no extractor in front of it to get wrong.
|
||||
#
|
||||
# Written as if/then, NOT as `[ -f "$record" ] && recorded=yes`. Under `set -e`
|
||||
# a bare `a && b` statement whose last command fails takes the whole script
|
||||
# down with its exit status — so a miss would exit 1 here, before the failure
|
||||
# message below ever printed, and the author would see nothing.
|
||||
recorded=no
|
||||
if [ -f "$record" ] && grep -q '[^[:space:]]' "$record"; then
|
||||
recorded=yes
|
||||
fi
|
||||
|
||||
if [ -z "$record" ]; then
|
||||
if [ "$recorded" != yes ]; then
|
||||
{
|
||||
echo "drill-recorded: VERSION is $version, and $runs has NO drill record for it."
|
||||
echo "drill-recorded: VERSION is $version, and there is no drill record at $record."
|
||||
echo
|
||||
cat <<EOF
|
||||
This tree is a release ceremony tree — VERSION is bare, so merging it ships
|
||||
$version. CONTRIBUTING ("Releasing") requires that release to carry a real
|
||||
hardware drill, recorded in $runs under a heading of exactly this shape:
|
||||
hardware drill, recorded in a file named for the version, exactly:
|
||||
|
||||
## Release drill — $version — YYYY-MM-DD
|
||||
$drills/$version.md
|
||||
|
||||
The trailing date is optional; the version is matched WHOLE, so a record for
|
||||
$version-rc1 (or for a different version entirely) does not count. The
|
||||
section must have at least one non-blank line under it — a bare heading is
|
||||
not a record.
|
||||
One file per version, so the name IS the match: a record for
|
||||
$version-rc1 lives at a different path and does not count. The file must
|
||||
hold at least one non-whitespace character — an empty file, or one of only
|
||||
spaces and tabs, is not a record.
|
||||
|
||||
Two ways to unblock, and both are a commit on this PR:
|
||||
|
||||
1. RUN THE DRILL and record it. What ran, on what hardware, the numbers,
|
||||
and what failed. It is ONE orchestrated run over the whole stack, on
|
||||
CANDIDATE refs (RIG_REPO/RIG_REF are mint-time variables, so the run
|
||||
pins the commits under test) — drilling the candidate IS drilling the
|
||||
release, since a release PR's diff is VERSION + CHANGELOG.md and nothing
|
||||
executable differs. Cite the run ID and the other repos' SHAs.
|
||||
and what failed. rig's drill asserts CONVERGENCE — a machine reaches
|
||||
its role, idempotently — against a PINNED set of candidate refs
|
||||
(RIG_REPO/RIG_REF and BOX_REF are mint-time variables, so the run pins
|
||||
the commits under test). Drilling the candidate IS drilling the
|
||||
release, since a release PR's diff is VERSION + CHANGELOG.md and
|
||||
nothing executable differs. Cite the run ID and the other repos' SHAs.
|
||||
The three repos' drills are independent — rig's does not wait on box's.
|
||||
|
||||
2. RECORD AN EXPLICIT MAINTAINER WAIVER under the same heading, saying who
|
||||
2. RECORD AN EXPLICIT MAINTAINER WAIVER in that same file, saying who
|
||||
waived it and why. This guard asks for a RECORD, not a passing result,
|
||||
so a deliberate skip is allowed — it just has to be visible and
|
||||
reviewable rather than silent.
|
||||
|
||||
See $drills/README.md for what a record should contain.
|
||||
|
||||
Do not delete this step to get green. A release that cannot say what was
|
||||
drilled is the state this check exists to end.
|
||||
EOF
|
||||
|
|
@ -154,5 +150,5 @@ EOF
|
|||
exit 1
|
||||
fi
|
||||
|
||||
lines="$(printf '%s\n' "$record" | grep -c . || true)"
|
||||
echo "drill-recorded: $runs records a drill for $version ($lines line(s) under the heading)."
|
||||
lines="$(grep -c . "$record" || true)"
|
||||
echo "drill-recorded: $record records a drill for $version ($lines non-blank line(s))."
|
||||
|
|
|
|||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
|
@ -78,7 +78,7 @@ jobs:
|
|||
CHANGELOG_MONOTONIC_STRICT: '1'
|
||||
run: bash .github/scripts/changelog-monotonic.sh "origin/${{ github.base_ref || github.ref_name }}"
|
||||
# The release this tree would ship has a recorded real-hardware drill
|
||||
# (drill/RUNS.md). CONTRIBUTING ("Releasing") has always required one and
|
||||
# (drills/<version>.md). CONTRIBUTING ("Releasing") has always required one and
|
||||
# nothing enforced it, so no release in this family has ever carried one
|
||||
# — the drill was the single ceremony step checked by a reviewer
|
||||
# remembering rather than by a script.
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ on the way to cutting its first release, and this file starts there.
|
|||
|
||||
### Added
|
||||
|
||||
- CI refuses a release PR with no drill record in `drill/RUNS.md`
|
||||
- CI refuses a release PR with no drill record at `drills/<version>.md`
|
||||
- `rig platform` — what this machine is, computed at run time, stored nowhere
|
||||
(#64)
|
||||
- `/etc/rig/manifest` records which rig converged a machine, and when (#61)
|
||||
|
|
|
|||
|
|
@ -100,33 +100,52 @@ extra gate before the handoff:
|
|||
|
||||
The **drill** is a real-hardware run — tenant guests minted and converged via
|
||||
box, `test/db-integration.sh`, the GitHub runner lifecycle against a fork, a
|
||||
coolify install — recorded in [`drill/RUNS.md`](drill/RUNS.md) under a heading
|
||||
of exactly:
|
||||
coolify install — recorded in **one file per version**:
|
||||
|
||||
```
|
||||
## Release drill — X.Y.Z — YYYY-MM-DD
|
||||
drills/<version>.md
|
||||
```
|
||||
|
||||
named for the version exactly as `VERSION` carries it. See
|
||||
[`drills/README.md`](drills/README.md) for what a record should contain.
|
||||
|
||||
`.github/scripts/drill-recorded.sh` enforces it on every release: a bare
|
||||
`VERSION` with no non-empty section for it turns CI red, naming the version.
|
||||
It is **not a thing a reviewer has to remember** — that is how every release
|
||||
in this family shipped undrilled until a bot finally blocked on one. On a
|
||||
`-dev` tree it asserts nothing, so it is invisible to ordinary PRs. rig reads
|
||||
`VERSION` with no non-empty `drills/<version>.md` turns CI red, naming the
|
||||
version. It is **not a thing a reviewer has to remember** — that is how every
|
||||
release in this family shipped undrilled until a bot finally blocked on one. On
|
||||
a `-dev` tree it asserts nothing, so it is invisible to ordinary PRs. rig reads
|
||||
rig's own record and never box's repo: a cross-repo lookup fails on a token,
|
||||
a fork checkout or a network blip, and all of those degrade to "pass" —
|
||||
the UNREADABLE-vs-NONE shape #90 fixed.
|
||||
|
||||
**The drill is ONE orchestrated run over the whole stack**, not three
|
||||
independent ones. box and rig are mutually recursive, so there is no linear
|
||||
order to drill them in: rig sits *below* box as the host-builder and *above*
|
||||
it as the guest-converger. The run therefore goes:
|
||||
One file per version is what keeps the guard small. Records used to share a
|
||||
single log, which forced a heading grammar, an optional-date tail, a
|
||||
whole-version comparison and a non-blank-body rule just to read them back — and
|
||||
both sibling repos shipped a defect out of that complexity in review. Now
|
||||
`0.3.0.md` and `0.3.0-rc1.md` are simply different files.
|
||||
|
||||
1. `rig bootstrap … --host yes` on a bare Debian host — which installs box and
|
||||
runs box's `setup-host` (`RIG_SKIP_BOX_INSTALL=1` skips it; see README)
|
||||
2. `box new` mints a creds-free seed
|
||||
3. the seed converges via `rig bootstrap <tenant>-box` — the seed's cloud-init
|
||||
curls rig's installer at `@RIG_REPO@/@RIG_REF@`
|
||||
4. cast on top
|
||||
**The three repos' drills are INDEPENDENT.** Run them in any order, on any
|
||||
schedule, in separate sittings. What makes that safe is that every drill **pins
|
||||
the same fixed set of candidate refs**: rig's drill runs `--host yes` with
|
||||
`BOX_REF=release/<box-version>`, so it exercises the box that will actually
|
||||
ship; box's drill mints with `RIG_REF=release/<rig-version>`, so it exercises
|
||||
the rig that will actually ship. Both measure the same pair.
|
||||
|
||||
That — not sequencing — is what dissolves the box↔rig recursion. box and rig
|
||||
are mutually recursive (`rig bootstrap … --host yes` installs box and runs
|
||||
box's `setup-host`; box's `box new` seeds converge back through rig's installer
|
||||
at `@RIG_REPO@/@RIG_REF@`), but the refs are static identifiers that exist as
|
||||
soon as the release branches do, long before any drill runs, so a cycle at
|
||||
runtime becomes independent tests against one fixed pair. Within a single drill
|
||||
you naturally bring the substrate up before probing it — a host before a guest
|
||||
— but that is how you run a drill, not an ordering rule between repos.
|
||||
|
||||
Each repo drills in a **different way** and asserts a different thing: rig
|
||||
asserts **convergence** (a machine reaches its role, idempotently), box asserts
|
||||
the **isolation contract** (the VM trust boundary), cast asserts **promotion**
|
||||
(A→B reproduces, the diff is idempotent). Three different exercises sharing a
|
||||
substrate, not three phases of one script — which is exactly why the records
|
||||
are per-repo.
|
||||
|
||||
It drills **candidate refs, not released artifacts.** `RIG_REPO`/`RIG_REF` are
|
||||
mint-time environment variables (default `heavy-duty/rig@main`), so a run pins
|
||||
|
|
@ -137,16 +156,18 @@ repo has to be released before another can be drilled.
|
|||
`VERSION` + `CHANGELOG.md` and nothing else — no executable difference exists
|
||||
between the tree that was drilled and the tree that ships.
|
||||
|
||||
One run emits **one shared run ID**. Each repo records *its own* legs under its
|
||||
own `## Release drill — X.Y.Z — DATE`, citing that run ID and the other two
|
||||
repos' commit SHAs, so the three records can be joined after the fact by
|
||||
anyone reading them. The guard still reads only this repo's file — there is no
|
||||
Drills that share a substrate share **one run ID**. Each repo records *its own*
|
||||
legs in its own `drills/<version>.md`, citing that run ID and the other two
|
||||
repos' commit SHAs, so the records can be joined after the fact by anyone
|
||||
reading them. The guard still reads only this repo's file — there is no
|
||||
cross-repo lookup anywhere in the gate. Releases do **not** have to be
|
||||
published in a fixed order.
|
||||
published in a fixed order. If a defect shows up only in the combination:
|
||||
patch, re-drill, re-record. The three releases converge on a set that holds
|
||||
together; they are not required to be right in one pass.
|
||||
|
||||
A **maintainer waiver** is possible — a doc-only release, a hardware outage —
|
||||
but it must be **recorded in `drill/RUNS.md` for that version**, saying who
|
||||
waived it and why. The guard asks for a *record*, not a passing result,
|
||||
but it must be **recorded in `drills/<version>.md` for that version**, saying
|
||||
who waived it and why. The guard asks for a *record*, not a passing result,
|
||||
precisely so that skipping is a deliberate, reviewable commit instead of a
|
||||
silence. Deleting the check is not the move.
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ PR carries as many as apply.
|
|||
| `blocker:conflict` | `#B60205` | GitHub says `CONFLICTING` — the agent owes a **rebase** | it merges cleanly |
|
||||
| `blocker:ci-red` | `#B60205` | a check failed — the agent owes a **fix**, which a rebase will not provide | checks are green |
|
||||
| `blocker:unrequested` | `#E99695` | this head has no verdict from somebody — never reviewed, or staled by a push — and **nobody was asked** for one | reviews are requested |
|
||||
| `blocker:drill-pending` | `#E99695` | a `release` PR whose version has **no drill record** in [`drill/RUNS.md`](drill/RUNS.md) — the ceremony is correct but *unevidenced* | the drill is run and recorded, or a maintainer waiver is recorded for that version |
|
||||
| `blocker:drill-pending` | `#E99695` | a `release` PR whose version has **no drill record** at [`drills/<version>.md`](drills/README.md) — the ceremony is correct but *unevidenced* | the drill is run and recorded, or a maintainer waiver is recorded for that version |
|
||||
|
||||
`blocker:drill-pending` is the one blocker that is not about the code: the
|
||||
branch merges, the checks that read the tree are green, and the release is
|
||||
|
|
@ -165,7 +165,7 @@ gh label create "blocker:conflict" --color B60205 --description "Does not me
|
|||
gh label create "blocker:ci-red" --color B60205 --description "A check is failing — the agent owes a fix (not a rebase)" --force
|
||||
gh label create "blocker:unrequested" --color E99695 --description "Somebody still owes a verdict and nobody was asked for one" --force
|
||||
# Needs a MAINTAINER account — the bot 403s on label creation. Until it exists, `blocked` stands in.
|
||||
gh label create "blocker:drill-pending" --color E99695 --description "Release PR with no drill record in drill/RUNS.md — correct but unevidenced" --force
|
||||
gh label create "blocker:drill-pending" --color E99695 --description "Release PR with no drill record at drills/<version>.md — correct but unevidenced" --force
|
||||
# retired — the reconciler strips it; delete it once no PR carries it
|
||||
# gh label delete "state:needs-rebase"
|
||||
gh label create "state:needs-human" --color 8250DF --description "No blockers, all bots approve — waiting on the human reviewer" --force
|
||||
|
|
|
|||
|
|
@ -1,80 +0,0 @@
|
|||
# Drill run log
|
||||
|
||||
rig's own record of its real-hardware drill legs. One section per run,
|
||||
appended. **This file is the record, not the instrument.**
|
||||
|
||||
rig has **no drill harness script of its own yet**. Its legs are run by hand,
|
||||
following the documented procedure:
|
||||
|
||||
- tenant guests minted and converged **via box**
|
||||
- `bash test/db-integration.sh` against a real Postgres on the machine
|
||||
- the GitHub runner lifecycle — register, take a job, deregister — against a
|
||||
fork
|
||||
- a coolify install
|
||||
|
||||
The harness lives in heavy-duty/box's `drill/`. rig does **not** reach into it
|
||||
to decide whether rig may ship. A cross-repo lookup that fails silently
|
||||
degrades to "pass", which is the UNREADABLE-vs-NONE shape #90 fixed — so the
|
||||
gate reads this file, in this repo, and nothing else.
|
||||
|
||||
The drill itself is **one orchestrated run over the whole stack** —
|
||||
`rig bootstrap --host yes` on a bare host (which installs box and runs box's
|
||||
`setup-host`), then `box new` for a creds-free seed, then that seed converging
|
||||
via `rig bootstrap <tenant>-box`, then cast on top. box and rig are mutually
|
||||
recursive, so there is no order to drill them in: rig is both the host-builder
|
||||
below box and the guest-converger above it.
|
||||
|
||||
It drills **candidate refs, not released artifacts**: `RIG_REPO`/`RIG_REF` are
|
||||
mint-time environment variables, so a run pins the exact commits under test.
|
||||
Drilling the candidate *is* drilling the release, because a release PR's diff
|
||||
is `VERSION` + `CHANGELOG.md` and nothing executable differs. One run emits one
|
||||
shared **run ID**; each repo records its own legs under its own heading, citing
|
||||
that run ID and the other two repos' commit SHAs (CONTRIBUTING, "Releasing").
|
||||
|
||||
## What the gate requires
|
||||
|
||||
`.github/scripts/drill-recorded.sh` runs on every PR. On a `-dev` tree it
|
||||
asserts nothing — a development tree has no release to evidence. On a bare
|
||||
`VERSION` — a release ceremony tree — it requires a section here headed
|
||||
exactly:
|
||||
|
||||
## Release drill — X.Y.Z — YYYY-MM-DD
|
||||
|
||||
The trailing ` — DATE` is optional; the version is matched **whole**, so a
|
||||
`0.3.0-rc1` record does not satisfy `0.3.0` and the reverse is equally false.
|
||||
The section must extract non-empty: at least one non-blank line before the
|
||||
next `## `.
|
||||
|
||||
The guard requires a **record**, not a passing result. A maintainer waiver is
|
||||
a legitimate outcome of a release — but it is written here, under that
|
||||
version's heading, so that skipping the drill is a deliberate, reviewable
|
||||
commit rather than a silence.
|
||||
|
||||
### What a record should contain
|
||||
|
||||
What ran, on what, the numbers, and what failed. Below is the *shape*, not a
|
||||
run that happened — no drill has been recorded here yet:
|
||||
|
||||
## Release drill — 9.9.9 — 2026-01-01
|
||||
|
||||
Run ID: drill-2026-01-01-a. Host: bare Debian 13 cloud image, 4 vCPU / 8 GB.
|
||||
Candidate refs: box@1a2b3c4, rig@5d6e7f8, cast@9a0b1c2.
|
||||
|
||||
| Leg | Result |
|
||||
| --- | --- |
|
||||
| tenant guests minted + converged via box | 3/3 |
|
||||
| `test/db-integration.sh` | 14/14 |
|
||||
| runner lifecycle against a fork | PASS — registered, took a job, deregistered clean |
|
||||
| coolify install | PASS, ~6 min |
|
||||
|
||||
Failed: `rig users apply` left one revoked key in `authorized_keys`
|
||||
(filed #NNN). Everything else clean.
|
||||
|
||||
State what failed. A record with no failures listed reads as "nothing broke",
|
||||
so if a leg was not run, say that instead of omitting it.
|
||||
|
||||
## Runs
|
||||
|
||||
*None recorded yet.* This log starts empty rather than reconstructing runs
|
||||
from memory — an invented number is worse than no number. The first release
|
||||
cut under the gate writes the first section here.
|
||||
113
drills/README.md
Normal file
113
drills/README.md
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
# Drill records
|
||||
|
||||
Per-release evidence for rig's real-hardware drill. **One file per version**,
|
||||
named for the version exactly as `VERSION` carries it:
|
||||
|
||||
drills/<version>.md
|
||||
|
||||
So `0.3.0` is recorded in `drills/0.3.0.md`, and `0.3.0-rc1` in
|
||||
`drills/0.3.0-rc1.md`. They are different files, which is the whole point: the
|
||||
filesystem does the whole-version comparison that an earlier single-file
|
||||
version of this had to do with an awk extractor and a heading grammar. A
|
||||
record for the candidate cannot be mistaken for evidence for the final.
|
||||
|
||||
The directory is `drills/`, not `.drills/` — a dot-directory is invisible to
|
||||
any glob without `dotglob`, which is how #70 here and box#116 / box#118 all
|
||||
happened.
|
||||
|
||||
**This directory is the record, not the instrument.** rig has **no drill
|
||||
harness script of its own**; its legs are run by following the documented
|
||||
procedure, and the harness lives in heavy-duty/box's `drill/`. rig does not
|
||||
reach into it to decide whether rig may ship: a cross-repo lookup that fails
|
||||
silently degrades to "pass", which is the UNREADABLE-vs-NONE shape #90 fixed.
|
||||
The gate reads a file in this repo, and nothing else.
|
||||
|
||||
## What the gate requires
|
||||
|
||||
`.github/scripts/drill-recorded.sh` runs on every PR. On a `-dev` tree it
|
||||
asserts nothing — a development tree has no release to evidence. On a bare
|
||||
`VERSION` — a release ceremony tree — it requires `drills/<version>.md` to
|
||||
exist and to hold at least one non-whitespace character. An empty file, or one
|
||||
of only spaces and tabs, is not a record (box#149 and cast#138 both shipped an
|
||||
extractor where a single tab satisfied the gate).
|
||||
|
||||
The guard requires a **record**, not a passing result. A maintainer waiver is
|
||||
a legitimate outcome of a release — but it is written in that version's file,
|
||||
so that skipping the drill is a deliberate, reviewable commit rather than a
|
||||
silence. **A failed drill is still a valid record**: the gate wants evidence,
|
||||
not success.
|
||||
|
||||
## The drill
|
||||
|
||||
rig's legs:
|
||||
|
||||
- tenant guests minted and converged **via box**
|
||||
- `bash test/db-integration.sh` against a real Postgres on the machine
|
||||
- the GitHub runner lifecycle — register, take a job, deregister — against a
|
||||
fork
|
||||
- a coolify install
|
||||
|
||||
box and rig are **mutually recursive**: `rig bootstrap --host yes` installs box
|
||||
and runs box's `setup-host`, while box's guests converge back through rig's
|
||||
installer. Within a single drill you naturally bring the substrate up before
|
||||
probing it — a host before a guest — but that is how you run a drill, not an
|
||||
ordering rule between repos.
|
||||
|
||||
**The three repos' drills are independent.** Run them in any order, on any
|
||||
schedule, in separate sittings. What makes that safe is that every drill
|
||||
**pins the same fixed set of candidate refs**: rig's drill runs `--host yes`
|
||||
with `BOX_REF=release/<box-version>`, so it exercises the box that will
|
||||
actually ship; box's drill mints with `RIG_REF=release/<rig-version>`, so it
|
||||
exercises the rig that will actually ship. Both measure the same pair.
|
||||
|
||||
That — not sequencing — is what dissolves the box↔rig recursion. The refs are
|
||||
static identifiers that exist as soon as the release branches do, long before
|
||||
any drill runs, so a cycle at runtime becomes two independent tests against
|
||||
one fixed pair. It also means **candidate refs, not released artifacts**:
|
||||
`RIG_REPO`/`RIG_REF` are mint-time environment variables, so no repo has to be
|
||||
released before another can be drilled. Drilling the candidate *is* drilling
|
||||
the release, because a release PR's diff is `VERSION` + `CHANGELOG.md` and
|
||||
nothing executable differs.
|
||||
|
||||
Each repo drills in a **different way** and asserts a different thing: rig
|
||||
asserts **convergence** (a machine reaches its role, idempotently), box asserts
|
||||
the **isolation contract** (the VM trust boundary), cast asserts **promotion**
|
||||
(A→B reproduces, the diff is idempotent). Three different exercises sharing a
|
||||
substrate, not three phases of one script — which is exactly why the records
|
||||
are per-repo.
|
||||
|
||||
Drills that share a substrate share **one run ID**; each repo records its own
|
||||
legs in its own file, citing that run ID and the other repos' commit SHAs, so
|
||||
the records can be joined after the fact. If a defect shows up only in the
|
||||
combination: patch, re-drill, re-record. The three releases converge on a set
|
||||
that holds together; they are not required to be right in one pass. Releases
|
||||
do **not** have to be published in a fixed order.
|
||||
|
||||
## What a record should contain
|
||||
|
||||
What ran, on what host, the pinned candidate refs, the numbers, and what
|
||||
failed. Below is the *shape*, in a file named `drills/9.9.9.md` — a version
|
||||
that can never collide with a real release. **No drill has been recorded here
|
||||
yet**; this log starts empty rather than reconstructing runs from memory, since
|
||||
an invented number is worse than no number.
|
||||
|
||||
```markdown
|
||||
# Release drill — 9.9.9 — 2026-01-01
|
||||
|
||||
Run ID: drill-2026-01-01-a. Host: bare Debian 13 cloud image, 4 vCPU / 8 GB.
|
||||
Candidate refs: box@1a2b3c4 (BOX_REF=release/0.4.0), rig@5d6e7f8, cast@9a0b1c2.
|
||||
|
||||
| Leg | Result |
|
||||
| --- | --- |
|
||||
| tenant guests minted + converged via box | 3/3 |
|
||||
| re-converge (idempotence) | clean, no changes |
|
||||
| `test/db-integration.sh` | 14/14 |
|
||||
| runner lifecycle against a fork | PASS — registered, took a job, deregistered clean |
|
||||
| coolify install | PASS, ~6 min |
|
||||
|
||||
Failed: `rig users apply` left one revoked key in `authorized_keys`
|
||||
(filed #NNN). Everything else clean.
|
||||
```
|
||||
|
||||
State what failed. A record with no failures listed reads as "nothing broke",
|
||||
so if a leg was not run, say that instead of omitting it.
|
||||
212
test/release.sh
212
test/release.sh
|
|
@ -471,49 +471,71 @@ check "ci.yml: the checkout has full history (the base ref must resolve)" 0 "" \
|
|||
# other ceremony step is checked by a script, and the one that costs an
|
||||
# afternoon was checked by a reviewer remembering. A bot finally blocked on it.
|
||||
#
|
||||
# Fixtures carry their OWN VERSION and RUNS.md, inside the fixture dir. This is
|
||||
# not tidiness — it is heavy-duty/box#146, verbatim: fixtures that read the
|
||||
# REPO's VERSION exercised the `-dev` branch on every ordinary tree, so the
|
||||
# whole bare-version half of the guard was untested and went red for the first
|
||||
# time while somebody was cutting a release. A fixture must state the tree it
|
||||
# is about.
|
||||
# Records are ONE FILE PER VERSION, at drills/<version>.md. The first cut of
|
||||
# this guard kept them as sections in a single drill/RUNS.md and needed a
|
||||
# heading grammar, an optional-date tail, a whole-version comparison and a
|
||||
# non-blank-body rule to read them back — all of it there only because the
|
||||
# records shared a file, and both sibling repos shipped a defect out of it in
|
||||
# review. Splitting the files deletes most of these tests along with the code
|
||||
# they covered: `0.3.0.md` and `0.3.0-rc1.md` cannot be confused by any
|
||||
# grammar, because there is no grammar.
|
||||
#
|
||||
# Fixtures carry their OWN version file and their OWN drills dir, inside the
|
||||
# fixture dir. This is not tidiness — it is heavy-duty/box#146, verbatim:
|
||||
# fixtures that read the REPO's VERSION exercised the `-dev` branch on every
|
||||
# ordinary tree, so the whole bare-version half of the guard was untested and
|
||||
# went red for the first time while somebody was cutting a release. A fixture
|
||||
# must state the tree it is about.
|
||||
DRILL="$ROOT/.github/scripts/drill-recorded.sh"
|
||||
check "drill-recorded.sh: exists and is the guard under test" 0 "" test -f "$DRILL"
|
||||
check "drill-recorded.sh: is executable" 0 "" test -x "$DRILL"
|
||||
|
||||
drilltree() { # drilltree <name> <version> <runs-line...> -> prints the dir
|
||||
drilltree() { # drilltree <name> <version> -> prints the dir (no drills/ yet)
|
||||
local d="$WORK/drill-$1"; mkdir -p "$d"; printf '%s\n' "$2" > "$d/VERSION"
|
||||
shift 2; printf '%s\n' "$@" > "$d/RUNS.md"; printf '%s' "$d"
|
||||
printf '%s' "$d"
|
||||
}
|
||||
drill() { bash "$DRILL" "$1/RUNS.md" "$1/VERSION" 2>&1; }
|
||||
drillrec() { # drillrec <dir> <version> <line...> — write drills/<version>.md
|
||||
mkdir -p "$1/drills"; local f="$1/drills/$2.md"; shift 2
|
||||
printf '%s\n' "$@" > "$f"
|
||||
}
|
||||
drill() { bash "$DRILL" "$1/drills" "$1/VERSION" 2>&1; }
|
||||
|
||||
# A development tree. Vacuous by construction — every ordinary PR looks like
|
||||
# this, and none of them can be asked to have drilled a release that does not
|
||||
# exist. It must pass with the log empty of records, which is the state
|
||||
# drill/RUNS.md ships in today.
|
||||
T="$(drilltree dev 0.2.1-dev '# Drill run log' '' 'No runs recorded yet.')"
|
||||
check "drill: a -dev tree passes with NO drill record" 0 "" drill "$T"
|
||||
# exist. It passes with no drills/ directory present AT ALL, which is the
|
||||
# state this repo ships in today.
|
||||
T="$(drilltree dev 0.2.1-dev)"
|
||||
check "drill: a -dev tree passes with NO drills dir at all" 0 "" drill "$T"
|
||||
check "drill: ...saying so out loud, not exiting 0 in silence" 0 \
|
||||
"nothing to assert" drill "$T"
|
||||
|
||||
# The release ceremony tree, drilled and recorded. GREEN.
|
||||
T="$(drilltree recorded 0.3.0 '# Drill run log' '' '## Release drill — 0.3.0 — 2026-07-21' '' \
|
||||
'Host: bare Debian 13. Guests via box 0.4.0 (released, pinned).' '' \
|
||||
'- db-integration: 14/14' '- runner lifecycle: PASS' '' \
|
||||
'## Release drill — 0.2.0 — 2026-07-01' '' 'an older run')"
|
||||
check "drill: a bare VERSION with a matching non-empty record passes" 0 "records a drill for 0.3.0" drill "$T"
|
||||
T="$(drilltree recorded 0.3.0)"
|
||||
drillrec "$T" 0.3.0 '# Release drill — 0.3.0 — 2026-07-21' '' \
|
||||
'Host: bare Debian 13. Candidate refs pinned: box@1a2b3c4, cast@9a0b1c2.' '' \
|
||||
'- convergence, then re-converge: clean' \
|
||||
'- db-integration: 14/14' '- runner lifecycle: PASS'
|
||||
drillrec "$T" 0.2.0 '# Release drill — 0.2.0 — 2026-07-01' '' 'an older run'
|
||||
check "drill: a bare VERSION with a non-empty record for it passes" 0 \
|
||||
"records a drill for 0.3.0" drill "$T"
|
||||
|
||||
# The heading with no date — the trailing ' — DATE' is optional, so a record
|
||||
# written without one is still a record.
|
||||
T="$(drilltree nodate 0.3.0 '# Drill run log' '' '## Release drill — 0.3.0' '' 'ran it, 12/12')"
|
||||
check "drill: the date suffix is optional" 0 "records a drill" drill "$T"
|
||||
|
||||
# The gate itself: a release tree with no record at all. RED, naming the
|
||||
# version, because "which release is unevidenced" is the only fact the author
|
||||
# needs.
|
||||
T="$(drilltree norecord 0.3.0 '# Drill run log' '' 'No runs recorded yet.')"
|
||||
check "drill: a bare VERSION with NO record FAILS" 1 "NO drill record" drill "$T"
|
||||
# The gate itself: a release tree with no drills/ directory at all. RED,
|
||||
# naming the version, because "which release is unevidenced" is the only fact
|
||||
# the author needs. This is the state a repo is in the first time it cuts a
|
||||
# release under the gate — it must read as a to-do, not a broken invocation.
|
||||
T="$(drilltree norecord 0.3.0)"
|
||||
check "drill: a bare VERSION with NO drills dir FAILS" 1 \
|
||||
"no drill record" drill "$T"
|
||||
check "drill: ...and the failure names the version" 1 "VERSION is 0.3.0" drill "$T"
|
||||
check "drill: ...and names the file it wanted" 1 "drills/0.3.0.md" drill "$T"
|
||||
|
||||
# A drills/ that exists but holds nothing for THIS version. Same failure —
|
||||
# other releases having been drilled says nothing about this one.
|
||||
T="$(drilltree otherversion 0.4.0)"
|
||||
drillrec "$T" 0.3.0 '# Release drill — 0.3.0' '' 'the previous release'
|
||||
check "drill: a drills dir with no file for THIS version FAILS" 1 \
|
||||
"no drill record" drill "$T"
|
||||
check "drill: ...naming the version that is unevidenced" 1 "VERSION is 0.4.0" drill "$T"
|
||||
|
||||
# ...and the failure has to say how to get out of it. Both moves are a commit
|
||||
# on the PR, and the second one is the point of asking for a RECORD rather
|
||||
|
|
@ -522,75 +544,67 @@ check "drill: ...and the failure names the unblock — run the drill" 1 \
|
|||
"RUN THE DRILL" drill "$T"
|
||||
check "drill: ...and the waiver, recorded, as the other way out" 1 \
|
||||
"MAINTAINER WAIVER" drill "$T"
|
||||
check "drill: ...and shows the exact heading the guard wants" 1 \
|
||||
"## Release drill — 0.3.0" drill "$T"
|
||||
check "drill: ...and points at the README for what a record contains" 1 \
|
||||
"README.md" drill "$T"
|
||||
|
||||
# A heading with nothing under it. This is the failure a laxer guard invites —
|
||||
# the ceremony PR adds the heading to get green and fills it in never. A
|
||||
# section is a record only if something is in it.
|
||||
T="$(drilltree empty 0.3.0 '# Drill run log' '' '## Release drill — 0.3.0 — 2026-07-21' '' '' \
|
||||
'## Release drill — 0.2.0 — 2026-07-01' '' 'an older run')"
|
||||
check "drill: a PRESENT but EMPTY record FAILS" 1 "NO drill record" drill "$T"
|
||||
# ...and it fails for being empty, not for being unfindable: the older section
|
||||
# below it must not be scavenged to satisfy the newer heading.
|
||||
# shellcheck disable=SC2016 # the $-refs are the inner bash -c's, deliberately
|
||||
check "drill: ...an empty section never borrows the next section's body" 1 "" \
|
||||
bash -c 'bash "$1" "$2/RUNS.md" "$2/VERSION" 2>&1 | grep -q "an older run"' _ "$DRILL" "$T"
|
||||
# An EMPTY file at the right path. This is the failure a laxer guard invites —
|
||||
# the ceremony PR touches the file to get green and fills it in never.
|
||||
T="$(drilltree emptyfile 0.3.0)"
|
||||
mkdir -p "$T/drills"; : > "$T/drills/0.3.0.md"
|
||||
check "drill: a PRESENT but EMPTY record FAILS" 1 "no drill record" drill "$T"
|
||||
|
||||
# ...and WHITESPACE is not a record either. This guard already gets it right —
|
||||
# `/^[[:space:]]*$/` skips a spaces-or-tabs line the same as a bare one — so
|
||||
# this pins behaviour rather than fixing it. It is here because the siblings
|
||||
# did NOT get it right: box#149 and cast#138 both extracted with
|
||||
# `sed '/./,$!d'`, where `.` matches a space, so a heading followed by one tab
|
||||
# satisfied the gate and shipped an evidence-free release. All three reviewers
|
||||
# caught it there. Nothing caught it here, because there was nothing to catch —
|
||||
# which is exactly the state in which a later "simplify this to match its
|
||||
# siblings" quietly reintroduces it. The cheapest moment to pin a property is
|
||||
# while you still remember why it matters.
|
||||
T="$(drilltree blank 0.3.0 '# Drill run log' '' '## Release drill — 0.3.0 — 2026-07-21' ' ' ' ' '' \
|
||||
'## Release drill — 0.2.0 — 2026-07-01' '' 'an older run')"
|
||||
check "drill: a record body of only spaces and tabs FAILS (box#149, cast#138)" \
|
||||
1 "NO drill record" drill "$T"
|
||||
# ...and WHITESPACE is not a record either. This is the ONE piece of the old
|
||||
# section-parsing rule set that splitting the files did not make
|
||||
# unrepresentable, so it is the one that still needs a test. It is here because
|
||||
# the siblings got it wrong: box#149 and cast#138 both extracted with
|
||||
# `sed '/./,$!d'`, where `.` matches a space, so one tab satisfied the gate and
|
||||
# shipped an evidence-free release. All three reviewers caught it there.
|
||||
# Nothing caught it here, because there was nothing to catch — which is exactly
|
||||
# the state in which a later simplification quietly reintroduces it.
|
||||
T="$(drilltree blank 0.3.0)"
|
||||
drillrec "$T" 0.3.0 ' ' ' ' ''
|
||||
check "drill: a record of only spaces, tabs and newlines FAILS (box#149, cast#138)" \
|
||||
1 "no drill record" drill "$T"
|
||||
|
||||
# The version is matched WHOLE, both directions. A drill run against a release
|
||||
# The version is matched WHOLE, both directions — and now the filesystem does
|
||||
# it, since the version IS the filename. A drill run against a release
|
||||
# candidate is not evidence for the final release, and the reverse is equally
|
||||
# false — in both cases the string that matched is not the artefact that
|
||||
# ships. changelog_section()'s exact `$2 == ver` is the precedent; this heading
|
||||
# is longer, so the comparison had to be rebuilt rather than inherited.
|
||||
T="$(drilltree whole-rc 0.3.0 '# Drill run log' '' '## Release drill — 0.3.0-rc1 — 2026-07-20' '' 'the rc drill')"
|
||||
check "drill: an -rc1 record does NOT satisfy the bare version" 1 "NO drill record" drill "$T"
|
||||
T="$(drilltree whole-final 0.3.0-rc1 '# Drill run log' '' '## Release drill — 0.3.0 — 2026-07-21' '' 'the final drill')"
|
||||
check "drill: ...and a bare-version record does NOT satisfy the -rc1" 1 "NO drill record" drill "$T"
|
||||
# A shorter version is not a prefix win either: 0.3.0 must not be answered by
|
||||
# a 0.3.0.1 heading, nor 0.3 by 0.3.0.
|
||||
T="$(drilltree whole-longer 0.3.0 '# Drill run log' '' '## Release drill — 0.3.0.1 — 2026-07-21' '' 'a different thing')"
|
||||
check "drill: ...nor does a LONGER version number match by prefix" 1 "NO drill record" drill "$T"
|
||||
# And an unrelated version is simply absent, which is the same failure.
|
||||
T="$(drilltree other 0.4.0 '# Drill run log' '' '## Release drill — 0.3.0 — 2026-07-21' '' 'the previous release')"
|
||||
check "drill: a record for a DIFFERENT version is not this version's record" 1 "VERSION is 0.4.0" drill "$T"
|
||||
|
||||
# The repo with no drill/ directory yet — the state rig was in before this
|
||||
# guard. It must read as "this release has no record", the same to-do as an
|
||||
# empty log, not as a broken invocation.
|
||||
T="$(drilltree norunsfile 0.3.0 'placeholder')"
|
||||
rm -f "$T/RUNS.md"
|
||||
check "drill: a MISSING runs file is the same failure, not a crash" 1 "NO drill record" drill "$T"
|
||||
check "drill: ...and still names the unblock" 1 "RUN THE DRILL" drill "$T"
|
||||
# false: in both cases the string that matched is not the artefact that ships.
|
||||
T="$(drilltree whole-rc 0.3.0)"
|
||||
drillrec "$T" 0.3.0-rc1 '# Release drill — 0.3.0-rc1' '' 'the rc drill'
|
||||
check "drill: an -rc1 record does NOT satisfy the bare version" 1 \
|
||||
"no drill record" drill "$T"
|
||||
T="$(drilltree whole-final 0.3.0-rc1)"
|
||||
drillrec "$T" 0.3.0 '# Release drill — 0.3.0' '' 'the final drill'
|
||||
check "drill: ...and a bare-version record does NOT satisfy the -rc1" 1 \
|
||||
"no drill record" drill "$T"
|
||||
|
||||
# A missing VERSION file is a wrong invocation, not a degradation — there is
|
||||
# no version to be lenient about.
|
||||
T="$(drilltree noversion 0.3.0 '# Drill run log')"
|
||||
# no version to be lenient about, so it must never read as a pass.
|
||||
T="$(drilltree noversion 0.3.0)"
|
||||
rm -f "$T/VERSION"
|
||||
check "drill: a missing VERSION file is an error, never a pass" 1 "no such file" drill "$T"
|
||||
|
||||
# The real files, last. The shipped log must be readable by the guard the
|
||||
# repo actually runs, on the VERSION the repo actually carries.
|
||||
check "drill/RUNS.md: exists" 0 "" test -f "$ROOT/drill/RUNS.md"
|
||||
# The real files, last. The shipped README must be readable, and the guard the
|
||||
# repo actually runs must pass on the VERSION the repo actually carries.
|
||||
check "drills/README.md: exists" 0 "" test -f "$ROOT/drills/README.md"
|
||||
# shellcheck disable=SC2016 # the $-refs are the inner bash -c's, deliberately
|
||||
check "drill/RUNS.md: documents the heading format the guard requires" 0 "" \
|
||||
bash -c 'grep -qF "## Release drill — X.Y.Z — YYYY-MM-DD" "$1"' _ "$ROOT/drill/RUNS.md"
|
||||
check "drills/README.md: documents the one-file-per-version naming rule" 0 "" \
|
||||
bash -c 'grep -qF "drills/<version>.md" "$1"' _ "$ROOT/drills/README.md"
|
||||
# shellcheck disable=SC2016 # the $-refs are the inner bash -c's, deliberately
|
||||
check "drills/README.md: says a FAILED drill is still a valid record" 0 "" \
|
||||
bash -c 'grep -qi "failed drill is still a valid record" "$1"' _ "$ROOT/drills/README.md"
|
||||
# The old single-file record must be gone, not merely unreferenced: a stale
|
||||
# drill/RUNS.md would be a second place to write a record that nothing reads.
|
||||
check "drill/RUNS.md: is gone — records live one per version now" 1 "" \
|
||||
test -e "$ROOT/drill/RUNS.md"
|
||||
check "drill-recorded.sh: passes against the real tree" 0 "" \
|
||||
bash "$DRILL" "$ROOT/drill/RUNS.md" "$ROOT/VERSION"
|
||||
bash "$DRILL" "$ROOT/drills" "$ROOT/VERSION"
|
||||
# ...and with no arguments at all, since that is how ci.yml invokes it. The
|
||||
# defaults must be the paths this repo actually uses.
|
||||
# shellcheck disable=SC2016 # the $-refs are the inner bash -c's, deliberately
|
||||
check "drill-recorded.sh: ...and with its DEFAULT arguments, as CI runs it" 0 "" \
|
||||
bash -c 'cd "$1" && bash .github/scripts/drill-recorded.sh' _ "$ROOT"
|
||||
|
||||
# ci.yml: the guard runs from there, so pin the wiring — a script nothing
|
||||
# invokes is not a check (same reasoning as the monotonic pins above).
|
||||
|
|
@ -607,22 +621,26 @@ check "ci.yml: the drill step itself is NOT trigger-gated" 1 "" drill_step_gated
|
|||
check "ci.yml: ...and the block was actually found (guards the awk above)" 0 \
|
||||
"drill-recorded" drill_step_block
|
||||
|
||||
# CONTRIBUTING must state the gate, and must state what the drill actually is:
|
||||
# ONE orchestrated run over the whole stack, on CANDIDATE refs. box and rig are
|
||||
# mutually recursive — rig builds the host box runs on, and box mints the seeds
|
||||
# rig converges — so there is no linear "drill A, release A, then drill B"
|
||||
# order to write down, and pinning RIG_REPO/RIG_REF at mint time is what makes
|
||||
# the recursion drillable at all. An earlier draft of this doc claimed a fixed
|
||||
# box → rig → cast release order; it is wrong, and this pins the correction.
|
||||
# CONTRIBUTING must state the gate, and must state what the drill actually is.
|
||||
# The three repos' drills are INDEPENDENT — run in any order, on any schedule —
|
||||
# and what makes that safe is that each one pins the same fixed set of
|
||||
# CANDIDATE refs, so box and rig measure the same pair. That, not sequencing,
|
||||
# is what dissolves the mutual recursion (rig builds the host box runs on, and
|
||||
# box mints the seeds rig converges). An earlier draft of this doc claimed a
|
||||
# fixed box → rig → cast release order; it is wrong, and this pins the
|
||||
# correction.
|
||||
CONTRIB="$ROOT/CONTRIBUTING.md"
|
||||
# shellcheck disable=SC2016 # the $-refs are the inner bash -c's, deliberately
|
||||
check "CONTRIBUTING: the release flow names the drill gate" 0 "" \
|
||||
bash -c 'grep -qF "drill/RUNS.md" "$1"' _ "$CONTRIB"
|
||||
bash -c 'grep -qF "drills/<version>.md" "$1"' _ "$CONTRIB"
|
||||
# shellcheck disable=SC2016 # the $-refs are the inner bash -c's, deliberately
|
||||
check "CONTRIBUTING: ...and says the drill is ONE orchestrated stack run" 0 "" \
|
||||
bash -c 'grep -qi "one orchestrated run" "$1"' _ "$CONTRIB"
|
||||
check "CONTRIBUTING: ...and says the three repos' drills are INDEPENDENT" 0 "" \
|
||||
bash -c 'grep -qi "drills are independent" "$1"' _ "$CONTRIB"
|
||||
# shellcheck disable=SC2016 # the $-refs are the inner bash -c's, deliberately
|
||||
check "CONTRIBUTING: ...on candidate refs, which is what dissolves the recursion" 0 "" \
|
||||
check "CONTRIBUTING: ...pinned to one fixed set of candidate refs" 0 "" \
|
||||
bash -c 'grep -qi "same fixed set of candidate refs" "$1"' _ "$CONTRIB"
|
||||
# shellcheck disable=SC2016 # the $-refs are the inner bash -c's, deliberately
|
||||
check "CONTRIBUTING: ...which is what dissolves the recursion, not sequencing" 0 "" \
|
||||
bash -c 'grep -qF "RIG_REF" "$1"' _ "$CONTRIB"
|
||||
# The negative that keeps the correction from being re-lost: no fixed release
|
||||
# order may be claimed. Nothing requires box to ship before rig.
|
||||
|
|
|
|||
Loading…
Reference in a new issue