Skip to content

fix(ci): bind eval merge source to the live main tip - #358

Merged
mrizzi merged 1 commit into
RHEcosystemAppEng:mainfrom
mrizzi:TC-7209
Oct 27, 2026
Merged

mrizzi merged 1 commit into
RHEcosystemAppEng:mainfrom
mrizzi:TC-7209

Conversation

@mrizzi

@mrizzi mrizzi commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Discover / "Resolve PR identity and check trust": the merge commit's parents[0] must now equal the live main tip (git.getRef heads/main) instead of the PR object's base.sha, which goes stale whenever main advances without a PR sync (TC-7207). This is stricter than before: an older main commit is refused, even one in main's history.
  • Head mismatch / wrong parent count / unreadable or invalid tip → fail closed immediately. Only a parents[0]/tip mismatch triggers a bounded re-read (3 attempts, 2000 ms apart, same as the existing merge_commit_sha === null poll). Each re-read re-runs every PR identity check.
  • base_sha output = verified live tip (== merge.parents[0].sha), so the native report binding matches the merge commit that run-evals/run-native-evals check out.
  • run-native-evals / "Recheck approved revision before WIF": dropped the stale pr.base.sha comparison and the now-unused BASE_SHA env. merge_commit_sha still pins both parents, so the recheck still fails closed if main advances during approval.
  • No checkout changes.

Tests (test_native_fullsend_eval_ci.py)

  • run_js double gains git.getRef (mainTip / mainTips / refError) and a per-read getCommit sequence (parentsSeq).
  • TC-7207 reproducer (stale base.sha, parents [tip, head]). It fails on current main and passes with the fix.
  • Parametrized negatives: older main commit, unrelated parent, head mismatch, 1 or 3 parents, getRef throws, invalid tip SHA.
  • Race recovery (one 2000 ms retry) and re-read identity enforcement.
  • Native recheck: stale base.sha passes; mismatched merge_commit_sha or branch fails.

python3 -m pytest plugins/sdlc-workflow/scripts/ -q → 753 passed, 3 skipped. uvx skillsaw → 0 errors. claude plugin validate → passed.

Implements TC-7209

🤖 Generated with Claude Code

Summary by Sourcery

Bind evaluation workflows to merges whose first parent is the verified live main tip and fail closed when the merge source or pull request identity cannot be trusted.

Bug Fixes:

  • Bind evaluated pull request merge sources to the exact live main tip, preventing stale or unrelated base commits from reaching credentialed jobs.
  • Recheck the approved merge revision and branch without relying on the potentially stale pull request base SHA.

Enhancements:

  • Add bounded recovery for races between merge computation and reading the live main tip while reapplying pull request identity checks on each retry.

Tests:

  • Expand CI coverage for stale bases, invalid merge ancestry, live-tip races, identity changes during retries, and native approval rechecks.

The Eval PR Run trust check compared the merge commit's first parent
with the PR object's base.sha, which only refreshes on push/sync, while
GitHub recomputes merge_commit_sha on the live main tip. Any main
advance without a PR sync failed Discover PR Context and skipped all
evals.

Require parents[0] to equal the live main tip read from heads/main
(stricter: older main commits are refused), with a bounded re-read
(3 attempts, 2000 ms) on a tip mismatch only that re-runs every
identity check. base_sha is now the verified tip. The native WIF
recheck no longer compares the stale base.sha; merge_commit_sha still
pins both parents.

Implements TC-7209

Assisted-by: Claude Code
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Reviewer's Guide

The workflow now securely binds eval execution to a merge commit whose first parent is the current live main tip and whose second parent is the event head, with bounded race recovery and complete identity revalidation. The native approval recheck no longer relies on stale pr.base.sha, and comprehensive tests cover valid, invalid, and concurrent-update scenarios.

Sequence diagram for live main tip merge verification

sequenceDiagram
    participant Workflow
    participant GitHubAPI
    participant Main
    Workflow->>GitHubAPI: pulls.get(pr)
    GitHubAPI-->>Workflow: PR identity and merge_commit_sha
    Workflow->>GitHubAPI: git.getCommit(merge_commit_sha)
    GitHubAPI-->>Workflow: merge.parents
    Workflow->>GitHubAPI: git.getRef(ref: heads/main)
    GitHubAPI-->>Workflow: live main tip
    alt parents[1].sha == event head and parents[0].sha == live main tip
        Workflow->>Workflow: set base_sha = live main tip
        Workflow-->>Workflow: continue eval
    else parent or tip mismatch
        alt attempts remain
            Workflow->>Workflow: wait 2000 ms
            Workflow->>GitHubAPI: pulls.get(pr)
            GitHubAPI-->>Workflow: revalidated PR identity
            Workflow->>GitHubAPI: git.getCommit(merge_commit_sha)
            Workflow->>GitHubAPI: git.getRef(ref: heads/main)
        else retry limit reached or invalid data
            Workflow-->>Workflow: fail closed
        end
    end
Loading

Sequence diagram for native approval revision recheck

sequenceDiagram
    participant Workflow
    participant GitHubAPI
    Workflow->>GitHubAPI: pulls.get(PR_NUMBER)
    GitHubAPI-->>Workflow: PR state, branch, head, merge_commit_sha
    alt identity and merge_commit_sha match recorded values
        Workflow-->>Workflow: proceed to WIF and native eval
    else mismatch or changed branch
        Workflow-->>Workflow: fail closed
    end
Loading

File-Level Changes

Change Details Files
Binds evaluation sources to the exact live main tip and validates the merge commit’s parent structure before emitting trusted revisions.
  • Adds a reusable PR identity read that revalidates all PR attributes during retries.
  • Reads heads/main and requires the merge parents to be exactly [live main tip, event head].
  • Fails closed for invalid refs, unreadable tips, incorrect parent counts, head mismatches, and exhausted retries.
  • Retries only parent-versus-tip mismatches, with up to three attempts and 2-second delays.
  • Emits the verified live tip as base_sha.
.github/workflows/eval-pr-run.yml
Removes the stale PR base SHA dependency from the post-approval native evaluation check while retaining merge revision pinning.
  • Drops the BASE_SHA environment input and pr.base.sha comparison.
  • Continues validating PR state, branch, head, repository, source branch, and merge_commit_sha.
.github/workflows/eval-pr-run.yml
Expands CI workflow tests to model live-ref reads, merge recomputation races, fail-closed validation, and native recheck behavior.
  • Adds git.getRef and sequenced getCommit test doubles.
  • Covers stale base SHA recovery, invalid or unrelated merge parents, invalid/unavailable main tips, bounded retries, and identity changes during rereads.
  • Verifies stale base SHA is accepted after approval while changed heads, merge SHAs, or branches are rejected.
plugins/sdlc-workflow/scripts/test_native_fullsend_eval_ci.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="plugins/sdlc-workflow/scripts/test_native_fullsend_eval_ci.py" line_range="96-98" />
<code_context>
  createReview:async r=>{reviews.push(r);storedReviews.push({...r,id:storedReviews.length+1,user:{login:'github-actions[bot]'}});},
  listFiles:async()=> (data.paths||[]).map(filename=>({filename}))},
- git:{getCommit:async()=>({data:{parents:(data.parents||['b'.repeat(40),'a'.repeat(40)]).map(sha=>({sha}))}})},
+ git:{getCommit:async()=>{
+   const parents=data.parentsSeq ? data.parentsSeq[Math.min(commitReads++,data.parentsSeq.length-1)] : data.parents;
+   return {data:{parents:(parents||['b'.repeat(40),'a'.repeat(40)]).map(sha=>({sha}))}};},
+ getRef:async()=>{if(data.refError) throw Error('ref unavailable');
+   const tip=data.mainTips ? data.mainTips[Math.min(refReads++,data.mainTips.length-1)] : data.mainTip;
</code_context>
<issue_to_address>
**Wrong Git requests pass tests**

When the workflow calls `getCommit` with a SHA other than the PR merge SHA, or `getRef` with a ref other than `heads/main`, `test_source_resolution_binds_merge_to_live_main_tip_despite_stale_base` (lines 142–143) and the retry/negative tests still pass if the workflow requests the wrong commit or ref: these stubs ignore the request arguments and return the configured parents and tip regardless.

Have the stubs capture and assert the requested commit SHA and ref so the tests fail when either resource selector is wrong.

Also at `plugins/sdlc-workflow/scripts/test_native_fullsend_eval_ci.py:99-101`.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 1 finding to address first, and if the merge-parent/live-tip binding is wrong, a credentialed evaluation could run against a merge built from an unintended base. Reverting would stop future runs, but it cannot undo credentials issued or actions already taken by a run that passed the check.

Blocking findings: plugins/sdlc-workflow/scripts/test_native_fullsend_eval_ci.py:98


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment on lines +96 to +98
git:{getCommit:async()=>{
const parents=data.parentsSeq ? data.parentsSeq[Math.min(commitReads++,data.parentsSeq.length-1)] : data.parents;
return {data:{parents:(parents||['b'.repeat(40),'a'.repeat(40)]).map(sha=>({sha}))}};},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium · Wrong Git requests pass tests

When the workflow calls getCommit with a SHA other than the PR merge SHA, or getRef with a ref other than heads/main, test_source_resolution_binds_merge_to_live_main_tip_despite_stale_base (lines 142–143) and the retry/negative tests still pass if the workflow requests the wrong commit or ref: these stubs ignore the request arguments and return the configured parents and tip regardless.

Have the stubs capture and assert the requested commit SHA and ref so the tests fail when either resource selector is wrong.

Also at plugins/sdlc-workflow/scripts/test_native_fullsend_eval_ci.py:99-101.

Prompt for AI agents
In `plugins/sdlc-workflow/scripts/test_native_fullsend_eval_ci.py` at lines 96-98:

**Wrong Git requests pass tests**

When the workflow calls `getCommit` with a SHA other than the PR merge SHA, or `getRef` with a ref other than `heads/main`, `test_source_resolution_binds_merge_to_live_main_tip_despite_stale_base` (lines 142–143) and the retry/negative tests still pass if the workflow requests the wrong commit or ref: these stubs ignore the request arguments and return the configured parents and tip regardless.

Have the stubs capture and assert the requested commit SHA and ref so the tests fail when either resource selector is wrong.

Also at `plugins/sdlc-workflow/scripts/test_native_fullsend_eval_ci.py:99-101`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[sdlc-workflow/verify-pr] Classified as suggestion - hardening the GitHub API test doubles to capture and assert the requested commit SHA / ref is a reasonable test-robustness improvement, but it is not required for correctness: the new tests already exercise the live-main-tip binding, race re-read, and fail-closed paths, and neither CONVENTIONS.md nor an established codebase stub pattern requires argument-asserting read stubs (the existing API doubles in this suite are all response-only). No sub-task created.

@mrizzi mrizzi added the ok-to-test Enable verify-pr execution on PRs from forks label Oct 27, 2026
@fullsend-ai-review

fullsend-ai-review Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

🤖 Finished Verify Pr · ✅ Success · Started 8:56 AM UTC · Completed 9:09 AM UTC

Commit: 8f8e212 · View workflow run →

Runtime: claude · Model: claude-opus-4-8 · Effort: high · Cost: $6.05

@fullsend-ai-review

Copy link
Copy Markdown

[sdlc-workflow/verify-pr] Re: @sourcery-ai[bot] review - Classified as suggestion - the review raises a single point: the GitHub API test doubles should assert the requested commit SHA / ref so wrong-resource requests fail. This is an optional test-hardening improvement, not required for correctness, and is not backed by CONVENTIONS.md or an established codebase stub pattern (existing API doubles in this suite are response-only). No sub-task created.

@fullsend-ai-review

Copy link
Copy Markdown

Verification Report for TC-7209 (commit 8f8e212)

Check Result Details
Review Feedback PASS 2 items (1 inline + 1 review body) - a single Sourcery suggestion; no code change requests, no sub-tasks.
Root-Cause Investigation N/A No sub-tasks created, so nothing to investigate.
Scope Containment PASS Exactly the 2 task-specified files changed; no out-of-scope or unimplemented files.
Diff Size PASS ~177 lines (+140/-37) across 2 files - proportionate to a CI trust-check fix plus tests.
Commit Traceability PASS The sole commit references TC-7209 ("Implements TC-7209").
Sensitive Patterns PASS No secrets/credentials in added lines (hex SHA placeholders are synthetic test data).
CI Status PASS 8 check-runs (incl. Script Unit Tests 3.11-3.14) + "Eval PR Run" status all success on the head SHA.
Acceptance Criteria PASS 6 of 6 met - verified by static diff inspection and corroborated by the passing CI unit-test runs.
Test Quality PASS No repetitive tests; all new test functions have docstrings; Eval Quality: N/A (no eval reviews).
Test Change Classification ADDITIVE New getRef/parentsSeq stubs + 4 new tests + 2->5 case expansion; no coverage loss.
Verification Commands PASS pytest suite (the task's verification commands) runs green in CI on the head SHA; not executable in-sandbox.

Overall: PASS

All verification checks pass. The PR cleanly implements the TC-7207 fix: the Eval PR Run trust check now binds the merge commit's first parent to the live main tip (read via git.getRef heads/main) instead of the stale PR base.sha, with a bounded 3-attempt / 2000 ms re-read on a tip mismatch only, and the native WIF recheck no longer compares the stale base. All six acceptance criteria are satisfied and the pytest plugins/sdlc-workflow/scripts/ suite passes in CI across Python 3.11-3.14.

One Sourcery review item (inline + review body, the same point) suggests the GitHub API test doubles assert their requested commit SHA/ref. It is classified as an optional suggestion - a reasonable test-hardening idea that is not required for correctness and is not backed by CONVENTIONS.md or an established codebase stub pattern (the existing API doubles in this suite are response-only). No sub-task was created.

Note: the sandbox working tree was on main, not the PR head, so Acceptance Criteria and Verification Commands could not be executed in-sandbox. Those verdicts rest on static inspection of the pre-fetched PR diff plus the passing CI "Script Unit Tests" check-runs on the exact head SHA 8f8e212, which run those exact commands.


This comment was AI-generated by sdlc-workflow/verify-pr v0.13.10.

@mrizzi
mrizzi merged commit 8972552 into RHEcosystemAppEng:main Oct 27, 2026
23 checks passed
@mrizzi
mrizzi deleted the TC-7209 branch October 27, 2026 09:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ok-to-test Enable verify-pr execution on PRs from forks

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant