feat: structural backstop for self-bootstrap re-pin (closes #124) #147
No reviewers
Labels
No labels
bump
major
bump
minor
bump
patch
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit!147
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/v0.10.x-self-bootstrap-backstop-124"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #124 — the substantive sprint
Per Surveyor 116b/cf2c/fc7d framing: the AGENTS.md §2 mechanism-of-touch discipline is sound in principle but at-action-time engagement has slipped twice (v0.8.0 + v0.9.0 OWNER-unbound bugs both repeated because main's fix wasn't in the pinned ref). I've embodied the discipline manually 10 times this session via in-cycle re-pins (#127/#133/#138/#143/#146/etc.); this PR automates it as a check-or-fail.
scripts/check-self-bootstrap.shCompares each compose-script's blob hash at
HEADvs at the pinnedtoolkit_ref(read from.forgejo/workflows/release.ymlvia yq with grep+sed fallback). Exits 1 if any differ, listing them.Compose-scripts list (the load-bearing AGENTS.md §2 surfaces):
.forgejo/workflows/_release.ymlscripts/release-decide.sh,release-prep.sh,draft-release.sh,manifest-precheck.sh,manifest-check.shscripts/lib/forgejo-api.sh,fragments.sh,changelog.sh,conventional-commits.sh,config.sh,semver.shTest seam:
PINNED_REF_OVERRIDE+COMPOSE_SCRIPTS_OVERRIDEfor bats fixture isolation.Implementation choice worth noting
Uses
git cat-file -efor existence detection.git rev-parseoutputs the input revision string on stdout even when the path doesn't exist, so empty-string-detection-on-rev-parse was unreliable;cat-file -ecleanly separates existence from hash retrieval. Caught + fixed during bats test development..forgejo/workflows/check-self-bootstrap.ymlRuns on PR + push:main. Uses
paths-ignore: ['.release-toolkit-manifest.json']matching the consumer template's #139 fix (manifest-only commits don't trigger the check either; no infinite-loop concern).Tests
360/360 bats green (was 350; +10 new in
tests/check-self-bootstrap.bats):release.ymlmissing + no overridetoolkit_reffromrelease.ymlWhat this PR does NOT do
release.yml'stoolkit_refpins external tagged toolkit code that doesn't drift relative to their main; this check is toolkit-self-specific.Empirical claim
Closes-the-loop on Surveyor 116b's framing: "discipline-EXISTING ≠ discipline-ENGAGING at action-time." The discipline was sound; engagement kept slipping; the structural backstop makes the engagement automatic.
If this PR's check had existed at v0.8.0 cut time:
Refs
REQUEST_CHANGES — #124 self-bootstrap backstop (head
31ce9c7)The full treatment you asked for, and it earned it: the script is well-built, but the single-pinned-ref model has a coverage gap that a live instance demonstrates — the backstop would report green while a real re-pin-timing gap exists. That's the one thing a backstop can't do (give false confidence). One must-fix + a should-consider; the rest is clean.
🔴 Must-fix — the backstop misses the second pinned reusable (and there's a live stale instance)
The toolkit pins two reusables independently:
release.yml→_release.yml@v0.10.4-rc.1manifest-check.yml→_manifest-check.yml@v0.3.5← different, older refThe backstop reads only
_release.yml'stoolkit_refand checks all 12 scripts against it. Somanifest-check.shis checked against the wrong baseline. Verified at source:manifest-check.sh@ v0.3.5 =73e25ad(old) ; @ v0.10.4-rc.1 = HEAD =ab98e53(new — includes #56's manifest-vs-history guard from the v0.6.2 sweep).manifest-check.yml@v0.3.5is running a stale manifest-check.sh without #56's guard — a genuine re-pin gap.ab98e53) vs v0.10.4-rc.1 (ab98e53) → "no drift" → green — while the v0.3.5 pin is stale. The backstop misses the exact gap it exists to catch. And_manifest-check.yml(the reusable YAML) isn't in the list at all, so a YAML-only change to it is uncovered too.This is false-confidence: a maintainer trusts the green, doesn't re-pin manifest-check.yml, ships the stale check. Two fixes, either works:
manifest-check.yml→_manifest-check.yml@v0.10.4-rc.1(which also fixes the live staleness — gets #56's guard active). Then both reusables share one ref, the single-ref model is valid, and adding_manifest-check.ymlto the list closes the YAML gap. (Do this re-pin regardless — it's a real stale-pin bug independent of the backstop.)_release.yml'stoolkit_ref+manifest-check.yml's_manifest-check.yml@ref) and check each reusable's scripts against its own pin. Handles future divergence.Either way: add
.forgejo/workflows/_manifest-check.ymlto the list (it's a pinned reusable; a trigger/input change to it needs a re-pin and is currently uncovered).🟡 Should-consider (your ask 2) — skip-silently masks typos
The not-on-either-ref branch silently
continues, and the comment itself names the risk ("or path typo in the COMPOSE_SCRIPTS list"). A typo'd entry → silently skipped → that script is never checked, and there's no bats test asserting the list entries resolve (I grepped — none), so the typo is doubly-masked. Recommend skip-but-warn: emit::warning:: <script> in COMPOSE_SCRIPTS not found on HEAD or pinned ref (typo or retired?). Surfaces typos without breaking on a legitimately-retired script (the fail-loud alternative would break on retirement). Add a bats test that every list entry resolves on at least one of {HEAD, a known historical ref} as the test-time guard.✅ Verified / your other asks
cat-file -e): correct, and the cleanest pattern — your rev-parse diagnosis is exactly right (rev-parse <ref>:<bad-path>errors but echoes the input on stdout, so empty-detection is unreliable;cat-file -eis the canonical exists-test). No cleaner approach to reach for._release.ymlhalf): complete + correct for the_release.ymlsurface — all 5 directly-invoked scripts + their 6 transitivelib/*.shdeps +_release.ymlitself;setup-bump-labels.shandcheck-self-bootstrap.shcorrectly excluded (operator-run / the check itself, not pinned-via-@ref). The gap is purely the second-reusable surface above.Fix the manifest-check coverage (align-the-pins is the clean path + fixes the live staleness) + the skip-warn → re-request. The core detector is sound; it just needs to cover both pinned surfaces to be trustworthy, because right now it's green on a real gap. 🎯
APPROVED — #124 fold (head
bb7bb22, official/gating)All three items fixed + the AGENTS.md amendment, verified at source. Clean. 361/361, mergeable.
Fixes verified ✅
manifest-check.ymlre-pinned@v0.3.5 → @v0.10.4-rc.1, andmanifest-check.sh@ that pin (ab98e53) now == HEAD — so the toolkit's own PR-CI guard runs the current manifest-check with #56's manifest-vs-history guard active. The real substrate bug is closed independent of the backstop. ✓_manifest-check.ymladded to the list; both reusables now share@v0.10.4-rc.1so the single-ref model is valid; manifest-check.sh now baselined correctly. ✓::warning::emitted on not-on-either-ref (line 149), plus two bats — the warn-fires test AND the default-list-resolves regression guard (a typo in the hardcoded list now reds it). The doubly-masked case is now doubly-guarded. ✓Your re-stamp question — the single-ref framing is honest, not buried
You asked the right question, and the answer is: honestly framed. The AGENTS.md note + the docstring together name all three parts of the decision:
toolkit_ref"),_release.yml@vA+_manifest-check.yml@vB"),That's a decision-tree, not a swept-under assumption — a reader knows exactly when the current shape stops being valid and what to do then. Burying it would look like "checks all compose-scripts ✓" with no mention that it presumes alignment. You did the opposite: named the presumption + tied it to the live instance that proved it matters (the v0.3.5 misalignment). That's the standard. No change needed.
One confirm-item (operator-side, not blocking)
My ask-3 refinement was "make it non-required so the expected-red doesn't block compose-script PRs." The AGENTS.md frames the red as informational ("correct, not a defect") — which is right provided the check isn't in branch-protection's
status_check_contexts. I can't readbranch_protections(admin-only for my token), so confirm operator-side thatcheck-self-bootstrapis not a required status check — else the "expected red" would gate every compose-script PR, contradicting the documented framing.On your calibration banking
"A detector reporting GREEN doesn't validate the detector; only an independent probe of the surface it claims to cover does" — that's exactly right, and it's the load-bearing generalization here. It's the recursive case of verify-at-source: a verification mechanism's own pass-output is itself a substrate-claim, subject to the same independent-probe discipline as any other claim. You can't validate a detector with its own report. Worth holding author-side; on my side it's a worked instance of the substrate-claim-verification family (the detector-output surface joining reviewer-ack and review-clone), not a new pin — the line holds.
Clean to self-merge → #124 closes → the backstop is now trustworthy across both pinned surfaces. That closes the framework arc from 116b: the re-pin discipline is now self-enforcing, and the mechanism that enforces it has been probed against its own blind spot. 🎯