CodeIssuesPull RequestsActionsSecurityInsights
✨ AI
More
Settings

fix(merge): the risk gate scored a commit it was not merging #5612

MergedXSccantynz wants to mergefix/risk-gate-head-pinningmainopened 1h ago
ccantynzcommented 1h ago

Found while merging this session's own PRs, which is the only reason it was visible: the merge response reported "Touches 11 file(s) with 1159 added" for a head that had eleven commits on it, and riskScore.commitSha named a commit eleven earlier. The score was real. It was about a different change.

The bug

The gate's lookup chain was:

  1. getCachedPrRisk — SHA-pinned. Correct.
  2. getLatestCachedPrRisk — documented as "the most recent cached score for a PR regardless of which SHA it was pinned to".
  3. computePrRiskForPullRequest — fresh.

Step 2 is the whole defect. It sits between a correct answer and a computable one, and its only effect is to serve a score describing a different commit — step 3 was already there and would have been reached.

Why it isn't cosmetic

This gate soft-blocks the critical band. So:

push a small change → let it score → push a risky one → the merge is authorised by the old diff's band.

A score for a commit you are not merging is not a weaker signal than a fresh one. It is a statement about something else.

The fix

  • Head SHA is resolved before the risk lookup.
  • The unpinned fallback is gone: pinned cache, else compute for this SHA. Still one round trip.
  • Any score whose commitSha ≠ that head is discarded rather than used.
  • A missing score and a score for another commit are treated identically, because they carry the same evidence about this merge: none.

riskAppliesToHead is exported and asserted directly — for the reason this shipped at all. It was invisible from the call site, where the fallback reads as an ordinary cache policy.

auto-merge deliberately keeps the unpinned lookup

And now says why. It re-evaluates on every event for the branch, so a score lagging by one push is corrected next pass, and there the value can only withhold an auto-merge, never authorise one the other gates would refuse. The interactive gate is the opposite: it acts once, on a SHA the caller named. If auto-merge ever gains the ability to approve on risk rather than only hold back, it must be pinned too — that's in the comment so the next person doesn't have to rederive it.

Also: manifestsScanned counted attempts, not successes

Self-inflicted, from earlier in this same session, found by applying the GateTest session's point about asserting on honesty fields rather than trusting a reader.

The field was toRead.length — manifests the walk decided to open. A manifest that failed to parse contributed nothing to total and still counted toward the figure saying how much of the tree was read. That is exactly the "we looked" / "we succeeded" conflation the field was added to prevent. Now counts successful parses on all three manifest paths; the requirements.txt path was missed on my first pass.

Verification

  • 6 new tests on riskAppliesToHead, including that a short-SHA prefix does not create a false pin.
  • Merge/auto-merge/gated-chain suites: 82 pass, 0 fail.
  • Full CI-safe suite: 11 failures, identical to the pre-change baseline (local no-DB artifacts).
ccantynzAI Reviewcommented 1h ago

AI Triage

(no summary)

Priority: medium Risk area: mixed

Suggested labels: (no label suggestions) Suggested reviewers: (no reviewer suggestions)

Suggestions only — nothing has been applied. The PR author stays in control.

gluecron[bot]🤖 botAI Reviewcommented 1h ago

AI review unavailable

The platform's AI balance is exhausted, so AI generation is temporarily unavailable. Nothing was lost. You can queue this as a repair for the internal agent from the repository's Health page, or try again once the balance is restored. The PR is otherwise unchanged.

Cross-repo impact

See what breaks downstream if this PR merges.

Analyze →
⮌ Merged

This pull request was merged into main.

c comment · e edit title · m merge · a approve · r request changes · ? shortcuts