Skip to content

fix(speculate): wait for every assumption to settle before merging - #556

Merged
behinddwalls merged 0 commit into
preetam/codem-424-lazy-dependency-scoringfrom
preetam/speculate-merge-gate
Aug 10, 2026
Merged

fix(speculate): wait for every assumption to settle before merging#556
behinddwalls merged 0 commit into
preetam/codem-424-lazy-dependency-scoringfrom
preetam/speculate-merge-gate

Conversation

@behinddwalls

@behinddwalls behinddwalls commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

Why?

A head could merge on an assumption that had not come true. mergeablePath required every dependency a path assumed would succeed to have actually merged, but imposed no wait at all on one it assumed would fail.

The doc comment stated the reasoning: "A dependency assumed to fail imposes no wait: the path is broken the moment that dependency succeeds, so a still-live path has already been vindicated on it." That holds only if the transition were instantaneous. It is not — a dependency spends time in speculating, and then in merging, having neither succeeded nor failed. assumptionBroken only fires on a terminal state, so throughout that window the path is neither broken nor vindicated. It is unsettled, and the gate read unsettled as permission.

A path that assumed a dependency would fail was built without that dependency's changes. Landing the head while that dependency is still live, and watching it land too, puts a combination on the trunk that no build ever validated — the one thing the queue exists to prevent. It needs no textual conflict to break the trunk, because the two changes were never built together.

Measured against the real predicates before fixing, with a passed fails(D) path and only D's state varying:

D's state mergeablePath correct?
speculating true no
merging true no
cancelling true no
failed true yes — the assumption came true
succeeded false yes — assumptionBroken catches it

decide returned merge in all three of the wrong rows.

What?

The rule is now symmetric: a path may merge once every dependency it took a position on has finished the way it assumed. succeeds needs Succeeded; fails needs Failed or Cancelled; ignored is not a position, so it still imposes no wait and conflict relaxation is untouched. allAssumedSucceedingMerged becomes allAssumptionsSettled, since it no longer looks only at succeeding dependencies.

Note this is not the "bypass large diff" early merge the RFC describes. That reads a passed path for every combination of the dependencies, and is not implemented on the controller side — nothing enumerates combinations. A single path betting the right way was never a sound approximation of it, and speculation.md now says so rather than describing behaviour the code does not have.

One liveness consequence, recorded on CODEM-428 rather than fixed here: a dependency stuck in Cancelling now stalls its dependents too, not just itself. finalizeCancellations converges Cancelling → Cancelled on any subsequent run, so this only bites when no further run is triggered — which is that issue's edge-triggering gap.

Test Plan

  • make test — 96/96 pass
  • make lint, make check-gazelle, make check-tidy

TestMergeablePath gains the cases the old rule got wrong: a fails assumption waits out speculating, merging and cancelling, and merges on Failed or Cancelled. An assumed-succeeding dependency waits out its merge, and an ignored one still imposes no wait.

Issue

Fixes https://linear.app/uber/issue/CODEM-429

Issues

LINEAR-CODEM-429

@behinddwalls

Copy link
Copy Markdown
Collaborator Author

Closed as "merged" by GitHub, not by a real merge — nothing landed on main.

This PR's head (preetam/speculate-merge-gate) was reordered to sit below its old base (preetam/codem-424-lazy-dependency-scoring) so the merge-gate fix lands before the snapshot widening that depends on it. Once the rebase made this head an ancestor of its own base branch, GitHub auto-closed the PR and recorded the head commit as the merge commit. main is unchanged at a0d65327.

Superseded by #559, which carries the same commit with the correct base. Stack is now #558#559#554.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant