diff --git a/.github/workflows/actions.lock b/.github/workflows/actions.lock index 746dbf0..bc650e1 100644 --- a/.github/workflows/actions.lock +++ b/.github/workflows/actions.lock @@ -31,13 +31,13 @@ workflows: '.github/workflows/estate-rules.yml': - 'actions/checkout@v7.0.1' '.github/workflows/governance.yml': - - 'hyperpolymath/standards@934b879f6068295e42dac20d80e010d555ebec82' + - 'hyperpolymath/standards@75bd5994470661a099df562d6d5ad2fe744dbd66' '.github/workflows/groove-check.yml': - 'actions/checkout@v7.0.1' '.github/workflows/guix-policy.yml': - 'actions/checkout@v7.0.1' '.github/workflows/hypatia-scan.yml': - - 'hyperpolymath/standards@934b879f6068295e42dac20d80e010d555ebec82' + - 'hyperpolymath/standards@75bd5994470661a099df562d6d5ad2fe744dbd66' '.github/workflows/k9-validate.yml': - 'actions/checkout@v7.0.1' - 'hyperpolymath/k9-ecosystem@main' @@ -47,7 +47,7 @@ workflows: '.github/workflows/main-estate-audit.yml': - 'hyperpolymath/cicd-suite@55556cf5ba71ba744695861503c13148d3915a5d' '.github/workflows/mirror.yml': - - 'hyperpolymath/standards@934b879f6068295e42dac20d80e010d555ebec82' + - 'hyperpolymath/standards@75bd5994470661a099df562d6d5ad2fe744dbd66' '.github/workflows/ossf-best-practices.yml': - 'actions/checkout@v7.0.1' '.github/workflows/pages.yml': @@ -74,11 +74,11 @@ workflows: '.github/workflows/runtime-policy.yml': - 'actions/checkout@v7.0.1' '.github/workflows/rust-ci.yml': - - 'hyperpolymath/standards@934b879f6068295e42dac20d80e010d555ebec82' + - 'hyperpolymath/standards@75bd5994470661a099df562d6d5ad2fe744dbd66' '.github/workflows/scorecard.yml': - - 'hyperpolymath/standards@934b879f6068295e42dac20d80e010d555ebec82' + - 'hyperpolymath/standards@75bd5994470661a099df562d6d5ad2fe744dbd66' '.github/workflows/secret-scanner.yml': - - 'hyperpolymath/standards@934b879f6068295e42dac20d80e010d555ebec82' + - 'hyperpolymath/standards@75bd5994470661a099df562d6d5ad2fe744dbd66' '.github/workflows/security-policy.yml': - 'actions/checkout@v7.0.1' '.github/workflows/sonarqube.yml': @@ -106,7 +106,7 @@ dependencies: owner_id: 44036562 repo_id: 760701061 'actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9': - ref: '55cc8345863c7cc4c66a329aec7e433d2d1c52a9' + ref: 'v6.1.0' commit: 'sha1-55cc8345863c7cc4c66a329aec7e433d2d1c52a9' owner_id: 44036562 repo_id: 215566462 @@ -116,7 +116,7 @@ dependencies: owner_id: 44036562 repo_id: 215566462 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1': - ref: '3d3c42e5aac5ba805825da76410c181273ba90b1' + ref: 'v7.0.1' commit: 'sha1-3d3c42e5aac5ba805825da76410c181273ba90b1' owner_id: 44036562 repo_id: 197814629 @@ -146,7 +146,7 @@ dependencies: owner_id: 44036562 repo_id: 192625955 'actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9': - ref: 'cf430e030ddbb5b0abf93d22962f4752f3646cd9' + ref: 'v7.0.2' commit: 'sha1-cf430e030ddbb5b0abf93d22962f4752f3646cd9' owner_id: 44036562 repo_id: 192625955 @@ -163,7 +163,7 @@ dependencies: uses: - 'actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f' 'denoland/setup-deno@22d081ff2d3a40755e97629de92e3bcbfa7cf2ed': - ref: '22d081ff2d3a40755e97629de92e3bcbfa7cf2ed' + ref: 'v2.0.5' commit: 'sha1-22d081ff2d3a40755e97629de92e3bcbfa7cf2ed' owner_id: 42048915 repo_id: 356423100 @@ -173,12 +173,12 @@ dependencies: owner_id: 27347476 repo_id: 371068214 'dtolnay/rust-toolchain@e2a55d2ffb04f378e9626c28d38b36d230d1e12f': - ref: 'e2a55d2ffb04f378e9626c28d38b36d230d1e12f' + ref: 'v1' commit: 'sha1-e2a55d2ffb04f378e9626c28d38b36d230d1e12f' owner_id: 1940490 repo_id: 260749683 'editorconfig-checker/action-editorconfig-checker@51f63319f592f97930c73d9c46184d20bd206393': - ref: '51f63319f592f97930c73d9c46184d20bd206393' + ref: 'v3.0.0' commit: 'sha1-51f63319f592f97930c73d9c46184d20bd206393' owner_id: 26415196 repo_id: 297874902 @@ -188,7 +188,7 @@ dependencies: owner_id: 26415196 repo_id: 297874902 'erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124': - ref: '54075bcc5e249e4758d363f27d099f55d843f124' + ref: 'v1.24.1' commit: 'sha1-54075bcc5e249e4758d363f27d099f55d843f124' owner_id: 47606891 repo_id: 331103973 @@ -203,7 +203,7 @@ dependencies: owner_id: 9919 repo_id: 259445878 'goto-bus-stop/setup-zig@abea47f85e598557f500fa1fd2ab7464fcb39406': - ref: 'abea47f85e598557f500fa1fd2ab7464fcb39406' + ref: 'v2.2.1' commit: 'sha1-abea47f85e598557f500fa1fd2ab7464fcb39406' owner_id: 1006268 repo_id: 212984112 @@ -240,9 +240,9 @@ dependencies: commit: 'sha1-c1c9fa07992a02c1fd3d67a0dc1b08cccb852aef' owner_id: 6759885 repo_id: 1352485172 - 'hyperpolymath/standards@934b879f6068295e42dac20d80e010d555ebec82': - ref: '934b879f6068295e42dac20d80e010d555ebec82' - commit: 'sha1-934b879f6068295e42dac20d80e010d555ebec82' + 'hyperpolymath/standards@75bd5994470661a099df562d6d5ad2fe744dbd66': + ref: '75bd5994470661a099df562d6d5ad2fe744dbd66' + commit: 'sha1-75bd5994470661a099df562d6d5ad2fe744dbd66' owner_id: 6759885 repo_id: 1116521501 uses: @@ -260,12 +260,12 @@ dependencies: - 'swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6' - 'webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555' 'ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc': - ref: '2d1146689b8cda280b9bc96326124645441f03bc' + ref: 'v2.4.4' commit: 'sha1-2d1146689b8cda280b9bc96326124645441f03bc' owner_id: 67707773 repo_id: 421101922 'oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6': - ref: '0c5077e51419868618aeaa5fe8019c62421857d6' + ref: 'v2.2.0' commit: 'sha1-0c5077e51419868618aeaa5fe8019c62421857d6' owner_id: 108928776 repo_id: 512644635 @@ -285,12 +285,12 @@ dependencies: owner_id: 545988 repo_id: 366408409 'swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6': - ref: '6323deb102c322ba6fcbdcafc7e3dddab59af2b6' + ref: 'v2.9.2' commit: 'sha1-6323deb102c322ba6fcbdcafc7e3dddab59af2b6' owner_id: 580492 repo_id: 298565987 'webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555': - ref: 'e83874834305fe9a4a2997156cb26c5de65a8555' + ref: 'v0.10.0' commit: 'sha1-e83874834305fe9a4a2997156cb26c5de65a8555' owner_id: 135788 repo_id: 208510314 diff --git a/.github/workflows/governance.yml b/.github/workflows/governance.yml index 8ef98da..4609772 100644 --- a/.github/workflows/governance.yml +++ b/.github/workflows/governance.yml @@ -18,4 +18,4 @@ permissions: jobs: governance: - uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@934b879f6068295e42dac20d80e010d555ebec82 + uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@75bd5994470661a099df562d6d5ad2fe744dbd66 diff --git a/.github/workflows/hypatia-scan.yml b/.github/workflows/hypatia-scan.yml index e95d4ed..734f602 100644 --- a/.github/workflows/hypatia-scan.yml +++ b/.github/workflows/hypatia-scan.yml @@ -27,4 +27,4 @@ permissions: jobs: hypatia: - uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@934b879f6068295e42dac20d80e010d555ebec82 + uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@75bd5994470661a099df562d6d5ad2fe744dbd66 diff --git a/.github/workflows/mirror.yml b/.github/workflows/mirror.yml index c118db0..5e19042 100644 --- a/.github/workflows/mirror.yml +++ b/.github/workflows/mirror.yml @@ -13,7 +13,7 @@ permissions: contents: read jobs: mirror: - uses: hyperpolymath/standards/.github/workflows/mirror-reusable.yml@934b879f6068295e42dac20d80e010d555ebec82 + uses: hyperpolymath/standards/.github/workflows/mirror-reusable.yml@75bd5994470661a099df562d6d5ad2fe744dbd66 # Explicit secrets map — no `secrets: inherit` (Hypatia WH008, alert #131). # All seven are the callee's complete optional contract (standards # mirror-reusable.yml@da2c748); behaviour is unchanged, future secrets diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index 4f27132..d4d39e5 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -16,4 +16,4 @@ permissions: contents: read jobs: rust-ci: - uses: hyperpolymath/standards/.github/workflows/rust-ci-reusable.yml@934b879f6068295e42dac20d80e010d555ebec82 + uses: hyperpolymath/standards/.github/workflows/rust-ci-reusable.yml@75bd5994470661a099df562d6d5ad2fe744dbd66 diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 77510c4..bd93efe 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -16,7 +16,7 @@ permissions: jobs: scorecard: - uses: hyperpolymath/standards/.github/workflows/scorecard-reusable.yml@934b879f6068295e42dac20d80e010d555ebec82 + uses: hyperpolymath/standards/.github/workflows/scorecard-reusable.yml@75bd5994470661a099df562d6d5ad2fe744dbd66 # Reusable called-workflow permissions are CAPPED by the caller's grants; # without security-events: write here the scorecard SARIF upload fails with # startup_failure (hypatia WF018). id-token: write enables OIDC publish. diff --git a/.github/workflows/secret-scanner.yml b/.github/workflows/secret-scanner.yml index 403f0b3..2adeddc 100644 --- a/.github/workflows/secret-scanner.yml +++ b/.github/workflows/secret-scanner.yml @@ -26,4 +26,4 @@ jobs: # is therefore the cap GitHub applies to the called workflow. permissions: contents: read - uses: hyperpolymath/standards/.github/workflows/secret-scanner-reusable.yml@934b879f6068295e42dac20d80e010d555ebec82 + uses: hyperpolymath/standards/.github/workflows/secret-scanner-reusable.yml@75bd5994470661a099df562d6d5ad2fe744dbd66 diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 6f5d239..996fbf0 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -1,6 +1,20 @@ == Changelog +== 2026-10-19 — lock records carry tag refs; standards re-pinned to `75bd599` + +* Since 2026-10-15 GitHub's runner rejects a lock record whose `ref` is its + own SHA ("invalid lockfile pin"). Governance, Rust CI, Mirror, Code Quality + and Pages died at startup on main. +* The six standards reusables move from `934b879` to `75bd599`, whose own lock + carries tag refs (hyperpolymath/standards#1271, + hyperpolymath/standards#1285). `relock-standards-pin.sh` at `75bd599` gave + the twelve self-SHA action records in `actions.lock` the same tags. No + commit changes: the key and `commit` still pin each action by SHA. +* This reverses #7's four SHA refs (upload-artifact, setup-deno, + rust-toolchain, setup-bun). `tests/workflows/actions_lock_sha_refs_test.sh` + now requires a tag ref and rejects the self-SHA form as a negative control. + == 2026-10-14 — Scorecard grants `actions: read`; four lock refs kept immutable (resource-algebra#7) * `.github/workflows/scorecard.yml`: the calling job grants `actions: read`. diff --git a/tests/workflows/actions_lock_sha_refs_test.sh b/tests/workflows/actions_lock_sha_refs_test.sh index 20218c0..986f94e 100755 --- a/tests/workflows/actions_lock_sha_refs_test.sh +++ b/tests/workflows/actions_lock_sha_refs_test.sh @@ -1,8 +1,11 @@ #!/usr/bin/env bash # SPDX-License-Identifier: MPL-2.0 # -# PR #7 regression: the four SHA-keyed leaves added by standards must keep -# their immutable ref, rather than the tag gh-actions-lock assigned to them. +# The twelve SHA-keyed leaves relocked in PR #12 must carry a symbolic (tag) +# ref, with the SHA in their key and commit. GitHub's runner rejects a record +# whose ref is its own SHA ("invalid lockfile pin", enforced from 2026-10-15; +# standards#1271), so the SHA ref PR #7 asked for stopped every job that +# resolved such a record. The commit, not the ref, pins the action. # Separate tag-keyed records remain valid. This test is offline. # Usage: bash tests/workflows/actions_lock_sha_refs_test.sh [workflow-directory] set -euo pipefail @@ -26,16 +29,41 @@ STANDARDS=$(jq -er ' | select(length == 1) | .[0] ' "$FIXTURE/lock.json") || fail "governance must lock exactly one standards reusable" +# All six callers must use the same immutable standards revision, including +# the reusable's path (a lock key alone loses that path information). +standards_sha=${STANDARDS##*@} +[[ "$standards_sha" =~ ^[0-9a-f]{40}$ ]] || fail "standards must be pinned by SHA" +for workflow in governance hypatia-scan mirror rust-ci scorecard secret-scanner; do + jq -e --arg workflow ".github/workflows/$workflow.yml" --arg standards "$STANDARDS" ' + .workflows[$workflow] == [$standards] + ' "$FIXTURE/lock.json" >/dev/null || fail "$workflow must lock the shared standards pin" + expected="hyperpolymath/standards/.github/workflows/$workflow-reusable.yml@$standards_sha" + yq -o=json '.jobs' "$WF_DIR/$workflow.yml" | jq -e --arg expected "$expected" ' + [.[].uses | select(. != null)] == [$expected] + ' >/dev/null || fail "$workflow must call $expected" +done + +# Reusable-workflow records still use a SHA ref. The symbolic-ref requirement +# applies to action leaves, so do not reject this intentional exception. +jq -e --arg standards "$STANDARDS" --arg sha "$standards_sha" ' + .dependencies[$standards] + | .ref == $sha and .commit == ("sha1-" + $sha) + and .owner_id == 6759885 and .repo_id == 1116521501 + and (.uses | type == "array" and length > 0) +' "$FIXTURE/lock.json" >/dev/null || fail "standards lost its SHA binding or repository identity" +echo "PASS: six standards callers share a locked SHA and the correct reusable paths" + # Assertions on parsed data, independent of the sync gate (which checks edges # but deliberately does not inspect ref/commit metadata). -immutable_record() { +pinned_record() { local key="$1" file="$2" jq -e --arg key "$key" ' ($key | split("@")[1]) as $sha | .dependencies[$key] as $record | ($sha | test("^[0-9a-f]{40}$")) - and ($record.ref == $sha) and ($record.commit == ("sha1-" + $sha)) + and ($record.ref | type == "string" and . != "") + and ($record.ref | test("^[0-9a-fA-F]{40}$") | not) ' "$file" >/dev/null } @@ -47,35 +75,71 @@ mkdir -p "$FIXTURE/workflows" cp -a "$WF_DIR/." "$FIXTURE/workflows/" checked=0 -while IFS='|' read -r action former_ref owner_id repo_id; do +while IFS='|' read -r action tag_ref owner_id repo_id; do key=$(jq -er --arg standards "$STANDARDS" --arg action "$action" ' [.dependencies[$standards].uses[] | select(startswith($action + "@"))] | select(length == 1) | .[0] ' "$FIXTURE/lock.json") || fail "$action must occur exactly once in standards transitive coverage" - immutable_record "$key" "$FIXTURE/lock.json" \ - || fail "$key must have matching immutable ref and commit" + pinned_record "$key" "$FIXTURE/lock.json" \ + || fail "$key must have a symbolic ref and its own SHA as commit" jq -e --arg key "$key" --argjson owner "$owner_id" --argjson repo "$repo_id" ' .dependencies[$key] | .owner_id == $owner and .repo_id == $repo ' "$FIXTURE/lock.json" >/dev/null || fail "$key lost its repository identity" - # Reproduce each of the four exact pre-PR tag substitutions. Even though - # the key and commit still carry the SHA, ref must not float to a tag. - jq --arg key "$key" --arg tag "$former_ref" ' - .dependencies[$key].ref = $tag - ' "$FIXTURE/lock.json" > "$FIXTURE/mutated.json" - if immutable_record "$key" "$FIXTURE/mutated.json"; then - fail "$key accepted its former floating ref $former_ref" + # Any full SHA ref is refused, even one unrelated to the commit, and hex + # case must not bypass the check (including mixed case). + sha=${key##*@} + for bad_ref in "$sha" "${sha^^}" 'aBcDeF0123456789aBcDeF0123456789aBcDeF01' \ + '0000000000000000000000000000000000000000' ""; do + jq --arg key "$key" --arg ref "$bad_ref" ' + .dependencies[$key].ref = $ref + ' "$FIXTURE/lock.json" > "$FIXTURE/mutated.json" + if pinned_record "$key" "$FIXTURE/mutated.json"; then + fail "$key accepted the refused ref '$bad_ref'" + fi + done + + # Missing and non-string metadata must not masquerade as a symbolic ref. + # jq may report a type error for these inputs; either way the gate must + # reject the record rather than treating its truthiness as a valid pin. + for bad_ref in null false 42 '[]' '{}'; do + jq --arg key "$key" --argjson ref "$bad_ref" ' + .dependencies[$key].ref = $ref + ' "$FIXTURE/lock.json" > "$FIXTURE/mutated.json" + if pinned_record "$key" "$FIXTURE/mutated.json" 2> "$FIXTURE/ref.err"; then + fail "$key accepted a non-string ref $bad_ref" + fi + done + jq --arg key "$key" 'del(.dependencies[$key].ref)' \ + "$FIXTURE/lock.json" > "$FIXTURE/mutated.json" + if pinned_record "$key" "$FIXTURE/mutated.json" 2> "$FIXTURE/ref.err"; then + fail "$key accepted a missing ref" fi - # A valid-looking, different SHA is still a broken key/ref/commit binding. + # A valid-looking, different SHA is still a broken key/commit binding. jq --arg key "$key" ' .dependencies[$key].commit = "sha1-0000000000000000000000000000000000000000" ' "$FIXTURE/lock.json" > "$FIXTURE/mutated.json" - if immutable_record "$key" "$FIXTURE/mutated.json"; then + if pinned_record "$key" "$FIXTURE/mutated.json"; then fail "$key accepted a mismatched commit" fi + # A raw SHA, absent value, or JSON null cannot replace the sha1- binding. + for bad_commit in "\"$sha\"" null false '""'; do + jq --arg key "$key" --argjson commit "$bad_commit" ' + .dependencies[$key].commit = $commit + ' "$FIXTURE/lock.json" > "$FIXTURE/mutated.json" + if pinned_record "$key" "$FIXTURE/mutated.json"; then + fail "$key accepted an invalid commit $bad_commit" + fi + done + jq --arg key "$key" 'del(.dependencies[$key].commit)' \ + "$FIXTURE/lock.json" > "$FIXTURE/mutated.json" + if pinned_record "$key" "$FIXTURE/mutated.json"; then + fail "$key accepted a missing commit" + fi + # Delete only this leaf record, keeping its incoming standards edge and # all other workflow/lock data intact. Preserve gh-actions-lock layout: # the production gate parses that layout, not yq's reformatted output. @@ -93,12 +157,18 @@ while IFS='|' read -r action former_ref owner_id repo_id; do fail "sync gate did not identify the missing $key" fi - # upload-artifact and setup-bun also have direct tag users. Their records - # must coexist with the SHA records; the repair must not globally ban tags. - if [[ "$action" == actions/upload-artifact || "$action" == oven-sh/setup-bun ]]; then - jq -e --arg key "$key" --arg alias "$action@$former_ref" --arg tag "$former_ref" ' + # Some actions also have direct tag users (notably Pages' cache and Code + # Quality's editorconfig checker). Their tag and SHA records must coexist + # with identical commits and repository identities. + if [[ "$action" == actions/cache || "$action" == actions/checkout \ + || "$action" == actions/upload-artifact \ + || "$action" == editorconfig-checker/action-editorconfig-checker \ + || "$action" == erlef/setup-beam || "$action" == oven-sh/setup-bun ]]; then + jq -e --arg key "$key" --arg alias "$action@$tag_ref" --arg tag "$tag_ref" ' .dependencies[$alias].ref == $tag and (.dependencies[$alias].commit == .dependencies[$key].commit) + and (.dependencies[$alias].owner_id == .dependencies[$key].owner_id) + and (.dependencies[$alias].repo_id == .dependencies[$key].repo_id) and ([.workflows[][] | select(. == $alias)] | length > 0) ' "$FIXTURE/lock.json" >/dev/null || fail "$action lost its separate referenced tag record" fi @@ -106,11 +176,19 @@ while IFS='|' read -r action former_ref owner_id repo_id; do checked=$((checked + 1)) echo "PASS: $action SHA binding, identity, negative controls and closure" done <<'CASES' +actions/cache|v6.1.0|44036562|215566462 +actions/checkout|v7.0.1|44036562|197814629 actions/upload-artifact|v7.0.2|44036562|192625955 denoland/setup-deno|v2.0.5|42048915|356423100 dtolnay/rust-toolchain|v1|1940490|260749683 +editorconfig-checker/action-editorconfig-checker|v3.0.0|26415196|297874902 +erlef/setup-beam|v1.24.1|47606891|331103973 +goto-bus-stop/setup-zig|v2.2.1|1006268|212984112 +ossf/scorecard-action|v2.4.4|67707773|421101922 oven-sh/setup-bun|v2.2.0|108928776|512644635 +swatinem/rust-cache|v2.9.2|580492|298565987 +webfactory/ssh-agent|v0.10.0|135788|208510314 CASES -[[ "$checked" == 4 ]] || fail "expected to check all four changed dependency records" +[[ "$checked" == 12 ]] || fail "expected to check all twelve changed dependency records" echo "PASS: actions.lock SHA-ref regression controls ($checked dependencies)"