diff --git a/.github/workflows/actions.lock b/.github/workflows/actions.lock index 779b73296..a28bbd2e2 100644 --- a/.github/workflows/actions.lock +++ b/.github/workflows/actions.lock @@ -215,6 +215,13 @@ dependencies: repo_id: 496012378 uses: - 'actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f' + 'asana/push-signed-commits@d615ca88d8e1a946734c24970d1e7a6c56f34897': + ref: 'v1.3' + commit: 'sha1-d615ca88d8e1a946734c24970d1e7a6c56f34897' + owner_id: 1472111 + repo_id: 772313726 + uses: + - 'actions/setup-python@v2' 'dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067': ref: 'v1' commit: 'sha1-7e38f4b43b4db5c8dd498af069a4f6196df1d067' diff --git a/.github/workflows/governance-reusable.yml b/.github/workflows/governance-reusable.yml index 6150c2b2a..b02ce9792 100644 --- a/.github/workflows/governance-reusable.yml +++ b/.github/workflows/governance-reusable.yml @@ -977,7 +977,11 @@ jobs: if [ -n "$canon" ] && [ "$f_" = "$canon" ]; then continue; fi [ -f "$f_" ] || continue for pat in "${pats[@]}"; do - out=$(grep -nE -e "$pat" -- "$f_" 2>"$err"); rc=$? + # The step runs under `bash -e`: a bare `out=$(grep …)` that matches nothing + # returns 1 and kills the step silently before rc is read. `|| rc=$?` keeps + # the status without tripping -e. + rc=0 + out=$(grep -nE -e "$pat" -- "$f_" 2>"$err") || rc=$? if [ "$rc" -eq 0 ]; then while IFS= read -r line; do echo "::error file=$f_,line=${line%%:*}::[R5:$rid] ${line#*:} → route to ${canon:-drop or move to a canonical source}" diff --git a/tests/test_governance_r5_bash_e.sh b/tests/test_governance_r5_bash_e.sh new file mode 100755 index 000000000..63baf2b05 --- /dev/null +++ b/tests/test_governance_r5_bash_e.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +# +# test_governance_r5_bash_e.sh — runs the R5 canonical-reference drift step of +# .github/workflows/governance-reusable.yml exactly as a runner does: the step's +# `run:` text, extracted with yq, under `bash -e` (a step with no `shell:` runs +# as `bash -e {0}` on ubuntu runners). +# +# Regression: under `-e`, `out=$(grep …)` on an include file with no match +# returned 1 and aborted the step before `rc=$?` — exit 1, no output, no +# annotation. echidna#410 went red on clean content. The planted-hit cases are +# the positive control: a real drift must still fail and name the line. +# +# Run: bash tests/test_governance_r5_bash_e.sh +set -uo pipefail +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +F="$ROOT/.github/workflows/governance-reusable.yml" +STEP='Canonical-reference drift (R5 generic)' +WORK="$(mktemp -d)" +trap 'rm -rf "$WORK"' EXIT +pass=0; fail=0 + +# Print the R5 step's run: text; fail the suite if the step cannot be found. +extract_step() { + yq -r ".jobs.\"security-policy\".steps[] | select(.name == \"$STEP\") | .run" "$F" +} + +# Build a fixture repo in $1 with one rule over the given include files. +# Remaining args are "path:content" pairs written into the fixture. +make_fixture() { + local dir="$1"; shift + mkdir -p "$dir/.github/canonical-references" + cat > "$dir/.github/canonical-references/drift.yml" <<'RULE' +id: test-drift +description: planted drift marker +canonical_pointer: CANON.md +patterns: + - "DRIFT_MARKER_[0-9]+" +scope: + include: [a.md, b.md] +RULE + local pair + for pair in "$@"; do printf '%s\n' "${pair#*:}" > "$dir/${pair%%:*}"; done +} + +# assert