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
10 changes: 9 additions & 1 deletion .github/workflows/cargo-audit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ on:
- '**/Cargo.lock'
schedule:
- cron: '0 6 * * 1' # Weekly on Monday
workflow_dispatch: # re-run by hand: after remediating a finding, to turn the
# issue green without waiting for the schedule, and to prove the reconcile
# path itself works
Comment thread
hyperpolymath marked this conversation as resolved.
concurrency:
group: cargo-audit-${{ github.repository }}
cancel-in-progress: false
permissions:
contents: read
jobs:
Expand Down Expand Up @@ -64,4 +70,6 @@ jobs:
--summary "$RUNNER_TEMP/cargo-audit.txt" \
--run "$RUN_URL" \
--event "${{ github.event_name }}" \
--repo "${{ github.repository }}"
--repo "${{ github.repository }}" \
--label security \
--label dependencies
Comment thread
coderabbitai[bot] marked this conversation as resolved.
29 changes: 29 additions & 0 deletions docs/CI-CD-REPAIR.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,35 @@ cannot go unrecorded again:
link:CI-RED-DETERMINATIONS.adoc[CI red-check determinations] and
link:audits/ci-check-ledger.json[the ledger] (triage issue #147).

== A red that is fiction, second mechanism: the push actor

`startup_failure` with zero jobs is usually read as a workflow-file problem. It
has a second cause, measured on this repository on 2026-10-14:

A pull request's head commit was created by the CodeRabbit app (its `Commit
suggestion` flow). GitHub created a run for every workflow on that head and
started none of them — `startup_failure`, no jobs, nothing to read — while the
`.github/workflows` tree at that commit was **byte-identical** to the preceding
head whose eight workflows all ran to success. Re-creating the same tree as a
commit authored by the repository owner started all eight normally.

Evidence: head `626d9df` (`actor=coderabbitai[bot]`, eight runs, all
`startup_failure`, zero jobs) versus head `2c851fa` (same tree, `actor` the owner,
all runs started). `gh run rerun` refuses: "cannot be rerun; its workflow file
may be broken."

So, before chasing YAML: if every workflow on one head failed to start and the
workflow tree is unchanged from a head that worked, check the run's actor

[source,bash]
----
gh api "repos/OWNER/REPO/actions/runs?head_sha=HEAD&per_page=10" \
--jq '.workflow_runs[] | "\(.event) \(.actor.login) \(.conclusion)"'
----

and re-push the identical tree as a commit the repository's own identity
created. The repair is not to the workflows; it is to the head.

== Preventing recurrence

`check-lock-sync.sh` reads YAML through mikefarah/yq v4 and compares structured
Expand Down
25 changes: 18 additions & 7 deletions scripts/reconcile-audit-issue.sh
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ label_args=()
for label in "${LABELS[@]}"; do label_args+=(--label "$label"); done

if [ "$DRY_RUN" -eq 0 ]; then
if ! gh issue list --repo "$REPO" --state open "${label_args[@]}" --limit 100 \
--json number,title,body > "$TMP/open.json" 2> "$TMP/open.err"; then
if ! gh issue list --repo "$REPO" --state open --limit 100 \
--json number,title,body,labels > "$TMP/open.json" 2> "$TMP/open.err"; then
printf 'reconcile-audit-issue: cannot list issues: %s\n' "$(head -1 "$TMP/open.err")" >&2
exit 1
fi
Expand All @@ -104,12 +104,23 @@ fi
number="$(jq -r --arg m "$MARKER" '[.[] | select(.body | contains($m))][0].number // ""' "$TMP/open.json")"
adopted=""
if [ -z "$number" ]; then
# Adopt an issue created by the previous, markerless publisher: same title,
# same labels, no findings. Otherwise the old issue would never close.
# Adopt an issue created by the previous, markerless publisher by its title.
# Labels may have changed or been removed; the old issue must still close.
number="$(jq -r --arg t "$TITLE" '[.[] | select(.title == $t)][0].number // ""' "$TMP/open.json")"
[ -n "$number" ] && adopted=yes
fi

# Repair missing labels on the matched issue in either audit path.
edit_label_args=()
if [ -n "$number" ]; then
for label in "${LABELS[@]}"; do
if ! jq -e --argjson n "$number" --arg l "$label" \
'.[] | select(.number == $n) | any(.labels[]?; .name == $l)' "$TMP/open.json" >/dev/null; then
edit_label_args+=(--add-label "$label")
fi
done
fi

# ------------------------------------------------------------- body assembly
findings=""
if [ -n "$SUMMARY" ] && [ -f "$SUMMARY" ]; then
Expand Down Expand Up @@ -165,7 +176,7 @@ if [ "$STATUS" -eq 0 ]; then
# The body is rewritten as well, so a future failure finds this issue by its
# marker rather than by its title, and the record of why it closed lives in
# the issue rather than only in a comment.
gh_issue edit "$number" --repo "$REPO" --body-file "$TMP/body.md"
gh_issue edit "$number" --repo "$REPO" "${edit_label_args[@]}" --body-file "$TMP/body.md"
gh_issue close "$number" --repo "$REPO" \
--comment "Verified fixed: $RUN_URL — \`cargo audit --deny warnings\` and \`cargo audit --deny unmaintained\` both exited 0 on a run of the default branch."
[ "$FAILED" -eq 0 ] || exit 1
Expand All @@ -186,10 +197,10 @@ else
| sed -n 's/.*<!-- last-run: \(.*\) -->.*/\1/p' | head -1)"
if [ "$last_run" = "$RUN_URL" ]; then
say "audit failed: #$number already records this run; updating the report only"
gh_issue edit "$number" --repo "$REPO" --body-file "$TMP/body.md"
gh_issue edit "$number" --repo "$REPO" "${edit_label_args[@]}" --body-file "$TMP/body.md"
else
say "audit failed: updating #$number with the new findings"
gh_issue edit "$number" --repo "$REPO" --body-file "$TMP/body.md"
gh_issue edit "$number" --repo "$REPO" "${edit_label_args[@]}" --body-file "$TMP/body.md"
gh_issue comment "$number" --repo "$REPO" --body "New failing audit run: $RUN_URL"
fi
fi
Expand Down
73 changes: 69 additions & 4 deletions scripts/tests/reconcile-audit-issue-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,15 @@ cat > "$scratch/bin/gh" <<'STUB'
# copies any --body-file it is handed so the tests can inspect the report.
printf '%s\n' "$*" >> "$GH_STUB_LOG"
if [ "$1 $2" = "issue list" ]; then
cat "$GH_STUB_ISSUES"
issues="$(cat "$GH_STUB_ISSUES")"
prev=""
for arg in "$@"; do
if [ "$prev" = "--label" ]; then
issues="$(jq --arg l "$arg" '[.[] | select(any(.labels[]?; .name == $l))]' <<< "$issues")"
fi
prev="$arg"
done
printf '%s\n' "$issues"
exit 0
fi
prev=""
Expand All @@ -40,8 +48,9 @@ RUN2="https://github.com/hyperpolymath/rpa-elysium/actions/runs/456"
reset_log() { : > "$GH_STUB_LOG"; }

run_reconcile() { # status event
bash "$SCRIPT" --status "$1" --event "$2" --run "$RUN" \
--summary "$scratch/summary.txt" --repo hyperpolymath/rpa-elysium
local status="$1" event="$2"; shift 2
bash "$SCRIPT" --status "$status" --event "$event" --run "$RUN" \
--summary "$scratch/summary.txt" --repo hyperpolymath/rpa-elysium "$@"
}

expect_calls() { # description expected-substring...
Expand Down Expand Up @@ -116,6 +125,8 @@ jq -n --arg m "$MARKER" --arg r "$RUN" \
reset_log
run_reconcile 1 push > /dev/null
expect_calls 'the same failing run does not duplicate the comment' 'issue list' 'issue edit 143'
grep -q -- '--add-label security --add-label dependencies' "$GH_STUB_LOG" || {
echo "FAIL: the same failing run did not restore missing labels"; exit 1; }

# 7. Pull-request events never touch issues.
printf '%s' '[]' > "$GH_STUB_ISSUES"
Expand Down Expand Up @@ -148,4 +159,58 @@ grep -q 'cargo audit --deny warnings' "$WF" || { echo "FAIL: the workflow lost t
grep -q 'exit "\$status"' "$WF" || { echo "FAIL: the audit step no longer propagates its failure"; exit 1; }
echo "PASS: the workflow calls the reconciler and stays blocking"

echo '10 audit-issue reconciliation controls passed.'
# 11. Both audit outcomes find marked and legacy issues regardless of labels,
# adding only missing labels to the same issue without opening a duplicate.
for status in 0 1; do
for match in marked legacy; do
for labels in none security dependencies both; do
jq -n --arg m "$MARKER" --arg r "$RUN2" --arg match "$match" --arg labels "$labels" \
'[{number: 143,
title: (if $match == "legacy" then "Security: Dependency vulnerabilities detected" else "Renamed audit issue" end),
body: (if $match == "marked" then $m + " <!-- last-run: " + $r + " -->" else "Legacy audit report" end),
labels: (if $labels == "both" then [{name: "security"}, {name: "dependencies"}]
elif $labels == "none" then [] else [{name: $labels}] end)}]' > "$GH_STUB_ISSUES"
reset_log
run_reconcile "$status" push > /dev/null
action='issue comment 143'
[ "$status" -ne 0 ] || action='issue close 143'
expect_calls "status=$status $match issue with $labels labels is reconciled" \
'issue list' 'issue edit 143' "$action"
if head -1 "$GH_STUB_LOG" | grep -q -- '--label'; then
echo "FAIL: issue lookup filters by labels"; exit 1
fi
for label in security dependencies; do
if [ "$labels" = both ] || [ "$labels" = "$label" ]; then
if grep -q -- "--add-label $label" "$GH_STUB_LOG"; then
echo "FAIL: an existing $label label was added again"; exit 1
fi
else
grep -q -- "--add-label $label" "$GH_STUB_LOG" || {
echo "FAIL: missing $label label was not restored"; exit 1; }
fi
done
done
done
done

# 12. Explicit labels are restored on the selected marker match, even when a
# legacy title match appears first; unrelated existing labels are kept.
for status in 0 1; do
jq -n --arg m "$MARKER" --arg r "$RUN2" \
'[{number: 113, title: "Security: Dependency vulnerabilities detected", body: "Legacy audit report", labels: []},
{number: 143, title: "Renamed audit issue", body: ($m + " <!-- last-run: " + $r + " -->"),
labels: [{name: "security"}, {name: "dependencies"}]}]' > "$GH_STUB_ISSUES"
reset_log
run_reconcile "$status" push --label security --label triage > /dev/null
action='issue comment 143'
[ "$status" -ne 0 ] || action='issue close 143'
expect_calls "status=$status restores custom labels on the marker match" \
'issue list' 'issue edit 143' "$action"
grep -q -- '--add-label triage' "$GH_STUB_LOG" || {
echo "FAIL: missing custom label was not restored"; exit 1; }
if grep -Eq -- '--add-label (security|dependencies)|--remove-label' "$GH_STUB_LOG"; then
echo "FAIL: labels already on the selected issue were changed"; exit 1
fi
done

echo '12 audit-issue reconciliation controls passed.'
Loading