diff --git a/.github/workflows/estate-rules.yml b/.github/workflows/estate-rules.yml index 2104ecb..8c8f44d 100644 --- a/.github/workflows/estate-rules.yml +++ b/.github/workflows/estate-rules.yml @@ -94,6 +94,14 @@ jobs: # the spine's at mint and left every Julia mint with Lock Sync red. - name: Archetype lock fragments merge into a valid lock run: bash tests/workflows/archetype_lock_merge_test.sh + # The four SHA-keyed leaves standards' reusables add keep an immutable ref, + # and the sync gate names each one if its record goes missing. + - name: actions.lock SHA-ref regression controls + run: bash tests/workflows/actions_lock_sha_refs_test.sh + # The Scorecard calling job's own permissions map must grant actions: read; + # the workflow-level grant does not reach a job that declares its own map. + - name: Scorecard caller grants + run: bash tests/workflows/scorecard_permissions_test.sh # The CHECK half of the template contract. repo-init has always written an # answer-file (now the child deed's (provenance) clause); nothing ever read it. # This is the missing reader, and it covers the #200/#201/#203 class: diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 744f792..6f5d239 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -1,20 +1,35 @@ == Changelog -== 2026-10-14 — governance pin `da2c748` → `934b879` - -* The six standards reusables (governance, hypatia-scan, mirror, rust-ci, - scorecard, secret-scanner) move to hyperpolymath/standards `934b879`. - At `da2c748`, governance ran its lock gate from a fixed helper pin with an - unpinned gh-actions-lock. Under v0.2.0 that gate failed *Actions lockfile - verify* on every job-level reusable ref, although it had accepted each one - as coverage. The new pin runs the gate at its own commit, with v0.2.0 - pinned (standards#1250). -* `actions.lock`: the `hyperpolymath/standards` record is rewritten at the - new pin with its called workflows' `uses:` union, and closed over the four - refs that union adds (setup-bun, setup-deno, upload-artifact, - rust-toolchain). The old record and the three refs only it named are - removed. +== 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`. + Its job-level `permissions:` block replaces the workflow-level one, so the + workflow-level grant never reached scorecard-reusable.yml, whose Packaging + check needs it. +* `actions.lock`: the four leaves that the reusables at standards `934b879` add + (upload-artifact, setup-deno, rust-toolchain, setup-bun) record their SHA as + `ref`, not the tag copied from standards' lock. + `tests/workflows/actions_lock_sha_refs_test.sh` checks this, with negative + controls, and `tests/workflows/scorecard_permissions_test.sh` checks the + grant; both run in Estate Rules. +* The governance re-pin that #7 was opened for had already landed, in #8. + +== 2026-10-14 — minted, and the estate checks made green + +* Minted with `just repo-init`. The repository had been created from + rsr-template-repo df09a6c with GitHub's template copy and never minted, + so it still declared itself the template. The deed is now + `resource-algebra_chora.deed` (clade `fv`), placeholders are filled, + and CLAUDE.md and the repository map describe this repository. +* `build/just/repo-init.just`: the non-interactive mint now completes; + just's own interpolation, the arrival pack, the repository map, the + state-validation detector and the OpenSSF audit table survive the mint. +* `scripts/check-agda.sh`: each expected rejection must land on the + fixture's `invalid` definition. +* `PROOF-STATUS.adoc` moved to `docs/status/PROOF-STATUS.adoc`. +* CI: the standards reusables re-pinned to standards main, with a closed + `actions.lock`; `CITATION.cff` gained its required `message`. == 2026-10-14 — MinMax and morphisms (resource-algebra#3) diff --git a/tests/workflows/scorecard_permissions_test.sh b/tests/workflows/scorecard_permissions_test.sh new file mode 100755 index 0000000..7e23c59 --- /dev/null +++ b/tests/workflows/scorecard_permissions_test.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# +# PR #7: the caller's explicit job permission map must grant actions: read. +# Top-level grants do not supply omitted scopes in a job permission map. +# Run: bash tests/workflows/scorecard_permissions_test.sh +# Requires mikefarah/yq v4. +set -euo pipefail + +repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd) +workflow="$repo/.github/workflows/scorecard.yml" +fixture=$(mktemp -d) +trap 'rm -rf "$fixture"' EXIT + +fail() { echo "FAIL: $*" >&2; exit 1; } +yq --version | grep -q 'github.com/mikefarah/yq.*version v4\.' \ + || fail "mikefarah/yq v4 is required" + +permissions_ok() { + yq -e ' + .jobs.scorecard.permissions | + (.actions == "read" and .contents == "read" and + .["security-events"] == "write" and .["id-token"] == "write") + ' "$1" >/dev/null 2>&1 +} + +reject_mutant() { + local label=$1 expression=$2 + cp "$workflow" "$fixture/mutant.yml" + yq -i "$expression" "$fixture/mutant.yml" + cmp -s "$workflow" "$fixture/mutant.yml" && fail "$label: mutation did not change the workflow" + if permissions_ok "$fixture/mutant.yml"; then + fail "$label: invalid job permissions accepted" + fi +} + +permissions_ok "$workflow" || fail "Scorecard lacks a required caller grant" + +# The original failure: workflow-level actions: read is still present, but the +# job's explicit map omits actions. A text search would incorrectly pass this. +reject_mutant "top-level-only actions grant" 'del(.jobs.scorecard.permissions.actions)' +reject_mutant "actions disabled" '.jobs.scorecard.permissions.actions = "none"' +reject_mutant "unnecessary actions write access" '.jobs.scorecard.permissions.actions = "write"' +reject_mutant "null actions grant" '.jobs.scorecard.permissions.actions = null' +reject_mutant "empty job permission map" '.jobs.scorecard.permissions = {}' +reject_mutant "missing job permission map" 'del(.jobs.scorecard.permissions)' + +# A grant on a sibling job cannot authorize the reusable call either. +reject_mutant "actions grant on the wrong job" ' + del(.jobs.scorecard.permissions.actions) | + .jobs.other.permissions.actions = "read" +' + +# Adding the staleness-check grant must preserve the existing checkout, SARIF +# upload and OIDC grants needed by this same reusable call. +for scope in contents security-events id-token; do + SCOPE="$scope" reject_mutant "missing $scope grant" \ + 'del(.jobs.scorecard.permissions[strenv(SCOPE)])' +done + +# YAML quoting, comments and key order have no effect on permission semantics. +cp "$workflow" "$fixture/reordered.yml" +yq -i '.jobs.scorecard.permissions = { + "id-token": "write", "security-events": "write", + "contents": "read", "actions": "read" +}' "$fixture/reordered.yml" +printf '\n# actions: none (a comment, not a grant)\n' >>"$fixture/reordered.yml" +permissions_ok "$fixture/reordered.yml" || fail "equivalent YAML permissions rejected" + +echo "PASS: Scorecard caller grants; 10 invalid permission controls rejected; equivalent YAML accepted"