docs: define doors-unchanged drill records

This commit is contained in:
Andriujose 2026-08-03 21:41:34 +00:00
parent ba55d1d552
commit 75d85df20c
3 changed files with 61 additions and 6 deletions

5
changelog.d/237.md Normal file
View file

@ -0,0 +1,5 @@
### Changed
- Define the doors-unchanged drill record and an executable release-path list,
so a release may reuse live evidence only when its door bytes are unchanged
since the last rehearsed tag. (#237)

View file

@ -66,6 +66,46 @@ record is the only thing that survives the drill, and 0.2.0's record shipped
its first draft asserting a cleanup that had not happened (#135) — false
evidence in the one file whose job is to be evidence.
A record has one of three shapes. A **rehearsal** records the disposable-repo
run above. **Doors unchanged** records the mechanically checked claim below
when a new rehearsal would execute the same bytes as the last one. **WAIVED**
records a maintainer's judgement under the standing paragraph below. If the
doors-unchanged conditions do not all hold, the release owes a rehearsal or a
waiver; the narrower shape is never a substitute for either.
## Doors unchanged
The builder may assert that no disposable-repo rehearsal is owed only when
all three conditions below hold at the candidate head. The release PR's panel
verifies the claim like any other evidence, and if any reviewer rules a full
drill owed, that verdict wins.
1. `git diff <last-rehearsed-tag>..HEAD -- <release-path>` contains no change
except the `CEREMONY_SELF_REF` pin line in
`.github/workflows/release.yml`.
2. The release path is exactly the output of
`.github/scripts/release-path.sh`: `.github/workflows/release.yml`, `bin/`,
`lib/version.sh`, `lib/decide.sh`, `lib/facts.sh`, and
`lib/changelog.sh`. The script is the record author's copy-paste source;
its contract test keeps this inline list and the workflow's direct and
transitive dependencies in agreement.
3. The last rehearsed tag's own record is a full rehearsal, its release is
published, and `main` was re-armed to `-dev` after it.
The baseline is the last **rehearsed** tag, never merely the previous tag. A
previous-tag baseline could chain one doors-unchanged assertion from another
while the doors drift a small diff at a time; the last-rehearsed anchor makes
any accumulated release-path change force a new rehearsal.
The record carries all three measurements as observed at its candidate head,
never copied from an earlier record. `drills/0.4.1.md` and
`drills/0.5.0.md` are the worked examples; the latter's amendment from a
predicted empty `lib/` diff to the observed `lib/ruling.sh` delta is why each
candidate is measured afresh (#233). Re-running its stricter baseline now is
also the path-enumeration proof: `git diff 0.4.0 0.5.0 -- <release-path>` is
only the `CEREMONY_SELF_REF` pin, while adding `lib/ruling.sh` makes the diff
non-empty even though neither release door reads that file (#217, #237).
`actions/drill-recorded` refuses any bare-version tree whose record is
missing or blank. A waived drill is still a record: the file says WAIVED and
why — a maintainer's call, visible and reviewable in the release PR's diff,

View file

@ -15,8 +15,9 @@ trap 'rm -rf "$TMP"' EXIT
# derive_path <tree> — print the workflow, bin/ when a bin command sources a
# door library, and the workflow's direct + transitive lib dependencies.
derive_path() {
local tree="$1" workflow="$tree/.github/workflows/release.yml"
local tree="$1" workflow
local pending seen=" " lib file refs ref bin_uses_lib=no
workflow="$tree/.github/workflows/release.yml"
printf '%s\n' .github/workflows/release.yml
pending="$(sed -n 's|.*\(lib/[[:alnum:]_.-]*\.sh\).*|\1|p' "$workflow" | sort -u)"
@ -75,10 +76,12 @@ fixture() {
tree="$TMP/$name"
mkdir -p "$tree/.github/scripts" "$tree/.github/workflows" "$tree/lib" "$tree/bin"
cp "$PATH_SCRIPT" "$tree/.github/scripts/release-path.sh"
printf '#!/usr/bin/env bash\n. "$ROOT/lib/changelog.sh"\n' >"$tree/bin/assemble"
printf '#!/usr/bin/env bash\n. "%s"\n' \
"\$ROOT/lib/changelog.sh" >"$tree/bin/assemble"
printf '#!/usr/bin/env bash\n' >"$tree/lib/changelog.sh"
printf '#!/usr/bin/env bash\n' >"$tree/lib/decide.sh"
printf '#!/usr/bin/env bash\n. "$ROOT/lib/version.sh"\n' >"$tree/lib/facts.sh"
printf '#!/usr/bin/env bash\n. "%s"\n' \
"\$ROOT/lib/version.sh" >"$tree/lib/facts.sh"
printf '#!/usr/bin/env bash\n' >"$tree/lib/version.sh"
printf '%s\n' "$tree"
}
@ -92,7 +95,10 @@ check "real workflow and transitive dependencies match the manifest" 0 "" \
# A door growing a dependency must name the missing path (#237 D7).
tree="$(fixture missing)"
printf 'run: bash "$CEREMONY_DIR/lib/facts.sh"\nrun: bash "$CEREMONY_DIR/lib/decide.sh"\nrun: . "$CEREMONY_DIR/lib/changelog.sh"\nrun: . "$CEREMONY_DIR/lib/version.sh"\nrun: . "$CEREMONY_DIR/lib/ruling.sh"\n' \
printf 'run: bash "%s"\nrun: bash "%s"\nrun: . "%s"\nrun: . "%s"\nrun: . "%s"\n' \
"\$CEREMONY_DIR/lib/facts.sh" "\$CEREMONY_DIR/lib/decide.sh" \
"\$CEREMONY_DIR/lib/changelog.sh" "\$CEREMONY_DIR/lib/version.sh" \
"\$CEREMONY_DIR/lib/ruling.sh" \
>"$tree/.github/workflows/release.yml"
printf '#!/usr/bin/env bash\n' >"$tree/lib/ruling.sh"
check "a new workflow library fails with its missing path" 1 \
@ -100,7 +106,9 @@ check "a new workflow library fails with its missing path" 1 \
# A manifest may not rot into a safe-looking superset.
tree="$(fixture extra)"
printf 'run: bash "$CEREMONY_DIR/lib/facts.sh"\nrun: bash "$CEREMONY_DIR/lib/decide.sh"\nrun: . "$CEREMONY_DIR/lib/changelog.sh"\n' \
printf 'run: bash "%s"\nrun: bash "%s"\nrun: . "%s"\n' \
"\$CEREMONY_DIR/lib/facts.sh" "\$CEREMONY_DIR/lib/decide.sh" \
"\$CEREMONY_DIR/lib/changelog.sh" \
>"$tree/.github/workflows/release.yml"
sed -i 's| lib/changelog.sh$| lib/changelog.sh \\|' \
"$tree/.github/scripts/release-path.sh"
@ -111,7 +119,9 @@ check "a path no door reads fails as stale" 1 "stale path: lib/ruling.sh" \
# Transitive sourcing is part of the derivation, not decoration.
tree="$(fixture transitive)"
printf 'run: bash "$CEREMONY_DIR/lib/facts.sh"\nrun: bash "$CEREMONY_DIR/lib/decide.sh"\nrun: . "$CEREMONY_DIR/lib/changelog.sh"\n' \
printf 'run: bash "%s"\nrun: bash "%s"\nrun: . "%s"\n' \
"\$CEREMONY_DIR/lib/facts.sh" "\$CEREMONY_DIR/lib/decide.sh" \
"\$CEREMONY_DIR/lib/changelog.sh" \
>"$tree/.github/workflows/release.yml"
: >"$tree/lib/facts.sh"
check "removing facts' version source fails as a stale path" 1 \