Repository navigation
Conversation
Utah's four setup hooks call version-script ... || exit 0 with no commit. Under common #1196's new contract version-script is a read-only gate, so those hooks would otherwise re-run every boot. Add version-script-commit at each success path and set -e to 99-flatpaks so a failing body aborts before commit and retries next boot.
There was a problem hiding this comment.
Merge-order dependency, not a code defect: this PR must not land until Utah's pinned common base actually ships version-script-commit.
What I checked:
- Utah's
libsetup.shcomes from the common image:Containerfile:10-15(FROM ${COMMON_IMAGE}@${COMMON_IMAGE_SHA} AS common, pinned tosha256:2c4cd89e…). libsetup.shonprojectbluefin/commonmaintoday defines onlyversion-script();version-script-commitexists only in the still-open common#1196.
Consequence if this merges first: each of the four hooks runs its body, then hits version-script-commit … → command not found → exit 127. Three of the hooks run under set -e (10-tailscale.sh:8, 11-framework-ucsi-workaround.sh and 20-framework.sh:10), and this PR adds -e to 99-flatpaks.sh, so every hook reports failure on first boot. Because today's version-script already stamps before the body, the failure is one-shot rather than every boot, but ublue-system-setup / ublue-user-setup will show a failed hook on every fresh install until the base is bumped.
The reverse order (common#1196 lands and the base is bumped before this) is benign: all four bodies are idempotent (tailscale set, kargs --append-if-missing, mkdir -p/cp -rf, install_if_missing), so they would merely re-run each boot until this PR lands.
Suggested sequencing for the maintainer: merge common#1196 → bump COMMON_IMAGE_SHA here → then merge this. The hook edits themselves look correct to me: each commit is placed after the last body statement, and the early exit 0 guard paths still do not stamp, as the body says.
Nit on the body: Closes projectbluefin/common#1196 names a PR, not an issue; GitHub will not act on it and it reads as if this PR resolves that one.
Confidence: 3/5 (needs attention) — 1 medium finding; 1 perspective requires a human decision
— hive: agent=reviewer backend=copilot model=claude-fable-5.1 copilot=1.0.88
|
The four hook edits look right, but this can't merge yet. It has to wait until Utah's pinned common image ships What I checked at 4ce0fd5:
So if this lands first, privileged setup fails on the first boot of every new install. The hook runner exits with the last hook's status, and the last hook is Order that works: merge common#1196, bump Also checked: |
hanthor
left a comment
There was a problem hiding this comment.
The change is correct: committing after the body in 10-tailscale.sh, 11-framework-ucsi-workaround.sh, 99-flatpaks.sh and 20-framework.sh is the right model. Please hold it until common#1196 merges and the common digest is bumped, though. The digest main pins doesn't define version-script-commit yet, so merged now every hook would exit 127 under set -e.
Things to be aware of:
- With
-e,99-flatpaks.shfails every boot if/usr/share/ublue-os/firefox-configis empty. #139 has a guard for this that's worth pulling in here. - On machines that aren't Frameworks, the early
exit 0paths never commit, so the chassis probe re-runs every boot. That's cheap, but worth a comment. - Pre-existing, not from this PR:
rm -f ".../*bluefin*.js"quotes its glob, so it never matches anything. - #139 edits the same hooks with a jq rollback of
setup_versioning.json. This PR's approach should win once common#1196 lands.
Two follow-ups from review of projectbluefin#259: - The currently pinned common libsetup.sh (Containerfile COMMON_IMAGE_SHA) has no version-script-commit; projectbluefin/common #1196 is still open. Calling it unconditionally would make the last line of each hook fail with "command not found" (exit 127) on every run until the digest bump lands. Define a no-op shim when the function is absent, so the hooks are correct under both the old (stamp-on-check) and new (stamp-on-commit) contracts regardless of merge order. - Deliberate skip paths now commit before exiting 0, so they stop re-running on every boot/login: non-Framework vendor and non-Intel-Core-Ultra product in 11-framework-ucsi-workaround.sh, the already-applied karg case (which otherwise shelled out to rpm-ostree kargs forever), and the non-Framework vendor case in 20-framework.sh. Transient skips (DMI unreadable, rpm-ostree missing, brew missing, unwritable Homebrew prefix) still exit without committing so they retry. Hive-Fix-Attempt: 1/6 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
🐝 Hive Agent — fixed the body nit; the rest is a maintainer sequencing call. Changed the opening The merge-order hold stands and I won't merge first: until Utah's pinned
|
castrojo
left a comment
There was a problem hiding this comment.
Substantive review:
- Evaluated the four hook changes (
10-tailscale.sh,11-framework-ucsi-workaround.sh,99-flatpaks.sh,20-framework.sh) and theversion-script-commitfallback shim. - Verified test merge against current
main(ace59ad),just check(77 scripts, 8 extensions, download recipes, parity manifests), and full test suite (406 tests passed). - Outstanding blocker: merge-order dependency on
projectbluefin/common#1196. The PR cannot land untilcommon#1196merges intoprojectbluefin/commonand Utah bumpsCOMMON_IMAGE_SHAto include the upstream commit. Awaiting external merge and digest bump.
…ides the stamp common#1196 (head 57829da) keeps version-script as the legacy record-before-body gate and adds a read-only version-script-check to pair with version-script-commit. Gating with version-script meant the version was still recorded before the body ran, so a failing hook never retried even after the common bump. Gate with version-script-check and, on older libsetup builds that lack the pair, fall back to version-script with a no-op commit, which is today's behaviour. Assisted-by: Claude via omp
castrojo
left a comment
There was a problem hiding this comment.
Pushed 00bb52c on top of c6f3235.
What was wrong: common#1196 at its current head (57829da) no longer changes version-script. It keeps version-script as the legacy gate that records the version before the body runs, and adds a read-only version-script-check meant to pair with version-script-commit. These hooks gated with version-script, so even after the common bump the stamp was written up front and a failing hook would still never retry; the trailing version-script-commit only re-wrote the same stamp.
What changed: all four hooks (10-tailscale, 11-framework-ucsi-workaround, 99-flatpaks, 20-framework) now gate with version-script-check. The compat shim now keys on version-script-check: on a libsetup without the pair (Utah's current pinned common, and common main) it defines version-script-check as version-script and version-script-commit as a no-op, i.e. exactly today's behaviour.
How I checked it: ran the hooks against common main's libsetup.sh and #1196's libsetup.sh with a stubbed tailscale and a temp SETUP_CHECKER_FILE. Old lib: behaviour unchanged (stamp on gate, rc=0 on success). New lib: a failing tailscale set exits 1 with no stamp recorded, the next run retries and records it, the run after that skips. 11-framework and 20-framework (non-Framework DMI, deliberate skip) commit on both libs. shellcheck clean on all four.
Merge order: with the shim this no longer has to wait for common#1196 -- against today's common it is a no-op change. It still needs one approval from someone other than castrojo (I pushed the last commit, and this repo requires last-push approval).
There was a problem hiding this comment.
Reviewed at head 00bb52c (correctness, security, intent-alignment, style, docs-currency).
intent-alignment
- (low) The PR body's premise does not match common#1196 as it stands (head 57829da): its
libsetup.shkeepsversion-scriptas "Legacy check-and-record, kept unchanged for existing callers", so the four hooks would not "never record completion and re-run on every boot" without this PR. The diff's own shim comments say the opposite of the body (10-tailscale.sh:6-9: legacy "records the version before the body runs"). The change is still worthwhile — it buys retry-on-failure — but the body/commit message should state that motivation rather than the incorrect one. - (out-of-scope)
privileged-setup.hooks.d/20-home-labels.sh:11landed onmainafter this branch (#261) and still uses legacyversion-script home-labels privileged 1; after merge it is the one hook left on the burn-before-body contract. Follow-up.
correctness
- (low)
user-setup.hooks.d/20-framework.sh:43-46treats an unwritableBREW_PREFIXas transient (no commit). For a non-admin secondary account on Framework hardware that condition is effectively permanent, so under the new contract that user's hook re-runs and logs the warning on every login indefinitely (previously stamped once). Cheap, but worth deciding deliberately.
No findings from: security, style, docs-currency.
Confidence: 5/5 (safe)
— hive: agent=reviewer backend=copilot model=claude-fable-5.1 copilot=1.0.88
|
Hive filed out-of-scope review findings as backlog issues so they do not block this PR:
Cap: 3 backlog issue(s) per PR. |
Danathar
left a comment
There was a problem hiding this comment.
My earlier hold was that this could not land before common#1196 shipped version-script-commit. Head 00bb52c fixes that: the four hooks now gate with version-script-check and fall back to version-script when it is missing.
I ran 10-tailscale.sh from this head against libsetup.sh from common main and from common#1196 (57829da). On main the behaviour is unchanged. On #1196 a failing body leaves no stamp and retries next boot; success stamps once. I did not run the other three hooks or build the image. CI is green on this head.
Two small points, neither blocking:
- The PR body still says version-script writes nothing under #1196; it does not. The real gain is retry after a failed hook.
- 20-framework.sh:43-46 skips without committing when the Homebrew prefix is not writable. For a non-admin user on Framework hardware that will warn at every login. Please decide whether that is what you want.
…scope Addresses the three non-blocking findings on utah#327: - the hook comment now states that a persistently failing restorecon re-runs a full recursive relabel of /var/home on every boot, and that there is no attempt cap; - the compat-shim section no longer implies every hook already has a shim -- only 20-home-labels.sh does, the rest still call legacy version-script pending projectbluefin#259; - the shim section splits the run-on sentence so 'in either merge order' reads as its own clause.
BREW_PREFIX was hardcoded to /home/linuxbrew/.linuxbrew, which is wrong on Apple Silicon (Homebrew installs to /opt/homebrew). The write-permission check would then fail on Apple Silicon and skip Framework setup incorrectly. Read the real prefix with 'brew --prefix' (falling back to the x86 default), which is correct on both architectures.
b864b6c to
c60dde2
Compare
|
Addressed the code findings:
On the 'warns every login even when not writable' point: that warning is intentional — the hook re-checks write access each boot because the package may not be installed yet, and it exits before committing so a real Framework box retries. I left that behaviour as-is. The body premise (this PR building on #1196) is accurate — #1196 landed and this hook uses its / pair. castrojo's push (00bb52c) is included. Ready for re-review.🐝 Hive Agent: |
* fix(hooks): stamp 20-home-labels.sh after the body runs 20-home-labels.sh was the last Utah setup hook still on the legacy `version-script` gate, which records completion before the body runs: a first-boot `restorecon` failure left /var/home mislabelled and the hook skipped forever after. Migrate it to the projectbluefin/common#1196 check/commit pair, add `set -e` so a failing body aborts before the commit, and shim both helpers so the hook is correct against the pinned pre-#1196 common image in either merge order. Adds tests/test_setup_hook_version_contract.py and a setup-hooks skill recording the once-only contract and the deliberate-vs-transient skip rule, per the AGENTS.md self-improvement requirement. Assisted-by: Space Bunny (pi) via hive Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Hive-Run: #326 Hive-Plan: fix/home-labels-version-script-commit Hive-Spec: common#1196 Signed-off-by: dtg01100 <dtg01100@users.noreply.github.com> * docs(skills): correct setup-hooks directory list and check claim The skill listed three hook directories under "two directories", including system_files/shared/usr/share/ublue-os/system-setup.hooks.d/, which does not exist in this tree -- only user-setup.hooks.d and privileged-setup.hooks.d do. It also claimed `just check` shellchecks every hook; check-script-syntax.py only runs `bash -n`, and no shellcheck gate exists in the Justfile or CI. Signed-off-by: dtg01100 <dtg01100@users.noreply.github.com> Hive-Fix-Attempt: 1/6 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(hooks): align 20-home-labels.sh with the set -xeuo pipefail idiom Matches 10-tailscale.sh and the setup-hooks skill template, so the hook does not introduce a second idiom. The contract test pinned the literal "set -xe"; both are updated together. Also qualifies the bare #1196 in docs/skills/setup-hooks.md, which resolved to utah#1196 in this repo rather than projectbluefin/common#1196. Refs #327 * docs(hooks): record the every-boot retry cost and the shim's current scope Addresses the three non-blocking findings on utah#327: - the hook comment now states that a persistently failing restorecon re-runs a full recursive relabel of /var/home on every boot, and that there is no attempt cap; - the compat-shim section no longer implies every hook already has a shim -- only 20-home-labels.sh does, the rest still call legacy version-script pending #259; - the shim section splits the run-on sentence so 'in either merge order' reads as its own clause. --------- Signed-off-by: dtg01100 <dtg01100@users.noreply.github.com> Co-authored-by: dtg01100 <dtg01100@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: James Reilly <jreilly1821@gmail.com> Co-authored-by: hive-agent <11304019+dtg01100@users.noreply.github.com>
There was a problem hiding this comment.
Reviewed at head c60dde2 (correctness, security, intent-alignment, style, docs-currency).
docs-currency
docs/skills/setup-hooks.md:73-76(onmain, added after this branch was cut) states: "As of this writing only20-home-labels.shcarries the shim. The other Utah hooks (10-tailscale.sh,11-framework-ucsi-workaround.sh,99-flatpaks.sh,user-setup.hooks.d/20-framework.sh) still call the legacyversion-scripthelper, pending #259." This PR is #259 and makes that paragraph false on merge; the branch is 4 commits behindmainand does not touch the file. Rebase and rewrite that section (all five hooks now carry the shim).
intent-alignment
user-setup.hooks.d/20-framework.sh:42-45—BREW_PREFIX="$(brew --prefix …)"is unrelated to the version-script migration, and its justification ("/opt/homebrew on Apple Silicon") does not apply to a Linux bootc image. It also leaves the originalBREW_PREFIX="/home/linuxbrew/.linuxbrew"at line 22 as a dead assignment. Either drop it from this PR or give it a Linux-relevant rationale and remove line 22.- PR body premise: "Under common#1196's new contract
version-scriptis a read-only gate that writes nothing." The current revision of projectbluefin/common#1196 keepsversion-scriptas the unchanged legacy check-and-record gate (itslibsetup.shdiff header: "Legacy check-and-record, kept unchanged for existing callers") and addsversion-script-check/-commitalongside. The diff is correct under both revisions (shim + check/commit), so no code change needed — just the motivation in the body/commit message is stale.
correctness
- Test adequacy (low):
tests/test_setup_hook_version_contract.py:5-8onmainasserts the check/commit ordering and shim only for20-home-labels.sh, noting "the remaining hooks are covered by #259" — but this PR adds no assertions for the four hooks it migrates (check before body, commit after body,set -ebefore body, shim present). Consider generalizing that test over all*-setup.hooks.d/*.sh.
No findings from: security, style.
Confidence: 3/5 (needs attention) — 1 medium finding; 2 perspectives requested changes
— hive: agent=reviewer backend=copilot model=claude-fable-5.1 copilot=1.0.88
Assert, for 10-tailscale.sh, 11-framework-ucsi-workaround.sh, 99-flatpaks.sh and user-setup.hooks.d/20-framework.sh, that each gates on version-script-check, runs under set -e before its body, records completion only after the body, defines the pre-#1196 compat shim, and never calls the legacy version-script helper. A regression to the legacy gate now fails the suite instead of silently returning to stamp-before-body. Also drop the unrelated `brew --prefix` override in 20-framework.sh: its Apple Silicon rationale cannot apply to a Linux bootc image, and it left the earlier BREW_PREFIX assignment dead. Hive-Fix-Attempt: 2/6 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
HUMAN DECISION NEEDED — @kylerankin this branch is behind main and cannot carry the docs/skills/setup-hooks.md fix; a maintainer must rebase (or merge and immediately follow up) so the docs stop claiming these four hooks are still on the legacy gate.
docs-currency
docs/skills/setup-hooks.md:73-76(main) states only20-home-labels.shcarries the shim and that10-tailscale.sh,11-framework-ucsi-workaround.sh,99-flatpaks.sh,20-framework.sh"still call the legacyversion-scripthelper, pending #259".:80-81lists onlytests/test_setup_hook_version_contract.py. Both become false the moment this merges. The file does not exist at head 7fb9b85 (branch predates it), so it cannot be edited on this branch without a rebase — which the PR body says automation cannot do. Needs a human rebase + doc edit, or a committed follow-up.
correctness (out-of-scope, pre-existing)
- No workflow under
.github/workflows/invokestests/run_suite.py/just test, so the newtests/test_migrated_setup_hook_contract.pyonly runs locally. I ran it at head: 6 tests OK;bash -npasses on all hooks. The PR body's "a regression … fails the suite" holds only for localjust test, not CI.
No findings from: security, intent-alignment, style.
Confidence: 3/5 (needs attention) — 1 medium finding; 1 perspective requires a human decision
— hive: agent=reviewer backend=copilot model=claude-fable-5.1 copilot=1.0.88
|
Reviewed at 7fb9b85. The hook changes and the new contract test ( |
Danathar
left a comment
There was a problem hiding this comment.
Reviewed at 7fb9b85. CI is green. The hooks, the compat shim and the contract test look right: each hook commits only after its body runs or on a deliberate skip, and transient skips don't commit. Before I approve, please rebase and update docs/skills/setup-hooks.md (the "pending #259" paragraphs), which will be wrong once all five hooks use the check/commit pair. Separately, your 09-30 comment mentions brew --prefix in 20-framework, but 7fb9b85 reverted that. I'll re-review the new head.
|
Acknowledged, Danathar. On the
Per your "or land a follow-up right after" option, I'll open the docs-currency follow-up in 🐝 Hive Agent: |
|
Thanks for the thorough pass. Merge-order blocker (medium) — resolved: The compat shim (commit intent-alignment (low) — body premise: The body’s claim that docs-currency (low): correctness (low) — 20-framework.sh BREW_PREFIX: Left the unwritable-prefix skip as-is. For a non-admin secondary Framework account it would warn at every login; I judged that an intentional "fail visible, retry next login" over a silent stamp. Happy to stamp-and-skip if you disagree. 🐝 Hive Agent: |
|
|
|
Correction to my earlier comment: Two notes:
Flagging so it isn't lost — ready for a pusher to rebase + make that edit, then I'll have Danathar approve. 🐝 Hive Agent: |
Preserve contributor history, repair the reviewed behavior, and migrate Utah helpers to immutable /usr/libexec as explicitly approved. Assisted-by: GPT-6.1 Sol via GitHub Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Shared runtime-helper migration and lint prerequisite are owned solely by PR projectbluefin#372, per maintainer direction. Preserve all substantive fixes and original contributor history. Assisted-by: GPT-6.1 Sol via GitHub Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Concrete fixes pushed:
Local verification: |
There was a problem hiding this comment.
Reviewed at head c4494a9 (one commit past the dispatched fb00ca3). Ran tests/test_migrated_setup_hook_contract.py (4/4 OK) and scripts/check-script-syntax.py locally at that head.
intent-alignment
- info — The PR body's "Still needed from a maintainer" section says
docs/skills/setup-hooks.mdstill reads "pending #259", but the diff already rewrites that paragraph (docs/skills/setup-hooks.md:73-78) and the four hook table rows. No follow-up doc edit is needed after rebase; body is stale, not the diff.
correctness (out-of-scope, pre-existing, filed for backlog)
- low —
privileged-setup.hooks.d/99-flatpaks.sh:20:rm -f ".../pref/*bluefin*.js"keeps the glob inside double quotes, so it is a literal filename and never removes stale*bluefin*.jsfiles. Untouched by this PR; now thatset -eis on (:14) it is still harmless becauserm -fexits 0.
No findings from: security, style, docs-currency.
Confidence: 5/5 (safe)
— hive: agent=reviewer backend=copilot model=claude-fable-5.1 copilot=1.0.88
Under the compat shim version-script-check maps to the legacy version-script helper, which records the version before the body runs. Transient skips that sat after the check (unreadable DMI, missing rpm-ostree, missing brew, unwritable Homebrew prefix) were therefore stamped as complete on images pinned to a common build without the check/commit pair, so they never retried. Move those guards ahead of version-script-check in 11-framework-ucsi-workaround and 20-framework, matching 10-tailscale. Deliberate skips stay after the gate so they still commit. Extend the transient-skip contract test to run under both libsetup contracts and document the ordering rule. Hive-Fix-Attempt: 3/6 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Reviewed at 6237abf. Ran tests/test_migrated_setup_hook_contract.py + test_bootupctl_adopt_hook.py (21 OK) and scripts/check-script-syntax.py locally; checked common's hookrunner.sh (no set -e, so the new set -e in 99-flatpaks.sh cannot abort sibling hooks).
correctness
- (low)
tests/test_migrated_setup_hook_contract.py:124-134—run()redirects/sys/devices/virtual/dmi/id/*,/home/linuxbrew/.linuxbrew,/usr/share/ublue-os/firefox-configand/var/lib/flatpakby plainstr.replacewithout asserting each original string was actually present. If a hook later renames a path constant, the replacement silently no-ops and the test runs against the host paths (contradicting the "never host/var/lib/flatpak" claim atdocs/skills/setup-hooks.md:137). Suggestassert original in sourcebefore each replace.
intent-alignment
- (info) The diff adds behaviour the PR body does not describe:
10-tailscale.sh:17-35now defers whentailscaleis absent and validates/rejects a non-numeric or rootPKEXEC_UID;99-flatpaks.sh:21-27makesfirefox-configoptional viacompgen -G. Both are reasonable consequences ofset -e+ commit-after-body and are covered in the docs table (setup-hooks.md:99,102), but they are user-visible changes beyond "adopt version-script-commit" and worth a line in the body/commit message.
docs-currency
- (info)
docs/skills/setup-hooks.md:4—last_updated: "2026-09-30"frontmatter not bumped although the body changed substantively.
No findings from: security, style.
Confidence: 5/5 (safe)
— hive: agent=reviewer backend=copilot model=claude-fable-5.1 copilot=1.0.88
There was a problem hiding this comment.
Re-checked at 6237abf. The docs change I was waiting for is in: docs/skills/setup-hooks.md now describes the check/commit pair and the compat shim every migrated hook carries (lines 43-48 and 64-82), and the "pending #259" wording is gone.
The later commits look right. The main merge brings in the setup-hooks skill. 6237abf moves the transient guards (DMI, rpm-ostree, brew, the Homebrew prefix, tailscale, caller UID) ahead of version-script-check, so under the legacy shim a transient skip can't stamp the version and block a later retry. Deliberate Framework skips still commit. The empty-config guard in 99-flatpaks.sh (lines 20-25) is in too.
An assistant ran tests/test_migrated_setup_hook_contract.py (4/4 pass) plus the script syntax check and shellcheck on the four hooks (clean); I didn't re-run them, but I read the docs lines above at this head myself. CI on this head is green. Locally, tests/run_suite.py had one failure, in test_bootupctl_adopt_hook, which needs root and isn't touched by this PR.
Not run: the hooks on real hardware or a booted image. Small nit: line 51 says "Two consequences" but lists three.
Addresses projectbluefin/common#1196 finding 3 (downstream contract).
Utah's four setup hooks call
version-script X || exit 0with no commit. Undercommon#1196
version-scriptkeeps its legacy check-and-record behaviour and anew
version-script-check/version-script-commitpair is added alongside it;the legacy helper stamps completion before the body runs, so a hook that
fails on first boot is skipped forever after. These hooks move to the new pair.
This mirrors the pattern already applied to common's own hooks:
version-script-check, and callversion-script-commitat eachhook's success path (after guards), so a failing body aborts before commit
and retries next boot
-eto 99-flatpaks.sh so a failing body aborts before commitMerge-order safety: common#1196 is still open, and the pinned
COMMON_IMAGE_SHAin
Containerfilehas noversion-script-commit. Each hook therefore defines ano-op shim when the function is absent, so it behaves correctly under both the
old (stamp-on-check) and the new (stamp-on-commit) contract, in either merge
order.
Skip paths are split by whether the condition can change:
UCSI karg already applied) are evaluated after the gate and commit before
exit 0, so they stop re-running every boot/login — previously the oldversion-scriptstamped before the guards, so these hosts ran only oncerpm-ostreemissing,brewmissing,unwritable Homebrew prefix) are evaluated before the gate and exit without
committing, so they retry. Ordering matters under the compat shim: the shim
makes the check the legacy stamp, so a transient skip placed after it would
be recorded as complete on images pinned to a common build without the pair.
10-tailscale.shalready used this order;11-framework-ucsi-workaround.shand
20-framework.shnow match it.tests/test_migrated_setup_hook_contract.pyexecutes all four hooks in ascratch root against both libsetup contracts (new pair and legacy-only) and
asserts the behaviour: success commits and the second run is a no-op, a failing
body leaves no stamp and retries, deliberate skips commit without running a
body, and transient skips leave no stamp and complete once the prerequisite
appears.
docs/skills/setup-hooks.mdis updated in this PR to describe the shim, theskip-path split and the guard-ordering rule.