Skip to content

fix(supply-chain): route promote-core lock update through an auto-merge PR#238

Merged
williaby merged 4 commits into
mainfrom
fix/promote-core-lock-pr-automerge
Jul 1, 2026
Merged

fix(supply-chain): route promote-core lock update through an auto-merge PR#238
williaby merged 4 commits into
mainfrom
fix/promote-core-lock-pr-automerge

Conversation

@williaby

@williaby williaby commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

Problem

promote-core's update-lock job pushes the approved-lock entry directly to
main
. On a protected main this is rejected with GH013 on four counts at
once (observed in container-images run 28477911147):

- Changes must be made through a pull request.
- Changes must be made through the merge queue
- Required status check "Validate catalog schema" is expected.
- Commits must have verified signatures.  (the bot commit is unsigned)

So even when publish + cosign sign + CycloneDX attest + SLSA provenance all
succeed, the lock entry never lands. RT-10's push-to-main design is
incompatible with a protected main branch.

Fix

Commit the lock entry to a per-run branch, open a PR, and enable
squash auto-merge. The merge queue lands it web-flow-signed (satisfying the
signature rule) after the Validate catalog schema check runs on the PR. This
preserves RT-10 (no [skip ci]; the validator still gates the change) while
satisfying every main-branch rule, with no bypass actor.

Also in this change

  • pull-requests: write added to the job (needed to open + auto-merge the PR).
  • image_id slug guard: it now reaches a git ref name, so it is constrained to
    ^[a-zA-Z0-9][a-zA-Z0-9._-]*$ before any git use.
  • Branch name keyed on run_id + run_attempt so a re-run never collides with
    a prior attempt's branch/PR.
  • Auto-merge enable is best-effort: if the repo setting is off, the PR is left
    open for manual merge rather than failing the promotion.
  • Dropped the push-to-main rebase-retry loop (no longer racing main; the merge
    queue serializes and a real approved-lock conflict surfaces on the PR).

Rollout

Callers pin promote-core by SHA, so after merge the container-images
supply-chain-mirror.yml caller must be re-pinned to this commit's SHA before
the bake exercises the new path.

Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com

Summary by CodeRabbit

  • New Features

    • Lock file updates now go through a pull request instead of being pushed directly, with auto-merge enabled when possible.
    • Added safer validation checks for update inputs to help prevent invalid lock update runs.
  • Bug Fixes

    • Improved reliability of lock update handling by skipping empty updates and keeping failed auto-merge requests open for review.

…ge PR

promote-core's update-lock job pushed the approved-lock entry straight to
main. On a protected main (PR + merge-queue + required catalog-schema check +
verified-signatures rules) that bot push is rejected with GH013 on four
counts, so the lock entry never lands even though publish/sign/attest succeed.

Commit the lock entry to a per-run branch instead, open a PR, and enable
squash auto-merge. The merge queue lands it web-flow-signed after the schema
check runs on the PR, preserving RT-10 (no [skip ci]; the validator still
gates the change) while satisfying every main-branch rule. Adds
pull-requests: write, an image_id slug guard (it reaches a git ref name), a
run-attempt-unique branch to survive re-runs, and a best-effort auto-merge
enable that leaves the PR open for manual merge rather than failing the
promotion.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@williaby, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 49 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 290e3b73-2066-4ef4-818a-e4b591c34412

📥 Commits

Reviewing files that changed from the base of the PR and between a00c4e1 and 010d2a5.

📒 Files selected for processing (1)
  • .github/workflows/supply-chain-promote-core.yml
📝 Walkthrough

Walkthrough

This PR modifies the update-lock job in supply-chain-promote-core.yml, replacing direct pushes to main with a branch-based PR flow. It adds pull-requests write permission, introduces unique feature branch naming, adds input validation for LOCK_FILE, IMAGE_ID, and GHCR_REF, and enables squash auto-merge on the created PR.

Changes

Update-lock Workflow Refactor

Layer / File(s) Summary
Documentation and permissions
.github/workflows/supply-chain-promote-core.yml
Comment block rewritten to describe the PR-based flow; job permissions extended with pull-requests: write.
Branch naming and input validation
.github/workflows/supply-chain-promote-core.yml
Step renamed to "open auto-merge lock PR"; introduces unique LOCK_BRANCH naming and validates LOCK_FILE, IMAGE_ID, and GHCR_REF before use.
Branch commit and auto-merge PR logic
.github/workflows/supply-chain-promote-core.yml
Replaces direct-to-main push/retry loop with branch creation, conditional commit, push, PR creation/lookup, and squash auto-merge attempt with fallback warning.

Sequence Diagram(s)

sequenceDiagram
  participant Workflow
  participant LockBranch
  participant UpdaterScript
  participant GitHubPR

  Workflow->>LockBranch: create/switch to unique branch
  Workflow->>UpdaterScript: run scripts/update_approved_lock.py
  UpdaterScript-->>Workflow: updated LOCK_FILE (or no changes)
  Workflow->>LockBranch: commit and push changes
  Workflow->>GitHubPR: gh pr create (or locate existing PR)
  Workflow->>GitHubPR: enable --auto --squash merge
  GitHubPR-->>Workflow: auto-merge enabled or warning left open
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

  • ByronWilliamsCPA/.github#234: Prior PR added the update-lock job's commit/push logic that this PR now refactors into a branch-based PR flow.

Poem

A bunny hopped from main's straight track,
No more direct pushes, no looking back!
A branch, a PR, a squash so neat,
Auto-merge makes the workflow sweet. 🐰
Hop, validate, and merge with glee!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is useful but does not follow the required template and omits several mandatory sections like Related Issue, Type of Change, Testing, and Checklist. Rewrite the PR description using the repository template and fill in the missing required sections, especially Related Issue, Type of Change, Changes Made, Testing, and Checklist.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: routing the promote-core lock update through an auto-merge PR.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/promote-core-lock-pr-automerge

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@williaby

williaby commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator Author

PR Review

BUILD FAILING: do not merge until CI is green.
PREMISE QUESTION: implements previously-deferred PR-routing work cleanly (no regression, no contradicted decision), but the chosen auth mechanism (GITHUB_TOKEN) likely prevents the design's own required check from running automatically — see Critical #2.

Critical (must fix before merge)

  1. CI phantom required checks: mergeStateStatus is BLOCKED (mergeable, no conflicts) because the three branch-protection-required checks — "Security Gate Validation," "Dependency & Standards Validation," "Check REUSE Compliance" — never triggered for this PR's pull_request event, despite firing reliably on all four prior sibling PRs (feat(supply-chain): add four composable reusable workflows #234fix(supply-chain): authenticate promote-core scan job to authenticated upstreams #237) today. The diff doesn't touch those workflows' triggers, so this isn't a content problem. Remediation: push an empty commit or close/reopen to re-trigger.

  2. GITHUB_TOKEN approval-gate risk (lines 508-523): gh pr create/gh pr merge --auto authenticate with the bare secrets.GITHUB_TOKEN. Per GitHub's docs and confirmed community reports (community discussion #65321, cli/cli discussion #6575), a pull_request event created by GITHUB_TOKEN (vs. a PAT/GitHub App token) creates workflow runs in an approval-required state — they don't run automatically. If "Validate catalog schema" in container-images is pull_request-triggered, it will sit pending approval on every auto-generated lock PR, and gh pr merge --auto will never actually complete: the exact "stuck forever" failure this PR aims to fix, relocated. Recommend verifying empirically (does the required check fire immediately on a real bot-opened PR?) before relying on this in production; the documented fix is a GitHub App installation token or PAT instead of GITHUB_TOKEN.

  3. PR_BODY renders broken markdown (lines 499-507): the multi-line bash string inside the YAML run: | block inherits ~10 literal leading spaces per continuation line (reproduced locally). GFM renders 4+-space-indented lines as a code block, so every auto-generated lock-PR's body will show broken/indented markdown instead of the intended bullet list. Fix: build with printf anchored at column 0, or strip leading whitespace before interpolation.

Important (should fix)

  • Lines 508-513: gh pr create ... 2>/dev/null || true discards the real failure reason before falling back to gh pr view; the job still exits 1 correctly on total failure, but the cause is unlogged. No RAD #VERIFY marker on this external-resource call.
  • Line 497: force-push to the per-run branch has no #ASSUME/#VERIFY marker despite RAD's mandatory concurrency-tagging category.
  • Lines 328-334: ghcr_name/ghcr_tag (caller-controlled, unvalidated) are newly exposed to the PR_BODYgh pr create --body context this diff adds; not exploitable by current callers, but there's no caller allowlist.
  • Lines 477-482: no re-sync with main before branching/pushing; the comment asserts merge-queue serialization but that's unverified for the target repos (container-images, homelab-infra) this workflow actually promotes into.

Suggested / Informational

Force-push is unnecessary given the branch name is already run-unique; IMAGE_ID has no length bound; the deferred GPG-signing follow-up has no tracking reference; branch naming doesn't match CLAUDE.md's claude/<description>-<id> convention (ambiguous scope, not a clear violation); missing issue reference on this bug-fix PR; "entry already present" detection depends on an unverified idempotency assumption in update_approved_lock.py. Full detail in the review session.

SonarQube: 0 PR-specific issues (never analyzed, consistent with Critical #1); 0 outstanding hotspots on main.

Reviewers: Copilot review was not requested for this PR (ruleset did not trigger it); CodeRabbit's check shows green but its actual comment reveals it was rate-limited and never ran ("you've reached your PR review limit"). Neither bot contributed findings to this review.

🤖 Generated with Claude Code

Addresses pr-review findings on #238:
- Fix PR_BODY markdown rendering: build with printf instead of a
  multi-line double-quoted string, which was baking the YAML step's
  own indentation into the PR description as literal whitespace.
- Preserve gh pr create's stderr instead of discarding it, so an
  auth/rate-limit/validation failure is distinguishable from the
  expected "PR already exists" fallback path.
- Add a regex guard on GHCR_REF (ghcr_name/ghcr_tag are caller-controlled
  and previously unvalidated) before it reaches PR_BODY or any git/gh
  command; bound IMAGE_ID's length in its existing guard.
- Drop the unnecessary --force on the per-run branch push so an
  unexpected collision fails loudly instead of silently overwriting.
- Add RAD #ASSUME/#VERIFY markers documenting the GITHUB_TOKEN
  approval-gate risk (a bot-opened PR's required checks may need
  manual approval to run), the merge-queue assumption on caller repos,
  and the lock-updater script's idempotency assumption.
- Reference #239 (tracked follow-up to sign the bot commit itself).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 1, 2026 00:59
@williaby

williaby commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator Author

PR Fix Summary

Addressed 9 of the pr-review's 10 auto-fixable findings in one commit (d8cc725):

Critical

  • Fixed PR_BODY markdown rendering (printf-built, no more leading-whitespace bug from the YAML block scalar).
  • Preserved gh pr create's stderr instead of discarding it, so a real failure is distinguishable from the "PR already exists" fallback.
  • GITHUB_TOKEN approval-gate risk: documented, not code-changed (per explicit decision) via a RAD #ASSUME/#VERIFY comment on the job. Recommend verifying empirically on a real promotion run before relying on this in production.

Important

  • Added a regex guard on GHCR_REF (ghcr_name/ghcr_tag were unvalidated and now reach the PR body).
  • Documented the merge-queue assumption on caller repos with a RAD #VERIFY marker.
  • Added the missing RAD marker on the branch push (folded into the force-push fix below).

Suggested

  • Dropped the unnecessary --force on the per-run branch push; a collision now fails loudly instead of silently overwriting.
  • Bounded IMAGE_ID's length in its existing regex guard.
  • Documented the lock-updater idempotency assumption with a RAD marker.
  • Opened supply-chain-promote-core: sign the update-lock bot commit (deferred from #238) #239 to track the still-deferred bot-commit-signing follow-up, replacing the untracked mention in the header comment.

Not changed: branch-naming convention (not a real violation) and missing issue reference (no issue existed to link).

Verification: yamllint, actionlint (matched to this repo's CI SHELLCHECK_OPTS=--severity=warning), pre-commit run --all-files (all hooks including no-em-dash), and reuse lint all pass locally. The PR_BODY fix was verified by executing the extracted bash against sample env vars and diffing byte-for-byte against the intended output.

CI re-run triggered by this push; watching for the previously-phantom required checks (Security Gate Validation, Dependency & Standards Validation, Check REUSE Compliance) to fire on the new head SHA.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the reusable supply-chain-promote-core workflow so the approved-lock update is routed through a per-run pull request with squash auto-merge, instead of attempting a direct push to main, which fails on protected branches.

Changes:

  • Switch update-lock from push-to-main to: create a per-run branch, open a PR, and enable squash auto-merge.
  • Add pull-requests: write permission to allow PR creation and auto-merge enablement.
  • Add input-shape validation for image_id and GHCR_REF, and introduce a unique LOCK_BRANCH name keyed on run_id and run_attempt.

Comment on lines 447 to 453
@@ -432,6 +451,22 @@ jobs:
echo "::error::lock_file has unexpected path shape: ${LOCK_FILE}"
exit 1
fi
The shape-only regex accepted values like
"catalog/../.github/workflows/ci.yml" because ".." matches
[a-zA-Z0-9._/-]. Add an explicit ".." substring rejection after the
shape check.

Addresses Copilot review finding on PR #238.
@williaby

williaby commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator Author

Fixed in a00c4e1: LOCK_FILE's shape regex allowed .. traversal segments (e.g. catalog/../.github/workflows/ci.yml); added an explicit .. substring rejection after the shape check. Addresses Copilot's review finding.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/supply-chain-promote-core.yml:
- Around line 453-476: The validation in the workflow job is only checking
line-oriented patterns, so newline-containing inputs can still pass and IMAGE_ID
also allows ".." segments. Update the checks around LOCK_FILE, IMAGE_ID, and
GHCR_REF to validate the entire string contents and reject embedded newlines or
traversal-like sequences, then add a git check-ref-format --branch validation
for LOCK_BRANCH before it is used with git switch -c. Keep the existing guard
logic in the same validation block so the final branch ref and related inputs
are sanitized before any git or gh command usage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c474e56c-cd3f-4634-ad27-9de247489772

📥 Commits

Reviewing files that changed from the base of the PR and between 5825ec5 and a00c4e1.

📒 Files selected for processing (1)
  • .github/workflows/supply-chain-promote-core.yml

Comment thread .github/workflows/supply-chain-promote-core.yml Outdated
grep's ^/$ anchors are per-line, not per-string: LOCK_FILE, IMAGE_ID,
and GHCR_REF values containing an embedded newline could pass their
grep-based shape check via a matching first line while the full value
differed. Switch the three shape checks to bash's [[ =~ ]], which
anchors to the whole string. Also reject consecutive dots in IMAGE_ID
explicitly (git already rejects them in the derived branch ref, but
this gives a clear error instead of a raw git failure) and add a
git check-ref-format guard on LOCK_BRANCH before git switch -c.

Addresses CodeRabbit review finding on PR #238.
@williaby

williaby commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator Author

Fixed in 010d2a5: CodeRabbit's finding was correct. grep's ^/$ anchors are per-line, not per-string, so LOCK_FILE/IMAGE_ID/GHCR_REF values with an embedded newline could pass the shape check via a matching first line. Swapped all three to bash's [[ =~ ]] (whole-string anchors). Also added explicit '..' rejection for IMAGE_ID and a git check-ref-format guard on LOCK_BRANCH before git switch -c, both confirmed by direct testing.

@sonarqubecloud

sonarqubecloud Bot commented Jul 1, 2026

Copy link
Copy Markdown

@williaby williaby added this pull request to the merge queue Jul 1, 2026
Merged via the queue into main with commit 1502ecd Jul 1, 2026
34 checks passed
@williaby williaby deleted the fix/promote-core-lock-pr-automerge branch July 1, 2026 01:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants