diff --git a/.github/workflows/actions-lock.yml b/.github/workflows/actions-lock.yml new file mode 100644 index 0000000..a49f8f8 --- /dev/null +++ b/.github/workflows/actions-lock.yml @@ -0,0 +1,116 @@ +# SPDX-License-Identifier: MPL-2.0 +name: Actions lock + +# Keeps .github/workflows/actions.lock in step with the workflows, remotely. +# +# Why this exists. A workflow listed in actions.lock runs only the commits the +# lock records. When a `uses:` ref changes and the lock does not, that workflow +# ends in startup_failure with no check run at all. Dependabot bumps `uses:` +# refs but does not yet rewrite the lock (dependabot-core gates that behind its +# `github_actions_lockfile` experiment), so every Actions bump broke CI here: +# #34 moved lean-action to v1.6.1 and codeql-action to v4.38.2 while the lock +# still named v1.6.0 / v4.38.0. Branch pins also go stale: the lock held +# cicd-suite@main at 5a10b72, before cicd-suite #32 fixed the gate that fails +# estate-audit on this repo. +# +# What it does. Runs the official extension (github/gh-actions-lock, pinned +# below) with --relock, which records new refs and advances branch pins to +# upstream HEAD, then opens a PR that changes ONLY actions.lock. A run that +# wants to touch any workflow file is refused (same assert as #25). +# +# When. On every push to main that changes a workflow (so a merged Dependabot +# bump is relocked within minutes), weekly (so branch pins track latest), and +# on demand. +# +# ⚠ NO `uses:` ANYWHERE, DELIBERATELY (see labels.yml): this workflow must run +# precisely when the lock is stale, so it depends on no action. +# +# Token. Falls back to GITHUB_TOKEN. Add a repository secret +# AUTOMATION_TOKEN (fine-grained: Contents, Issues, Pull requests and Workflows +# read/write on this repo) if GitHub refuses a GITHUB_TOKEN write under +# .github/workflows/, and so that CI runs on the PR it opens (PRs opened with +# GITHUB_TOKEN do not trigger workflows). The track-*.yml workflows share it. + +on: + push: + branches: [main] + paths: + - '.github/workflows/*.yml' + - '.github/workflows/*.yaml' + schedule: + - cron: "41 6 * * 1" # weekly, after Dependabot's Monday run + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: actions-lock + cancel-in-progress: false + +jobs: + relock: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + env: + GH_ACTIONS_LOCK_VERSION: v0.2.0 + GH_TOKEN: ${{ secrets.AUTOMATION_TOKEN || secrets.GITHUB_TOKEN }} + BASE: main + BRANCH: ci/relock-actions + steps: + - name: Relock and open a lockfile-only PR + run: | + set -euo pipefail + gh extension install github/gh-actions-lock --pin "$GH_ACTIONS_LOCK_VERSION" + gh auth setup-git + gh repo clone "$GITHUB_REPOSITORY" repo -- --quiet --depth 1 --branch "$BASE" + cd repo + + # Scope: the workflows the lock already tracks, plus every workflow + # with no `uses:` (recorded as [], nothing to rewrite). An untracked + # workflow WITH refs, e.g. the estate's canonical secret-scanner + # caller, is left alone: onboarding it may rewrite its SHA ref, so + # that stays a reviewed, manual step. A scoped run never prunes + # out-of-scope entries. + mapfile -t scope < <( + { sed -n "/^workflows:/,/^dependencies:/s/^ '\(\.github\/workflows\/[^']*\)':.*$/\1/p" \ + .github/workflows/actions.lock + for f in .github/workflows/*.yml .github/workflows/*.yaml; do + [ -f "$f" ] && ! grep -qE '^[[:space:]]*(-[[:space:]]+)?uses:' "$f" && echo "$f" + done + } | sort -u | while read -r p; do [ -f "$p" ] && echo "$p"; done) + printf 'relocking: %s\n' "${scope[@]}" + + gh actions-lock --relock --no-narrow --no-migrate-local-actions --no-interactive "${scope[@]}" + + changed=$(git status --porcelain | awk '{print $2}') + if [ -z "$changed" ]; then + echo "actions.lock is current; nothing to do." + exit 0 + fi + if [ "$changed" != ".github/workflows/actions.lock" ]; then + echo "::error::The relock wanted to change more than actions.lock. Refusing; handle by hand:" + git status --porcelain + exit 1 + fi + git --no-pager diff --stat + + git switch --quiet -c "$BRANCH" + git -c user.name='github-actions[bot]' \ + -c user.email='41898583+github-actions[bot]@users.noreply.github.com' \ + commit --quiet -am "chore(actions): relock actions.lock" \ + -m "Regenerated by .github/workflows/actions-lock.yml with github/gh-actions-lock $GH_ACTIONS_LOCK_VERSION (--relock --no-narrow --no-migrate-local-actions). Only actions.lock changes; every workflow file is byte-identical." + if ! git push --quiet --force origin "$BRANCH"; then + echo "::error::Push refused. If GitHub rejected a GITHUB_TOKEN write under .github/workflows/, add the AUTOMATION_TOKEN secret described at the top of this workflow." + exit 1 + fi + + body="Regenerated by \`actions-lock.yml\` (run $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID) with \`github/gh-actions-lock $GH_ACTIONS_LOCK_VERSION --relock\`. Only \`.github/workflows/actions.lock\` changes." + if [ -n "$(gh pr list --head "$BRANCH" --state open --json number --jq '.[].number')" ]; then + echo "Updated the open relock PR." + else + gh pr create --base "$BASE" --head "$BRANCH" \ + --title "chore(actions): relock actions.lock" --body "$body" + fi diff --git a/.github/workflows/actions.lock b/.github/workflows/actions.lock index 4e6ed21..3da4783 100644 --- a/.github/workflows/actions.lock +++ b/.github/workflows/actions.lock @@ -11,9 +11,6 @@ workflows: - 'github/codeql-action@v4.38.0' '.github/workflows/label-triage.yml': [] '.github/workflows/labels.yml': [] - '.github/workflows/main-estate-audit.yml': - - 'actions/checkout@v7.0.1' - - 'hyperpolymath/cicd-suite@main' '.github/workflows/pages.yml': - 'actions/checkout@v7.0.1' - 'actions/deploy-pages@v5.0.1' @@ -51,24 +48,6 @@ dependencies: commit: 'sha1-b96794f015dfd88f77b49b1c93e0fa7110f94c63' owner_id: 9919 repo_id: 259445878 - 'hyperpolymath/cicd-suite@main': - ref: 'main' - commit: 'sha1-5a10b72e574ef63855fafd4568a4c178657f3294' - owner_id: 6759885 - repo_id: 1326697643 - uses: - - 'hyperpolymath/deed-ecosystem@f9d999b60cb5f383679ea19912bcdc49c944973a' - - 'hyperpolymath/k9-ecosystem@2155aa26a21758f2ba119f61bc7e0e1981c106fb' - 'hyperpolymath/deed-ecosystem@f9d999b60cb5f383679ea19912bcdc49c944973a': - ref: 'main' - commit: 'sha1-f9d999b60cb5f383679ea19912bcdc49c944973a' - owner_id: 6759885 - repo_id: 1275649586 - 'hyperpolymath/k9-ecosystem@2155aa26a21758f2ba119f61bc7e0e1981c106fb': - ref: 'main' - commit: 'sha1-2155aa26a21758f2ba119f61bc7e0e1981c106fb' - owner_id: 6759885 - repo_id: 1275650185 'leanprover/lean-action@v1.6.0': ref: 'v1.6.0' commit: 'sha1-50fcf42d2e460296f1a34b402e990d1b24f8b596' diff --git a/.github/workflows/main-estate-audit.yml b/.github/workflows/main-estate-audit.yml index 2229d94..9880f31 100644 --- a/.github/workflows/main-estate-audit.yml +++ b/.github/workflows/main-estate-audit.yml @@ -1,95 +1,31 @@ -# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 -# This workflow is managed by gh actions-lock. - name: Central Estate CI/CD Audit +# Thin caller of the estate audit, which lives in hyperpolymath/cicd-suite as +# a reusable workflow and reports five component checks: governance, +# hygiene, manifests, languages, advisory. Do not copy the audit's steps in +# here. A copy freezes this repository at whatever cicd-suite commit its +# actions.lock happened to hold, and fixes made upstream never arrive. +# +# The pin is a full SHA, advanced weekly by track-estate-audit.yml (a PR +# whose own run of this audit is the check). +# +# ⚠ DE-ONBOARDED from actions.lock, deliberately, like the secret-scanner +# caller. A reusable workflow onboarded cross-repo startup-fails with zero +# jobs under sha_pinning_required (see the header of cicd-suite's +# main-estate-audit.yml). Do not run a full-directory `gh actions-lock` that +# re-onboards this file. If one does, cicd-suite/scripts/deonboard-estate-audit.sh +# reverses it. + on: push: branches: [ "main" ] pull_request: branches: [ "main" ] +permissions: + contents: read + jobs: - estate-audit: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7.0.1 - - - name: Required Files Gate - uses: hyperpolymath/cicd-suite/actions/required-files-check@main - - - name: Code Hygiene Gate - uses: hyperpolymath/cicd-suite/actions/code-hygiene-check@main - - - name: Manifest Validation Gate - uses: hyperpolymath/cicd-suite/actions/manifest-check@main - - - name: Idris2 ABI Purity Gate - uses: hyperpolymath/cicd-suite/actions/idris2-abi-check@main - - - name: Zig UnifiedApiAdapter API Gate - uses: hyperpolymath/cicd-suite/actions/zig-unified-api-adapter-check@main - - - name: Contractile Validation Gate - uses: hyperpolymath/cicd-suite/actions/contractile-validation-check@main - - - name: Recipes Set Validation Gate - uses: hyperpolymath/cicd-suite/actions/recipes-set-check@main - - - name: Affirmation Document Gate - uses: hyperpolymath/cicd-suite/actions/affirmation-check@main - - - name: Academic Referencing Gate - uses: hyperpolymath/cicd-suite/actions/referencing-check@main - - - name: Semantic Audit Gate - uses: hyperpolymath/cicd-suite/actions/semantic-audit-check@main - - - name: SPDX License Gate - uses: hyperpolymath/cicd-suite/actions/spdx-license-check@main - - - name: Proof Runner Gate - uses: hyperpolymath/cicd-suite/actions/proof-runner-check@main - - - name: PRAT Testing Gate - uses: hyperpolymath/cicd-suite/actions/prat-check@main - - - name: Panic Attack & Pons Gate - uses: hyperpolymath/cicd-suite/actions/custom-tools-check@main - - - name: WWW & Well-Known Compliance Gate - uses: hyperpolymath/cicd-suite/actions/www-compliance-check@main - - - name: BoJ Cartridge Validation Gate - uses: hyperpolymath/cicd-suite/actions/boj-cartridge-check@main - - - name: Formatting Validation Gate - uses: hyperpolymath/cicd-suite/actions/formatting-check@main - - - name: Accreditations & Badges Gate - uses: hyperpolymath/cicd-suite/actions/badges-check@main - - - name: Metrics Extraction Gate - uses: hyperpolymath/cicd-suite/actions/metrics-check@main - - - name: Linguist & Banned Languages Gate - uses: hyperpolymath/cicd-suite/actions/linguist-check@main - - - name: Test & Benchmarks Dashboard Gate - uses: hyperpolymath/cicd-suite/actions/tests-benches-check@main - - - name: Hosting & Site Status Gate - uses: hyperpolymath/cicd-suite/actions/hosting-check@main - - - name: Git-Sea Analytics Gate - uses: hyperpolymath/cicd-suite/actions/gitsea-check@main - - - name: Trust & Humans Validation Gate - uses: hyperpolymath/cicd-suite/actions/trust-humans-check@main - - - name: Are We UnAPI Gate (Secret Scanning) - uses: hyperpolymath/cicd-suite/actions/secrets-check@main - - - name: Reasonably Good Token Validation Gate - uses: hyperpolymath/cicd-suite/actions/vaulted-tokens-check@main + estate: + uses: hyperpolymath/cicd-suite/.github/workflows/main-estate-audit.yml@afa28ff81dc89ae7bd2304eead22917ec1733904 # cicd-suite#50 head; re-pin to its merge commit diff --git a/.github/workflows/track-estate-audit.yml b/.github/workflows/track-estate-audit.yml new file mode 100644 index 0000000..b52c28a --- /dev/null +++ b/.github/workflows/track-estate-audit.yml @@ -0,0 +1,90 @@ +# SPDX-License-Identifier: MPL-2.0 +name: Track latest estate audit + +# Weekly: is the cicd-suite estate audit this repository calls behind +# cicd-suite's main? If so, open a PR that moves the pin in +# main-estate-audit.yml to cicd-suite HEAD. The PR's own estate-audit run is +# the check: merge it when the five components are green. Nothing reaches +# main unreviewed. +# +# ⚠ NO `uses:` ANYWHERE, DELIBERATELY: actions.lock is keyed by workflow path, +# and this must run even when the lock is stale. +# +# Token: AUTOMATION_TOKEN, a fine-grained token on this repository with +# Contents, Pull requests and Workflows set to read/write. In practice it is +# required: the bump edits a file under .github/workflows/, which +# GITHUB_TOKEN may not write, and a PR opened by GITHUB_TOKEN does not trigger +# the estate-audit run that validates it. + +on: + schedule: + - cron: "23 7 * * 1" # weekly, Monday + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: track-estate-audit + cancel-in-progress: false + +jobs: + bump: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + env: + GH_TOKEN: ${{ secrets.AUTOMATION_TOKEN || secrets.GITHUB_TOKEN }} + BRANCH: ci/track-estate-audit + CALLER: .github/workflows/main-estate-audit.yml + BOT_EMAIL: 41898583+github-actions[bot]@users.noreply.github.com + steps: + - name: Move the cicd-suite pin to HEAD and open a PR + run: | + set -euo pipefail + gh auth setup-git + base=$(gh repo view "$GITHUB_REPOSITORY" --json defaultBranchRef --jq .defaultBranchRef.name) + gh repo clone "$GITHUB_REPOSITORY" repo -- --quiet --depth 1 --branch "$base" + cd repo + + pinned=$(grep -oE 'cicd-suite/\.github/workflows/main-estate-audit\.yml@[0-9a-f]{40}' "$CALLER" \ + | sed 's/.*@//' | head -n 1 || true) + if [ -z "$pinned" ]; then + echo "::error::$CALLER does not call cicd-suite's reusable estate audit at a full SHA." + exit 1 + fi + head=$(git ls-remote https://github.com/hyperpolymath/cicd-suite.git HEAD | cut -f1) + echo "pinned ${pinned:0:7}, cicd-suite HEAD ${head:0:7}" + if [ "$pinned" = "$head" ]; then + echo "Already at cicd-suite HEAD." + exit 0 + fi + + # The bot branch is rebuilt from the default branch each run, which + # would discard anything else on it. If someone has pushed to it + # (say, a fix the newer audit needed), leave it for review. + if git ls-remote --exit-code --heads origin "$BRANCH" >/dev/null; then + foreign=$(gh api "repos/$GITHUB_REPOSITORY/compare/$base...$BRANCH" \ + --jq "[.commits[] | select(.commit.author.email != \"$BOT_EMAIL\")] | length") + if [ "$foreign" != 0 ]; then + echo "::warning::$BRANCH carries $foreign commit(s) not made by this workflow; leaving it alone. Merge its PR or delete the branch, then re-run." + exit 0 + fi + fi + + sed -i "s/$pinned/$head/g" "$CALLER" + git switch --quiet -c "$BRANCH" + git -c user.name='github-actions[bot]' -c user.email="$BOT_EMAIL" \ + commit --quiet -am "ci(estate-audit): track cicd-suite ${head:0:7}" + if ! git push --quiet --force origin "$BRANCH"; then + echo "::error::Push refused: writing under .github/workflows/ needs the AUTOMATION_TOKEN secret described above." + exit 1 + fi + body="Moves the estate audit to cicd-suite \`${head:0:7}\` (was \`${pinned:0:7}\`): https://github.com/hyperpolymath/cicd-suite/compare/$pinned...$head. This PR's estate-audit run is the check. Opened by \`track-estate-audit.yml\`." + if [ -n "$(gh pr list --head "$BRANCH" --state open --json number --jq '.[].number')" ]; then + gh pr edit "$BRANCH" --title "ci(estate-audit): track cicd-suite ${head:0:7}" --body "$body" + else + gh pr create --base "$base" --head "$BRANCH" \ + --title "ci(estate-audit): track cicd-suite ${head:0:7}" --body "$body" + fi diff --git a/STATE.adoc b/STATE.adoc index 00ff2d0..3dbd601 100644 --- a/STATE.adoc +++ b/STATE.adoc @@ -1,5 +1,25 @@ = STATE — cross-session log +== 2026-10-14 — CI: actions.lock relocked remotely (not a rung) + +* One root cause behind both red checks: `.github/workflows/actions.lock` + drifted. + - Dependabot (#34) bumped `lean-action` → v1.6.1 and `codeql-action` → + v4.38.2 but left the lock on v1.6.0 / v4.38.0, so `CI` ends in + `startup_failure`. dependabot-core still gates lock rewriting behind its + `github_actions_lockfile` experiment. + - The lock held `cicd-suite@main` at `5a10b72`, before cicd-suite #32 fixed + the Required Files Gate (`grep -v | wc -l` under `-e -o pipefail` on a + comment-only CODEOWNERS). That is why `estate-audit` fails. The bug is + already fixed upstream, so nothing needs filing. +* `.github/workflows/actions-lock.yml` (new, no `uses:`) runs + `github/gh-actions-lock v0.2.0 --relock` remotely. It triggers on pushes to + main that change a workflow, weekly, and on dispatch. It is scoped to the + workflows the lock already tracks plus every workflow with no `uses:`, and opens a PR only when + `actions.lock` alone changes. +* Gates: actionlint 1.7.7 (with shellcheck) clean. The run itself can only + be exercised on GitHub, because this sandbox cannot reach the GitHub API. + == 2026-10-14 — toolchain v4.34.0 → v4.34.1 (patch; review finding on #36) * CodeRabbit flagged #36 (Major, security): v4.34.1 is the patched