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
11 changes: 11 additions & 0 deletions .github/bump-callers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ forward automatically instead of silently drifting commits behind.
| [`bump-agents-md-callers.yml`](../workflows/bump-agents-md-callers.yml) | `agents-md-integrity.yml` or `agents-md-integrity/**` | `AGENTS_MD_CALLERS` | empty `[]` (grows as callers land) |
| [`bump-pr-size-callers.yml`](../workflows/bump-pr-size-callers.yml) | `pr-size.yml` or `scripts/check-pr-size/**` | `PR_SIZE_CALLERS` | empty `[]` (grows as callers land) |
| [`bump-assign-reviewers-callers.yml`](../workflows/bump-assign-reviewers-callers.yml) | `assign-reviewers.yml` | `ASSIGN_REVIEWERS_CALLERS` | empty `[]` (grows as callers land) |
| [`bump-groom-callers.yml`](../workflows/bump-groom-callers.yml) | `groom.yml` or `groom/**` | `GROOM_CALLERS` | empty `[]` (grows as callers land) |

They stay as thin entrypoints rather than one matrix because their triggers
differ: a `cursor-review.yml` change must not spuriously bump agents-md or
Expand All @@ -37,6 +38,16 @@ existing one, swap the path filter + `VAR_NAME`/`TAG`/`WORKFLOW_FILE`/
`ALLOW_EMPTY`), seed its variable, and add a row to this table + the paths in
`test-bump-callers.yml`.

The **groom** fleet is the one that most needs this: a groom caller pins the
reusable **twice** — the `uses:` SHA *and* the `workflows_ref:` input that loads
the finder/verifier/builder briefs plus the dedup ledger. Those must stay in
lock-step or a run executes one version's workflow against another version's
briefs. `bump-callers.sh`'s pin rewrite moves both (it matches the `uses:` line
and any bare `workflows_ref:` line), so the fleet cannot drift into that split
state through a hand-bump of only one. It also re-points the `# main @ <short>`
pin comment those callers carry — a comment still naming the old commit after the
pin moved is worse than no comment.

## The caller variables

This repo is **public** — the workflow files and Actions run logs are both
Expand Down
51 changes: 43 additions & 8 deletions .github/bump-callers/bump-callers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,39 @@ bump_repo() {
if (( GW_HAS_SIBLING )); then
SHA_ADDR="/github-workflows[^[:space:]]*${WORKFLOW_FILE//./\\.}|workflows_ref/"
fi
NEW_CONTENT=$(sed -E "${SHA_ADDR} s/[0-9a-f]{40}/${NEW_SHA}/g" <<<"$OLD_CONTENT")
#
# The groom callers' `# main @ <short>` annotation rides on the pin line
# itself, so it moves HERE, under the SAME `${SHA_ADDR}` — the address is the
# exact set of lines whose pin this rule rewrites, which makes it honest
# attribution for that form (the `main (<short>)` form below cannot use it: it
# also appears in prose ABOVE the `uses:` line, naming no pin context of its
# own, so it needs the caller-level guard instead). The two MUST stay
# identical: a groom caller pins TWICE and its `workflows_ref: <sha> # main @
# <short>` line is rewritten by the 40-hex rule, so a narrower anchor here
# would bump that pin while leaving its comment naming the old commit —
# reintroducing, on the second of the two groom pins, exactly the confident lie
# these rewrites exist to kill. Sharing the address also means the sibling
# tightening covers this form for free: a sibling fleet's `uses: …/other.yml@…
# # main @ <short>` line is outside the tightened address, so neither its pin
# nor its annotation is stamped with our SHA.
#
# The short form is bounded to {7,12} hex so a deliberate FULL-sha annotation
# keeps its full form (the 40-hex rule has already corrected it in the same
# pass) instead of being shortened — and that bound only holds if the hex run
# ENDS there, hence two rules rather than one: `[0-9a-f]{7,12}` alone is happy
# to match the first 12 characters of a longer run, so on a `# main @ <40hex>`
# comment it would swap 12 hex for the 7-char SHORT and strand the remaining 28
# — mangling the very full-form comment the bound exists to protect. Requiring
# a non-hex character (or end of line) after the run makes the match a whole
# token: rule 2 catches the short form mid-line, rule 3 the same at EOL, and a
# 13+ hex run matches neither and is left intact. A hex-boundary assertion
# (`\b`, `[[:>:]]`) would be simpler but spells differently in GNU and BSD sed;
# this is portable ERE.
NEW_CONTENT=$(sed -E "
${SHA_ADDR} s/[0-9a-f]{40}/${NEW_SHA}/g
${SHA_ADDR} s|# main @ [0-9a-f]{7,12}([^0-9a-f])|# main @ ${SHORT}\1|g
${SHA_ADDR} s|# main @ [0-9a-f]{7,12}\$|# main @ ${SHORT}|g
" <<<"$OLD_CONTENT")

# Normalize the human-readable pin annotation so it never disagrees with the
# pin above. Two spellings, both handled here:
Expand All @@ -254,13 +286,16 @@ bump_repo() {
# clean-skip path still skips). A caller using some other comment form
# (agents-md-integrity's `# v1`) matches neither rule and is untouched.
#
# Attribution guard: an annotation names a SHA but not WHICH reusable it
# annotates, so in a file that calls several github-workflows reusables there
# is no honest way to tell whose is whose — rewriting them all would stamp
# this fleet's SHA onto a sibling fleet's annotation. So rewrite only when the
# file is provably ours alone; otherwise leave every annotation as found and
# say so. (No caller does this today; the guard is why the new rule cannot
# regress one that starts to.)
# Attribution guard: an annotation of these two forms names a SHA but not
# WHICH reusable it annotates — and, appearing in prose above the pin as often
# as beside it, it carries no line context to attribute it by either. So in a
# file that calls several github-workflows reusables there is no honest way to
# tell whose is whose, and rewriting them all would stamp this fleet's SHA
# onto a sibling fleet's annotation. Rewrite only when the file is provably
# ours alone; otherwise leave every annotation as found and say so. (No caller
# does this today; the guard is why the new rule cannot regress one that starts
# to.) The `# main @` form needs no guard here — it is bound to the pin line it
# annotates and moved above, under the same address as the pin itself.
if (( GW_ONLY_OURS )); then
NEW_CONTENT=$(sed -E "s|# github-workflows#[0-9]+|# github-workflows main (${SHORT})|g; s|github-workflows main \([0-9a-f]{7,40}\)|github-workflows main (${SHORT})|g" <<<"$NEW_CONTENT")
elif grep -qE '# github-workflows#[0-9]+|github-workflows main \([0-9a-f]{7,40}\)' <<<"$OLD_CONTENT"; then
Expand Down
78 changes: 77 additions & 1 deletion .github/bump-callers/tests/test_bump_callers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,64 @@ check "base_tree is NOT the commit sha" "! grep -qF '1234567890abcdef12345
check "opened a new PR (pr create called)" "grep -q '^pr-create' \"\$STUB_PUT_DIR/pr.log\""
check "did not edit (no open PR existed)" "! grep -q '^pr-edit' \"\$STUB_PUT_DIR/pr.log\""

echo "== groom fleet: BOTH pins (uses: + workflows_ref) and the '# main @' comment move together =="
# A groom caller pins the reusable TWICE — the `uses:` SHA and the `workflows_ref:`
# input that loads the finder/verifier briefs + dedup ledger. They must move in
# lock-step or a run executes one version's workflow against another version's
# briefs. The fixture also carries an `actions/checkout@<40hex>` pin, which must
# NOT be clobbered to github-workflows' SHA.
new_case groom
GROOM_FIXTURE="${WORK}/groom_caller.yml"
printf '%s\n' \
'name: Groom' \
'jobs:' \
' groom:' \
' steps:' \
' - uses: actions/checkout@abcdefabcdefabcdefabcdefabcdefabcdefabcd # v6' \
' uses: Comfy-Org/github-workflows/.github/workflows/groom.yml@1111111111111111111111111111111111111111 # main @ 1111111 — groom.yml not on the v1 tag yet' \
' with:' \
' workflows_ref: 1111111111111111111111111111111111111111 # main @ 1111111' \
" max_prs: \${{ github.event.inputs.max_prs || '1' }}" \
' # github-workflows pin note, short form, hex run ends at EOL: # main @ 1111111' \
' # github-workflows pin note, deliberate FULL sha: # main @ 1111111111111111111111111111111111111111' \
' # unrelated third-party note, neither anchor on the line: # main @ 2222222' \
> "$GROOM_FIXTURE"
STUB_CONTENT_FILE="$GROOM_FIXTURE" run_bump \
VAR_NAME=GROOM_CALLERS TAG=groom WORKFLOW_FILE=groom.yml \
CALLERS_JSON='[{"repo":"Comfy-Org/secret-groomed","file":".github/workflows/groom.yml","label":""}]'
check "exit 0" "[[ $RC -eq 0 ]]"
check "masked the private repo name" "grep -q '::add-mask::Comfy-Org/secret-groomed' <<<\"\$OUT\""
check "reported groom fleet complete" "grep -q 'groom bump complete' <<<\"\$OUT\""
PUT="${STUB_PUT_DIR}/put.last.txt"
check "uses: pin moved" "grep -qE \"groom.yml@${NEW_SHA}\" \"$PUT\""
check "workflows_ref pin moved" "grep -qE \"workflows_ref: ${NEW_SHA}\" \"$PUT\""
check "no stale 40-hex pin anywhere" "! grep -qF '1111111111111111111111111111111111111111' \"$PUT\""
check "'# main @' comment moved to new short" "grep -qF '# main @ $SHORT' \"$PUT\""
# The `# main @` rewrite is bounded to a 7-12 hex SHORT sha, and that bound has to
# hold at BOTH ends of the run or it mangles what it claims to protect.
check "'# main @ <short>' at EOL rewritten" "grep -qE '# main @ ${SHORT}\$' \"$PUT\""
# A deliberate FULL-sha comment is corrected by the 40-hex rule and must then be
# left alone — an unbounded {7,12} match would eat its first 12 characters, swap in
# the 7-char SHORT and strand the other 28 as a nonsense suffix.
check "full-sha '# main @' keeps full form" "grep -qF '# main @ ${NEW_SHA}' \"$PUT\""
# The `workflows_ref:` pin is bumped by the 40-hex rule, so its OWN `# main @
# <short>` note has to move with it — the comment rules are anchored to the same
# two pin contexts as that rule for exactly this line. A narrower anchor bumps the
# pin and leaves the comment naming the old commit: a confident lie on the second
# of groom's two pins.
check "workflows_ref's own pin comment moved" \
"grep -qE \"workflows_ref: ${NEW_SHA} +# main @ ${SHORT}\$\" \"$PUT\""
# ...and the anchor still BOUNDS the rewrite: a `# main @ <short>` note on a line
# naming neither pin context belongs to some other pin and must be left alone.
check "unanchored '# main @' note untouched" "grep -qF '# main @ 2222222' \"$PUT\""
# The third-party action pin is a full 40-hex SHA on a line that does NOT mention
# github-workflows — the address anchor is what keeps it intact (the org mandates
# SHA-pinning every action, so clobbering it would break the caller's CI).
check "actions/checkout pin untouched" "grep -qF 'actions/checkout@abcdefabcdefabcdefabcdefabcdefabcdefabcd' \"$PUT\""
# max_prs forwards a workflow_dispatch string (see groom.yml's input docs); the
# bumper must not mangle the expression while rewriting the pins around it.
check "max_prs forward expression intact" "grep -qF \"github.event.inputs.max_prs || '1'\" \"$PUT\""

echo "== cursor-review fleet: an open bump PR is UPDATED IN PLACE, not re-opened (BE-3882) =="
new_case reuse
STUB_CONTENT_FILE="$CR_FIXTURE" run_bump \
Expand Down Expand Up @@ -548,7 +606,9 @@ printf '%s\n' \
' review:' \
' uses: Comfy-Org/github-workflows/.github/workflows/cursor-review.yml@1111111111111111111111111111111111111111 # github-workflows main (1111111)' \
' auto-label:' \
' uses: Comfy-Org/github-workflows/.github/workflows/cursor-review-auto-label.yml@2222222222222222222222222222222222222222 # github-workflows#27' \
' uses: Comfy-Org/github-workflows/.github/workflows/cursor-review-auto-label.yml@2222222222222222222222222222222222222222 # github-workflows#27 # main @ 2222222' \
' with:' \
' workflows_ref: 1111111111111111111111111111111111111111 # main @ 1111111' \
> "$MULTI_FIXTURE"
STUB_CONTENT_FILE="$MULTI_FIXTURE" run_bump \
VAR_NAME=CURSOR_REVIEW_CALLERS TAG=cursor-review WORKFLOW_FILE=cursor-review.yml \
Expand All @@ -561,6 +621,22 @@ check "the SIBLING's 40-hex pin is UNCHANGED" "grep -qF 'cursor-review-auto-l
check "the sibling's legacy marker is untouched" "grep -qF '# github-workflows#27' \"$PUT\""
check "our own marker left alone (ambiguous)" "grep -qF 'github-workflows main (1111111)' \"$PUT\""
check "this fleet's short SHA was NOT stamped in" "! grep -qF 'github-workflows main ($SHORT)' \"$PUT\""
# The `# main @ <short>` form is attributed differently from the two markers above:
# it rides ON the pin line, so it moves with whatever the 40-hex rule moves — same
# address, both regimes. That makes the multi-reusable case a two-sided assertion:
# * the SIBLING's `# main @` note shares its line with a pin the tightened address
# deliberately does NOT bump, so stamping the note would be the same cross-fleet
# lie as stamping the pin, and
# * a `workflows_ref:` pin IS bumped even here (that context stays broad — an
# un-bumped workflows_ref disagreeing with its own `uses:` is the worse
# failure), so its own note MUST move with it or the guard manufactures exactly
# the stale comment BE-4346 removed.
# Unreachable today (no caller calls two reusables); asserted so the guard and the
# groom fleet's lock-step cannot silently trade one for the other.
check "sibling's '# main @' note NOT stamped" "grep -qF '# main @ 2222222' \"$PUT\""
check "workflows_ref pin bumped even here" "grep -qE \"workflows_ref: ${NEW_SHA}\" \"$PUT\""
check "workflows_ref's '# main @' moved with it" \
"grep -qE \"workflows_ref: ${NEW_SHA} +# main @ ${SHORT}\\\$\" \"$PUT\""

echo "== merely NAMING a sibling workflow in a comment is not a second caller (BE-4523) =="
# The multi-reusable check reads `uses:` lines only. A single-reusable caller that
Expand Down
Loading