Skip to content
Open
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
28 changes: 11 additions & 17 deletions .github/workflows/governance-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1115,25 +1115,20 @@ jobs:
# governance jobs on every PR estate-wide. github.sha resolves to the
# same merge commit but is always fetchable.
ref: ${{ github.sha }}
- name: Checkout standards for the duplicate-key check
- name: Checkout the pinned Standards policy helpers
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: hyperpolymath/standards
ref: main
# Execute helper code from the same immutable Standards revision as
# this reusable workflow. A moving `main` checkout would let a caller
# pinned to workflow revision X execute implementation revision Y.
ref: ${{ job.workflow_sha }}
path: .standards-dupkey
sparse-checkout: |
scripts/check-workflow-duplicate-keys.sh
scripts/update-actions-lock.sh
tools/policy/check-workflows-parse.sh
sparse-checkout-cone-mode: false
# ⚠ Not fatal if the file is absent. This checkout is pinned to
# standards@main, so during a rename of the script the fetch finds
# nothing — the new name does not exist on main until the renaming pull
# request merges. Without this, the RENAME ITSELF fails the linter, on
# the one pull request that cannot possibly be at fault. See the
# fallback in the next step.
continue-on-error: true

- name: Parse every tracked workflow
run: |
if [ "$GITHUB_REPOSITORY" = hyperpolymath/standards ]; then
Expand All @@ -1143,7 +1138,7 @@ jobs:
SCRIPT=".standards-dupkey/tools/policy/check-workflows-parse.sh"
fi
if [ ! -f "$SCRIPT" ]; then
echo "::error::workflow parser gate not found in standards@main or locally"
echo "::error::workflow parser gate not found in the pinned Standards revision or locally"
exit 1
fi
bash "$SCRIPT"
Expand All @@ -1158,10 +1153,9 @@ jobs:
# were dead this way, including a CodeQL workflow with zero
# successful runs in its entire lifetime.
set -euo pipefail
# Standards must exercise its pull-request scripts; every consumer
# must use the canonical scripts fetched from standards@main. A
# file-existence fallback is insufficient because the old script on
# main still exists while its replacement is being tested.
# Standards exercises its pull-request scripts; every consumer uses
# the canonical scripts fetched from this workflow's immutable
# Standards revision.
if [ "$GITHUB_REPOSITORY" = hyperpolymath/standards ]; then
SCRIPT="scripts/check-workflow-duplicate-keys.sh"
echo "Using this repository's own copy (standards self-lint)."
Expand All @@ -1170,7 +1164,7 @@ jobs:
fi
if [ ! -f "$SCRIPT" ]; then
echo "::error::duplicate-key checker not found — neither fetched from" \
"standards@main nor present locally."
"the pinned Standards revision nor present locally."
exit 1
fi
cp "$SCRIPT" "$RUNNER_TEMP/dupkeys.sh"
Expand All @@ -1185,7 +1179,7 @@ jobs:
fi
if [ ! -f "$LOCK_SCRIPT" ]; then
echo "::error::actions-lock verifier not found — neither fetched from" \
"standards@main nor present locally."
"the pinned Standards revision nor present locally."
exit 1
fi
cp "$LOCK_SCRIPT" "$RUNNER_TEMP/update-actions-lock.sh"
Expand Down
38 changes: 1 addition & 37 deletions .github/workflows/self-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,40 +54,4 @@ jobs:
python3 -m pip install --user --quiet pyyaml

- name: Run tests/*.sh and scripts/tests/*.sh
run: |
set -uo pipefail

mapfile -t TESTS < <(
{
find tests -maxdepth 1 -name '*.sh' -type f
find scripts/tests -maxdepth 1 -name '*.sh' -type f
} | sort
)

# Fail closed. If the suite is empty the discovery is broken, and a
# green tick here would assert something untrue.
if [ ${#TESTS[@]} -eq 0 ]; then
echo "::error::No tests found under tests/ or scripts/tests/ — discovery is broken."
exit 1
fi
echo "Discovered ${#TESTS[@]} test file(s)."

failed=0
for t in "${TESTS[@]}"; do
echo "::group::$t"
if bash "$t"; then
echo "PASS $t"
else
rc=$?
echo "::error file=$t::$t failed (exit $rc)"
failed=$((failed+1))
fi
echo "::endgroup::"
done

echo
if [ "$failed" -gt 0 ]; then
echo "::error::$failed of ${#TESTS[@]} test file(s) failed."
exit 1
fi
echo "All ${#TESTS[@]} test file(s) passed."
run: bash scripts/run-shell-test-suite.sh
2 changes: 1 addition & 1 deletion Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ build:

# Run all sub-project test suites
test:
# K9 Rust binding tests now live in hyperpolymath/k9-ecosystem (rs/) — see #491
@bash scripts/run-shell-test-suite.sh

# Regression test: test aggregation fails for missing prerequisites and test failures.
test-runner-test:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: MPL-2.0
# Allowlist Preflight — surfaces an Actions-allowlist gap as an ORDINARY red
# check instead of an opaque, estate-wide `startup_failure` blackout.
# Allowlist Preflight — thin, reproducible caller of the canonical focused
# Standards implementation. It surfaces an Actions-allowlist gap as an
# ordinary red check instead of an opaque `startup_failure` blackout.
#
# Under `allowed_actions: selected`, a workflow that `uses:` an un-allowlisted
# action/reusable dies at STARTUP with no job and no log. This job uses ONLY
Expand All @@ -27,14 +28,4 @@ concurrency:
jobs:
allowlist:
name: Actions allowlist coverage
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

- name: Every workflow `uses:` must be on the canonical allowlist
run: |
# Fetch the estate detector; it fetches the canonical allowlist itself.
curl -fsSL https://raw.githubusercontent.com/hyperpolymath/standards/main/scripts/check-allowed-actions.sh -o "${RUNNER_TEMP}/check-allowed-actions.sh"
bash "${RUNNER_TEMP}/check-allowed-actions.sh"
uses: hyperpolymath/standards/.github/workflows/allowlist-preflight-reusable.yml@f8cc276bb0add12debc9eddce79e2a842bac5d3e
41 changes: 41 additions & 0 deletions scripts/run-shell-test-suite.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: MPL-2.0
# Canonical fail-closed shell test discovery used by CI and `just test`.
set -uo pipefail

mapfile -t TESTS < <(
{
find tests -maxdepth 1 -name '*.sh' -type f
find scripts/tests -maxdepth 1 -name '*.sh' -type f
} | sort
)

if [ "${#TESTS[@]}" -eq 0 ]; then

Check failure on line 13 in scripts/run-shell-test-suite.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=AaBZdiU9tTCy1S7t087H&open=AaBZdiU9tTCy1S7t087H&pullRequest=697
echo "ERROR: no tests found under tests/ or scripts/tests/ — discovery is broken." >&2
exit 1
fi

echo "Discovered ${#TESTS[@]} test file(s)."
failed=0

for test_file in "${TESTS[@]}"; do
echo "::group::$test_file"

if bash "$test_file"; then
echo "PASS $test_file"
else
status=$?
echo "::error file=$test_file::$test_file failed (exit $status)"

Check warning on line 28 in scripts/run-shell-test-suite.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Redirect this error message to stderr (>&2).

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaBZdiU9tTCy1S7t087I&open=AaBZdiU9tTCy1S7t087I&pullRequest=697
failed=$((failed + 1))
fi

echo "::endgroup::"
done

echo
if [ "$failed" -gt 0 ]; then

Check failure on line 36 in scripts/run-shell-test-suite.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=AaBZdiU9tTCy1S7t087J&open=AaBZdiU9tTCy1S7t087J&pullRequest=697
echo "ERROR: $failed of ${#TESTS[@]} test file(s) failed." >&2
exit 1
fi

echo "All ${#TESTS[@]} test file(s) passed."
14 changes: 7 additions & 7 deletions scripts/tests/actions-lock-update-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,9 @@ cat > "$WORK/bin/fake-gh" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
if [ "${2:-}" = "--verify-local" ]; then
grep -q "actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1" \
grep -q "actions/checkout@v7.0.1" \
.github/workflows/actions.lock
sed -i 's#uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1#uses: actions/checkout@v7.0.1#' \
.github/workflows/ci.yml
sed -i '1i# verifier-only mutation' .github/workflows/ci.yml
case "${FAKE_VERIFY_FINDING:-}" in
reusable-exact)
printf '%s\n' '{"valid":false,"findings":[{"workflow":".github/workflows/reusable.yml","category":"stale","dependency":"hyperpolymath/standards@abc123"}]}'
Expand Down Expand Up @@ -60,7 +59,8 @@ if [ "${2:-}" = "--verify-local" ]; then
;;
esac
fi
sed -i '1i# This workflow is managed by gh actions-lock.' .github/workflows/ci.yml
grep -q '^# This workflow is managed by gh actions-lock.$' .github/workflows/ci.yml ||
sed -i '1i# This workflow is managed by gh actions-lock.' .github/workflows/ci.yml
sed -i 's#actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1#actions/checkout@v7.0.1#' \
.github/workflows/ci.yml
cat > .github/workflows/actions.lock <<'LOCK'
Expand All @@ -81,10 +81,10 @@ chmod +x "$WORK/bin/fake-gh"
cd "$WORK"
GH_BIN="$WORK/bin/fake-gh" bash "$UPDATE" .github/workflows >/dev/null

grep -q 'uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' \
grep -q 'uses: actions/checkout@v7.0.1' \
.github/workflows/ci.yml
[ "$(grep -c '^# This workflow is managed by gh actions-lock.$' .github/workflows/ci.yml)" -eq 1 ]
grep -q "'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1':" \
grep -q "'actions/checkout@v7.0.1':" \
.github/workflows/actions.lock

cp .github/workflows/ci.yml "$WORK/ci.before"
Expand All @@ -93,7 +93,7 @@ GH_BIN="$WORK/bin/fake-gh" bash "$UPDATE" .github/workflows >/dev/null
cmp -s "$WORK/ci.before" .github/workflows/ci.yml
cmp -s "$WORK/lock.before" .github/workflows/actions.lock

echo "PASS: Actions lock refresh preserves inline SHA source and is idempotent"
echo "PASS: Actions lock refresh keeps authoritative symbolic source and is idempotent"

# Verification is mutating in released gh-actions-lock versions too. The safe
# verification mode must restore workflow bytes while leaving the lock intact.
Expand Down
59 changes: 59 additions & 0 deletions scripts/tests/governance-reusable-contract-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: MPL-2.0
# Static regression contract for the reusable governance boundaries.
set -euo pipefail

ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
GOVERNANCE="$ROOT/.github/workflows/governance-reusable.yml"
FOCUSED="$ROOT/.github/workflows/allowlist-preflight-reusable.yml"
RSR_SEED="$ROOT/rhodium-standard-repositories/.github/workflows/allowlist-preflight.yml"
LOCK_HELPER="$ROOT/scripts/update-actions-lock.sh"

fail() {
echo "FAIL: $*" >&2
exit 1
}

helper_checkout="$(grep -F -A 18 -- '- name: Checkout the pinned Standards policy helpers' "$GOVERNANCE")"
# GitHub expression is an asserted literal.
# shellcheck disable=SC2016
printf '%s\n' "$helper_checkout" | grep -Fq 'ref: ${{ job.workflow_sha }}' ||
fail "governance helpers are not fetched from job.workflow_sha"
if printf '%s\n' "$helper_checkout" | grep -Eq '^[[:space:]]*ref:[[:space:]]*main[[:space:]]*$'; then
fail "governance helper execution still follows moving main"
fi

if grep -Fq 'bash scripts/update-actions-lock.sh --verify-local' "$GOVERNANCE"; then
fail "reusable governance still assumes a consumer-local Standards helper"
fi
# RUNNER_TEMP is an asserted workflow literal.
# shellcheck disable=SC2016
grep -Fq 'bash "$RUNNER_TEMP/update-actions-lock.sh" --verify-local' "$GOVERNANCE" ||
fail "reusable governance does not execute the staged pinned lock verifier"

for workflow in "$GOVERNANCE" "$FOCUSED"; do
grep -Fq 'Live Actions policy (credentialed advisory)' "$workflow" ||
fail "$workflow lacks the distinct live-policy advisory job"
grep -Fq "if: \${{ env.GH_TOKEN == '' }}" "$workflow" ||
fail "$workflow does not report a missing optional credential"
grep -Fq "if: \${{ env.GH_TOKEN != '' }}" "$workflow" ||
fail "$workflow can run the live API without a credential guard"
# rc is an asserted workflow-script literal.
# shellcheck disable=SC2016
grep -Fq 'if [ "$rc" -eq 3 ]; then' "$workflow" ||
fail "$workflow conflates API unavailability with a policy verdict"
done

grep -Eq 'uses: hyperpolymath/standards/.github/workflows/allowlist-preflight-reusable.yml@[0-9a-f]{40}$' \
"$RSR_SEED" || fail "RSR allowlist seed is not a thin immutable reusable-workflow caller"
if grep -Eq 'raw\.githubusercontent\.com/.*/main|curl[[:space:]]' "$RSR_SEED"; then
fail "RSR allowlist seed still downloads executable policy from moving main"
fi

grep -Fq 'when actions.lock is present, gh actions-lock is authoritative' "$LOCK_HELPER" ||
fail "lock helper does not state the authoritative-lock contract"
if grep -Fq 'relock-sha-keys.sh' "$LOCK_HELPER"; then
fail "lock helper still rewrites generated lock keys around legacy inline SHAs"
fi

echo "PASS: reusable governance uses one pinned implementation with focused and suite entry points"
57 changes: 33 additions & 24 deletions scripts/update-actions-lock.sh
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: MPL-2.0
#
# Refresh actions.lock without letting gh actions-lock rewrite the canonical
# workflow source from inline SHA pins to tag references.
# Refresh actions.lock under one unambiguous policy boundary:
#
# - when actions.lock is present, gh actions-lock is authoritative for direct
# and transitive commit resolution, so its symbolic workflow refs are kept;
# - when actions.lock is absent, the separate workflow gate requires inline
# immutable SHAs.
#
# Verification is treated as observational even though released tool versions
# can rewrite workflow bytes. Updates keep tool-authored workflow changes, but
# any failed update rolls both workflows and the lock back atomically.
set -euo pipefail

MODE=update
Expand All @@ -19,8 +27,20 @@ GH_BIN="${GH_BIN:-gh}"
SNAPSHOT="$(mktemp -d)"
COMPLETE=false

snapshot_workflows() {
destination=$1
mkdir -p "$destination"

find "$WF_DIR" -maxdepth 1 -type f \( -name '*.yml' -o -name '*.yaml' \) -print0 |
while IFS= read -r -d '' file; do
cp "$file" "$destination/$(basename "$file")"
done
}

restore_workflows() {
find "$SNAPSHOT/workflows" -maxdepth 1 -type f -print0 2>/dev/null |
source=$1

find "$source" -maxdepth 1 -type f -print0 2>/dev/null |
while IFS= read -r -d '' file; do
cp "$file" "$WF_DIR/$(basename "$file")"
done
Expand Down Expand Up @@ -73,10 +93,9 @@ verify_lock_coverage() {
fi
# The authoritative validity bit and the process exit are not equivalent.
# gh-actions-lock v0.1.6 exits 1 for advisory findings such as `sha-as-ref`
# even while reporting `"valid": true`. Treating that advisory exit as an
# invalid lock makes every inline-SHA repository permanently red. Preserve
# the findings in the log, but accept the lock exactly when the tool says it
# is valid.
# even while reporting `"valid": true`. Preserve advisory findings in the
# log, but accept the lock exactly when the authoritative tool says it is
# valid.
if printf '%s' "$result" | jq -e '.valid == true' >/dev/null; then
if printf '%s' "$result" | jq -e '.findings | length > 0' >/dev/null; then
printf '%s\n' "$result"
Expand Down Expand Up @@ -109,7 +128,7 @@ verify_lock_coverage() {
cleanup() {
status=$?
if [ "$COMPLETE" != true ]; then
restore_workflows
restore_workflows "$SNAPSHOT/original-workflows"
if [ -f "$SNAPSHOT/actions.lock" ]; then
cp "$SNAPSHOT/actions.lock" "$WF_DIR/actions.lock"
else
Expand All @@ -121,27 +140,17 @@ cleanup() {
}
trap cleanup EXIT

mkdir -p "$SNAPSHOT/workflows"
snapshot_workflows "$SNAPSHOT/original-workflows"
[ ! -f "$WF_DIR/actions.lock" ] || cp "$WF_DIR/actions.lock" "$SNAPSHOT/actions.lock"

# gh actions-lock currently inserts its managed marker and rewrites direct SHA
# refs to their release tags. Those edits conflict with the estate's inline-SHA
# gate. Preserve every authored workflow while allowing the generated lockfile
# to change.
find "$WF_DIR" -maxdepth 1 -type f \( -name '*.yml' -o -name '*.yaml' \) -print0 |
while IFS= read -r -d '' file; do
cp "$file" "$SNAPSHOT/workflows/$(basename "$file")"
done

if [ "$MODE" = update ]; then
"$GH_BIN" actions-lock
restore_workflows
bash "$(dirname "$0")/relock-sha-keys.sh" "$WF_DIR"
fi

# Despite its name, --verify-local can migrate local `./` action paths to an
# invalid `$/` spelling. Treat verification as mutating and restore authored
# workflow bytes afterward too.
# Despite its name, --verify-local can mutate workflow bytes (including
# migrating local `./` action paths to an invalid `$/` spelling). Preserve the
# post-update state, run verification, then discard verifier-only mutations.
snapshot_workflows "$SNAPSHOT/preverify-workflows"
verify_lock_coverage
restore_workflows
restore_workflows "$SNAPSHOT/preverify-workflows"
COMPLETE=true
Loading