From eeade51f5a19069ed3413a22d00af5f95b08a835 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 23 Sep 2026 10:02:46 +0000 Subject: [PATCH] Pin the installer to the 0.3.0 runtime `install-ladder.sh` and `check-install.sh` pinned 760977f: the build from before execution binding, the bound CI chain and the committed adoption line, still reporting `evidence-v3.1`. A scripted install delivered none of the hardening on main, and could not read a consumer's `evidence = ...` policy line at all. Both now pin c86bf47, the merge that released 0.3.0 / `evidence-v3.2`. It is the earliest commit the pin could name: the installer refuses a pin that is not already an ancestor of origin/main, so the release and the pin move could not share a PR. - PIN in both scripts, and the `PIN` constant in the installer tests. - CHECKER_SHA recomputed, because the checker changed; the suite already holds it equal to the checker's digest. - INV_SHA revalidated and unchanged: the INSTALL.md invariant block hashes to 282f2117... at both 760977f and c86bf47. - "The pin predates scripts/" is no longer true. Detaching to c86bf47 replaces the pair inside the checkout with that revision's own, older pair, reviewed with an older pin. The installer's comments, its missing- checker refusal, its closing message and INSTALL.md now say so, and say to run the bootstrap copies. Run from inside the checkout, the older checker reports DOES NOT CONFORM rather than passing. - CHANGELOG's 0.3.0 entry records the pin move instead of its absence. Regression: `test_the_pin_installs_the_hardened_release` reads verify.py at the pin and requires `evidence-v3.2`, `0.3.0` and the adoption-line reader, and requires both scripts to pin the same runtime. It fails against the old pin. Values are hardcoded rather than compared with the working tree, since a future contract bump must land one PR before the pin can follow it. Checked end to end: the scripted install into a clean HOME, against the real GitHub origin, detaches at c86bf47, verifies the invariant, and the bootstrap checker reports INSTALL CONFORMS; the installed verify.py reports evidence-v3.2 / 0.3.0. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016m6di8xFVdwwq949mJx9U1 --- CHANGELOG.md | 10 +++++----- INSTALL.md | 13 ++++++++----- scripts/check-install.sh | 5 +++-- scripts/install-ladder.sh | 24 +++++++++++++----------- tests/test_install_scripts.py | 22 ++++++++++++++++++++-- 5 files changed, 49 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6bc7d7..232dc78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,11 +29,11 @@ What changed what admissible means: rather than read as evidence/2. Qualification is scoped to the predicates the consumer's own policy puts in play. -Installation (#2): `scripts/install-ladder.sh` and `scripts/check-install.sh` -install and check a pinned checkout. **Their pin still names `760977f`**, which -predates everything above and still reports `evidence-v3.1`. Moving the pin to -this release is a separate change, because the installer refuses a pin that is -not already on `main`. +Installation (#2, #7): `scripts/install-ladder.sh` and `scripts/check-install.sh` +install and check a pinned checkout. #7 moves their pin from `760977f`, which +predates everything above and reports `evidence-v3.1`, to `c86bf47`, the commit +that released this contract. It had to be a separate change because the +installer refuses a pin that is not already on `main`. ## 0.2.0 — contract `evidence-v3.1` diff --git a/INSTALL.md b/INSTALL.md index 5e026e9..89af96d 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -200,10 +200,12 @@ rather than reporting a success it did not establish. ### Where the bootstrap tooling lives -Neither script is in the pinned checkout. The pin predates `scripts/`, so -detaching `~/src/verification-ladder` to it *removes both scripts from that -directory* - including `install-ladder.sh` while it is running. The installer -therefore copies the pair out first, to +The pinned checkout is not where these scripts run from. Detaching +`~/src/verification-ladder` to the pin *replaces both scripts in that directory* +with the pin's own copy, including `install-ladder.sh` while it is running. That +copy is an older pair, reviewed with an older pin, so running it would check +the install against the wrong runtime. The installer therefore copies the pair +it was reviewed with out first, to ``` ${XDG_DATA_HOME:-~/.local/share}/verification-ladder/ @@ -212,7 +214,8 @@ ${XDG_DATA_HOME:-~/.local/share}/verification-ladder/ PROVENANCE ``` -and names that path in its closing message. Run the checker from there: +and names that path in its closing message. Run the checker from there, never +from `~/src/verification-ladder/scripts/`: ```sh ~/.local/share/verification-ladder/check-install.sh # --codex to check Codex too diff --git a/scripts/check-install.sh b/scripts/check-install.sh index 7913af0..77ebc18 100755 --- a/scripts/check-install.sh +++ b/scripts/check-install.sh @@ -8,7 +8,7 @@ # repository with no verification policy. set -u -PIN=760977f69d6b74b9888be4c9cbb404f7726bea73 +PIN=c86bf47e70d7a3aa18c968011b487190e2becbea INV_SHA=282f21173aad9815a22a6ece35ad0fdd42653dd7a001dc6d85e6d51189aa5699 CLONE=$HOME/src/verification-ladder LINK=$HOME/.claude/skills/verification-ladder @@ -30,7 +30,8 @@ fail() { echo "FAIL $1"; rc=1; } ok() { echo "ok $1"; } note() { echo "note $1"; } -# These scripts live outside the pinned checkout, because $PIN predates them. +# These scripts live outside the pinned checkout: the scripts/ inside it are +# $PIN's own, older pair, reviewed with an older pin, and are not this checker. # Report where this copy came from: once detached from its source commit it has # no other identity. Informational - running from a branch checkout is valid. BOOTSTRAP_DIR=${XDG_DATA_HOME:-$HOME/.local/share}/verification-ladder diff --git a/scripts/install-ladder.sh b/scripts/install-ladder.sh index 0d49a20..4e4f684 100755 --- a/scripts/install-ladder.sh +++ b/scripts/install-ladder.sh @@ -10,7 +10,7 @@ # to the checker it was reviewed with, for the same reason. set -u -PIN=760977f69d6b74b9888be4c9cbb404f7726bea73 +PIN=c86bf47e70d7a3aa18c968011b487190e2becbea REPO=https://github.com/Deep-Sixed/Verification-Ladder # sha256 of the invariant block in INSTALL.md at $PIN. Moving the pin without # revalidating this makes the installer fail closed rather than paste unknown @@ -18,9 +18,10 @@ REPO=https://github.com/Deep-Sixed/Verification-Ladder INV_SHA=282f21173aad9815a22a6ece35ad0fdd42653dd7a001dc6d85e6d51189aa5699 CLONE=$HOME/src/verification-ladder CODEX_HOME=${CODEX_HOME:-$HOME/.codex} -# Bootstrap tooling lives OUTSIDE the pinned checkout. $PIN predates these -# scripts, so detaching to it removes them from $CLONE - including this file -# while it is running. They are copied here first so the checker survives. +# Bootstrap tooling lives OUTSIDE the pinned checkout. Detaching $CLONE to $PIN +# replaces this pair with $PIN's own copy - an older pair, reviewed with an +# older pin - including this file while it is running. They are copied here +# first so that the pair reviewed with THIS pin is the one that survives. BOOTSTRAP_DIR=${XDG_DATA_HOME:-$HOME/.local/share}/verification-ladder # sha256 of the check-install.sh this installer was reviewed with. The two are # reviewed as a pair and preserved as a pair, so requiring the checker merely to @@ -29,7 +30,7 @@ BOOTSTRAP_DIR=${XDG_DATA_HOME:-$HOME/.local/share}/verification-ladder # afterwards. Changing check-install.sh means changing this constant in the same # commit; a mismatch is refused rather than reported, because the checker is the # only thing that would have caught it. -CHECKER_SHA=09f65bcc7f81c91fea7ea1694b0f5a0d524974e35b3df2b2d334ef01c8ae0f1b +CHECKER_SHA=d7b922510547b8f9d856a897bd493a4a5db3785f2d2fd15fc259e542dcf6ce07 SELF=$(readlink -f "$0" 2>/dev/null || echo "$0") SELF_DIR=$(dirname "$SELF") DO_CODEX=0; DRY=0; FORCE=0 @@ -176,11 +177,12 @@ done # ---- step 1b: preserve bootstrap tooling outside the pinned checkout --- step "1b. Preserving bootstrap tooling in $BOOTSTRAP_DIR" preserve_bootstrap() { - # Fail closed: $PIN does not contain check-install.sh, so if it is not beside - # the installer now it cannot be recovered later, and the closing message - # would name a checker that was never preserved. + # Fail closed: the only checker reviewed with this installer is the one beside + # it. $PIN's own copy is an older checker, reviewed with an older pin, so if + # this one is not here now it cannot be recovered later, and the closing + # message would name a checker that was never preserved. [ -f "$SELF_DIR/check-install.sh" ] \ - || die "check-install.sh is not beside $SELF; refusing because $PIN does not contain it" + || die "check-install.sh is not beside $SELF; refusing because $PIN carries only an older checker" # Identity, not existence. Checked before the early return below, so a re-run # from $BOOTSTRAP_DIR re-establishes the pair rather than trusting what an # earlier run left there. @@ -343,5 +345,5 @@ echo "Steps 1-5 done. Next:" [ "$DO_CODEX" -eq 1 ] && CODEX_ARG=" --codex" || CODEX_ARG="" echo " $BOOTSTRAP_DIR/check-install.sh$CODEX_ARG" echo "then the discovery test, then the agent-level policy-less BLOCKED test." -echo "(\$CLONE is pinned at $PIN, which predates these scripts, so they live" -echo " in $BOOTSTRAP_DIR rather than inside the checkout.)" +echo "(Run the pair in $BOOTSTRAP_DIR. \$CLONE is pinned at $PIN, and the" +echo " scripts/ inside it are that revision's older pair, reviewed with an older pin.)" diff --git a/tests/test_install_scripts.py b/tests/test_install_scripts.py index 6414c88..6a11d87 100644 --- a/tests/test_install_scripts.py +++ b/tests/test_install_scripts.py @@ -36,7 +36,7 @@ REPO = Path(__file__).resolve().parents[1] INSTALLER = REPO / "scripts" / "install-ladder.sh" CHECKER = REPO / "scripts" / "check-install.sh" -PIN = "760977f69d6b74b9888be4c9cbb404f7726bea73" +PIN = "c86bf47e70d7a3aa18c968011b487190e2becbea" ORIGIN = "https://github.com/Deep-Sixed/Verification-Ladder" @@ -318,7 +318,7 @@ def test_installer_rejects_unknown_arguments(tmp_path): def test_installer_refuses_missing_sibling(tmp_path): - """$PIN does not contain check-install.sh, so it cannot be recovered later.""" + """$PIN carries only an older checker, so this one cannot be recovered later.""" solo = tmp_path / "bin" solo.mkdir() shutil.copy(INSTALLER, solo / "install-ladder.sh") @@ -512,6 +512,24 @@ def test_digest_read_failure_is_not_a_mismatch(tmp_path, stub, why): assert not (tmp_path / ".local" / "share" / "verification-ladder").exists() +def test_the_pin_installs_the_hardened_release(): + """The installer must deliver the verifier the rest of this repository describes. + + It pinned 760977f: the build from before execution binding, the CI chain and + the committed adoption line, reporting `evidence-v3.1`. A scripted install + therefore got none of that hardening, under the same contract identity as a + build that had it. Hardcoded rather than compared with the working tree, + because a future contract bump lands one PR before the pin can follow it. + """ + assert PIN != "760977f69d6b74b9888be4c9cbb404f7726bea73" + runtime = _git("show", f"{PIN}:skills/verification-ladder/bin/verify.py", cwd=REPO).stdout + assert 'COMPATIBILITY = "evidence-v3.2"' in runtime + assert 'VERSION = "0.3.0"' in runtime + assert 'EVIDENCE_CONTRACT = "evidence"' in runtime, "the pinned runtime cannot read the adoption line" + for script in (INSTALLER, CHECKER): + assert f"PIN={PIN}" in script.read_text(), f"{script.name} pins a different runtime" + + def test_network_surface_has_not_moved(): """Guard the assumption the markers rest on, not the markers themselves.