Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions .github/workflows/lockfile-drift-detect.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ jobs:
set -euo pipefail
: > drift-report.tsv
printf 'repo\tworkflow\trequested\tlocked\n' >> drift-report.tsv
# Second report from the same clones: workflows that run their own
# gh-actions-lock beside governance's lockfile gate (the copied
# workflow-linter step). Owner decision 2026-10-14: drop them; the
# estate wave works this list. Lock-bearing repos only, as below.
printf 'repo\tworkflow\tpins\n' > copied-lock-step.tsv

# Enumerate, then filter. `gh repo list` has a denominator we can
# state; `gh search` does not. A count landing on a round number is a
Expand All @@ -80,7 +85,7 @@ jobs:
mv r.tmp repos.txt
fi

scanned=0; withlock=0; drifted=0; errors=0
scanned=0; withlock=0; drifted=0; errors=0; copied=0
while read -r r; do
scanned=$((scanned+1))
# Cheap probe first: skip repos with no lockfile (that is failure
Expand All @@ -107,6 +112,14 @@ jobs:
errors=$((errors+1))
echo "::warning::$r: drift check errored (rc=$rc) — see drift-errors.log"
fi
rc=0
bash scripts/check-copied-lock-step.sh _w "$r" >> copied-lock-step.tsv 2>> drift-errors.log || rc=$?
if [ "$rc" -eq 1 ]; then
copied=$((copied+1))
elif [ "$rc" -ne 0 ]; then
errors=$((errors+1))
echo "::warning::$r: copied-lock-step check errored (rc=$rc) — see drift-errors.log"
fi
rm -rf _w
done < repos.txt

Expand All @@ -122,15 +135,18 @@ jobs:
echo "drifted=$drifted"
echo "errors=$errors"
echo "rows=$rows"
echo "copied=$copied"
} >> "$GITHUB_OUTPUT"
echo "scanned=$scanned withlock=$withlock drifted=$drifted errors=$errors rows=$rows"
echo "scanned=$scanned withlock=$withlock drifted=$drifted errors=$errors rows=$rows copied=$copied"

- name: Upload report
if: always()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9
with:
name: lockfile-drift-report
path: drift-report.tsv
path: |
drift-report.tsv
copied-lock-step.tsv
retention-days: 90

- name: Step summary
Expand All @@ -151,6 +167,12 @@ jobs:
echo '```'
head -40 drift-report.tsv
echo '```'
echo
echo "Running their own gh-actions-lock (copied linter step): **${{ steps.sweep.outputs.copied }}** repo(s)"
echo
echo '```'
head -40 copied-lock-step.tsv
echo '```'
} >> "$GITHUB_STEP_SUMMARY"

- name: Open / update the tracking issue
Expand Down Expand Up @@ -185,6 +207,7 @@ jobs:
echo "| **with drift** | **${{ steps.sweep.outputs.drifted }}** |"
echo "| check errors (rc≠0,1) | ${{ steps.sweep.outputs.errors }} |"
echo "| drifted entries | ${{ steps.sweep.outputs.rows }} |"
echo "| running their own gh-actions-lock | ${{ steps.sweep.outputs.copied }} |"
} > issue-body.md
body="$(cat issue-body.md)"
existing=$(gh issue list --repo "$GITHUB_REPOSITORY" --label lockfile-drift \
Expand Down
61 changes: 61 additions & 0 deletions scripts/check-copied-lock-step.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: MPL-2.0
#
# check-copied-lock-step.sh — find workflows that run their own gh-actions-lock.
#
# The fault: a sweep copied a "Verify immutable action lockfile" step into each
# repo's workflow-linter.yml. It downloads a pinned gh-actions-lock binary
# (v0.1.6) and runs it raw. That gives a second verifier beside governance's
# lockfile gate (scripts/update-actions-lock.sh), with a second version pin that
# drifts from the estate's. v0.1.6 cannot read lockfile v0.0.3, which every
# current `gh actions-lock` writes, so the step failed whenever a current tool
# refreshed the lock (proof-burrower#111, #112). Owner decision 2026-10-14:
# drop the step from copied linters and let governance own lock verification.
#
# This lists what is left. It reports; it never edits.
#
# Usage: check-copied-lock-step.sh REPO_DIR [REPO_SLUG]
# REPO_SLUG default: basename(REPO_DIR); pass it for a throwaway
# clone so column 1 names the repository.
# Output: TSV on stdout, one row per workflow:
# repo <TAB> workflow <TAB> version(s) it pins ("unpinned" if none)
# Exit: 0 = none found (or no workflows), 1 = found, 2 = usage error.
#
# standards itself is skipped: its installs ARE the estate pin, and
# scripts/tests/gh-actions-lock-contract-test.sh holds them to one version.
set -uo pipefail

REPO_DIR="${1:-}"
[ -n "$REPO_DIR" ] && [ -d "$REPO_DIR" ] || {

Check failure on line 29 in scripts/check-copied-lock-step.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaE53v-jMVzwizfJ5wmU&open=AaE53v-jMVzwizfJ5wmU&pullRequest=1251

Check failure on line 29 in scripts/check-copied-lock-step.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaE53v-jMVzwizfJ5wmV&open=AaE53v-jMVzwizfJ5wmV&pullRequest=1251
echo "usage: check-copied-lock-step.sh REPO_DIR [REPO_SLUG]" >&2; exit 2; }
REPO_SLUG="${2:-$(basename "$REPO_DIR")}"
WFDIR="$REPO_DIR/.github/workflows"

case "$REPO_SLUG" in standards|*/standards) exit 0 ;; esac

Check failure on line 34 in scripts/check-copied-lock-step.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add a default case (*) to handle unexpected values.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaE53v-jMVzwizfJ5wmW&open=AaE53v-jMVzwizfJ5wmW&pullRequest=1251
[ -d "$WFDIR" ] || exit 0

Check failure on line 35 in scripts/check-copied-lock-step.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaE53v-jMVzwizfJ5wmX&open=AaE53v-jMVzwizfJ5wmX&pullRequest=1251

found=0
for wf in "$WFDIR"/*.yml "$WFDIR"/*.yaml; do
[ -f "$wf" ] || continue

Check failure on line 39 in scripts/check-copied-lock-step.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaE53v-jMVzwizfJ5wmY&open=AaE53v-jMVzwizfJ5wmY&pullRequest=1251
# Comment lines are prose, not a step. A KYAML `run:` is one quoted scalar,
# so the substring test still sees the command inside it.
versions="$(awk '
/^[[:space:]]*#/ { next }
/gh-actions-lock\/releases\/download\// {
if (match($0, /gh-actions-lock\/releases\/download\/v[0-9]+\.[0-9]+\.[0-9]+/))
{ v = substr($0, RSTART, RLENGTH); sub(/.*\//, "", v); print v }
else print "unpinned"
next
}
/extension[[:space:]]+install[[:space:]]+github\/gh-actions-lock/ {
if (match($0, /--pin[[:space:]]+v[0-9]+\.[0-9]+\.[0-9]+/)) {
v = substr($0, RSTART, RLENGTH); sub(/--pin[[:space:]]+/, "", v); print v
} else print "unpinned"
}
' "$wf" | sort -u | paste -sd, -)"
if [ -n "$versions" ]; then

Check failure on line 56 in scripts/check-copied-lock-step.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaE53v-jMVzwizfJ5wmZ&open=AaE53v-jMVzwizfJ5wmZ&pullRequest=1251
printf '%s\t%s\t%s\n' "$REPO_SLUG" "${wf#"$REPO_DIR"/}" "$versions"
found=1
fi
done
exit "$found"
90 changes: 90 additions & 0 deletions scripts/tests/check-copied-lock-step-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: MPL-2.0
#
# Proves check-copied-lock-step.sh finds a workflow that runs its own
# gh-actions-lock (raw release download or `gh extension install`), in block
# YAML and KYAML, names the pinned version, and stays quiet for prose, for a
# clean tree and for standards itself.
set -uo pipefail
SCRIPT="$(cd "$(dirname "$0")/.." && pwd)/check-copied-lock-step.sh"
WORK="$(mktemp -d)"; trap 'rm -rf "$WORK"' EXIT

pass=0; fail=0
expect() { # expect <wanted-exit> <wanted-stdout> <label> <args...>

Check warning on line 13 in scripts/tests/check-copied-lock-step-test.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add an explicit return statement at the end of the function.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaE53v3sMVzwizfJ5wmR&open=AaE53v3sMVzwizfJ5wmR&pullRequest=1251
local want="$1" out="$2" label="$3" got=0; shift 3
bash "$SCRIPT" "$@" >"$WORK/out" 2>"$WORK/err" || got=$?
if [ "$got" = "$want" ] && [ "$(cat "$WORK/out")" = "$out" ]; then

Check failure on line 16 in scripts/tests/check-copied-lock-step-test.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaE53v3sMVzwizfJ5wmQ&open=AaE53v3sMVzwizfJ5wmQ&pullRequest=1251

Check failure on line 16 in scripts/tests/check-copied-lock-step-test.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaE53v3sMVzwizfJ5wmP&open=AaE53v3sMVzwizfJ5wmP&pullRequest=1251
pass=$((pass+1)); echo " ok $label"
else
fail=$((fail+1)); echo " FAIL $label (wanted exit $want, got $got)"
sed 's/^/ out: /' "$WORK/out"; sed 's/^/ err: /' "$WORK/err"
fi
}
repo() { rm -rf "$WORK/r"; mkdir -p "$WORK/r/.github/workflows"; }

Check warning on line 23 in scripts/tests/check-copied-lock-step-test.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add an explicit return statement at the end of the function.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaE53v3sMVzwizfJ5wmS&open=AaE53v3sMVzwizfJ5wmS&pullRequest=1251
TAB=$'\t'

repo
cat > "$WORK/r/.github/workflows/workflow-linter.yml" <<'YAML'
jobs:
lint:
steps:
- name: Verify immutable action lockfile with the authoritative tool
run: |
curl -fsSL --retry 3 -o "$RUNNER_TEMP/gh-actions-lock" https://github.com/github/gh-actions-lock/releases/download/v0.1.6/linux-amd64
"$RUNNER_TEMP/gh-actions-lock" --rescan --no-fix
YAML
expect 1 "o/r${TAB}.github/workflows/workflow-linter.yml${TAB}v0.1.6" \
"the copied raw-download step is found with its version" "$WORK/r" o/r

cat > "$WORK/r/.github/workflows/regen.yml" <<'YAML'
jobs:
a:
steps:
- run: gh extension install github/gh-actions-lock --pin v0.2.0
- run: gh extension install github/gh-actions-lock
YAML
expect 1 "o/r${TAB}.github/workflows/regen.yml${TAB}unpinned,v0.2.0
o/r${TAB}.github/workflows/workflow-linter.yml${TAB}v0.1.6" \
"a gh extension install is found, pinned and unpinned, one row per workflow" "$WORK/r" o/r

repo
cat > "$WORK/r/.github/workflows/lint.yml" <<'YAML'
{
jobs: {
lint: {
steps: [
{
run: "curl -o x https://github.com/github/gh-actions-lock/releases/download/v0.1.6/linux-amd64\n./x --rescan --no-fix\n",
},
],
},
},
}
YAML
expect 1 "o/r${TAB}.github/workflows/lint.yml${TAB}v0.1.6" "a KYAML run scalar is found" "$WORK/r" o/r

repo
cat > "$WORK/r/.github/workflows/workflow-linter.yml" <<'YAML'
jobs:
lint:
steps:
# The step that stood here downloaded
# https://github.com/github/gh-actions-lock/releases/download/v0.1.6/linux-amd64
# and ran `gh extension install github/gh-actions-lock` -- now governance's.
- run: echo ok
YAML
expect 0 "" "comment lines that name the tool are not a step" "$WORK/r" o/r

repo
printf 'jobs:\n a:\n steps:\n - run: gh extension install github/gh-actions-lock --pin v0.2.0\n' \
> "$WORK/r/.github/workflows/self-test.yml"
expect 0 "" "standards itself is skipped (its installs are the estate pin)" "$WORK/r" hyperpolymath/standards

rm -rf "$WORK/r"; mkdir -p "$WORK/r"
expect 0 "" "a repository without workflows is clean" "$WORK/r" o/r
expect 2 "" "a missing directory is a usage error" "$WORK/nope" o/r
expect 2 "" "no argument is a usage error"

echo
echo "Total: $pass passed, $fail failed"
[ "$fail" -eq 0 ]

Check failure on line 90 in scripts/tests/check-copied-lock-step-test.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaE53v3sMVzwizfJ5wmT&open=AaE53v3sMVzwizfJ5wmT&pullRequest=1251
Loading