---
name: ff-only-absorbs-one-sibling-lane
description: "A fast-forward-only main can absorb at most ONE lane branched from a given main sha; sibling lanes need the golden merge chain, not a bottom-up ff sweep"
metadata: 
  node_type: memory
  type: feedback
  originSessionId: a23c7b56-1bb5-41b1-b880-05d40c1daffc
  modified: 2026-09-06T15:35:25.233Z
---

A "merge #A -> #B -> #C into main, ff-only, bottom-up" plan is unexecutable the moment two of those lanes are **siblings** — both children of the same main sha. The first ff moves main off that sha and strands every sibling, so each remaining lane needs a rebase **and a re-gate**, which is the opposite of the cheap sweep the plan promised.

Measured 2026-07-29 (milestone A): `ci/golden-lane-w0` and `build/acl-core` were both children of `ffc7e9e`. My own pre-clear integration ruling ordered #119 -> #120 -> #121 -> #123 ff-only; it would have forced four rebases. Only the linear chain (`acl-core` ⊂ `two-key-subnet` ⊂ `sender-stamp`) can ride a single ff.

**Why:** ff requires main to be an ancestor of the branch tip. Siblings are not ancestors of each other, so linearity is a property of the whole set, not of each PR.

## ⭐⭐ THE DIRECTION IS THE WHOLE RULE — IT FAILED ITS OWN READER 2026-08-04

I ran the right command and drew the reversed conclusion. Grounding lane `obs/resume-attach-intent`
@`9c9e6c5` I measured `git merge-base --is-ancestor 11169c1 origin/main` → true, and reported the lane
"cleanly fast-forwardable, no rebase owed". That predicate says **main CONTAINS my base**. ff needs the
OPPOSITE direction — main an ancestor of the LANE TIP — and `4b37512` is not, because the lane branched
before it. doyle caught it in his gate read and stated the merge-base plainly; the lane rides the golden
head merge chain, nothing owed.

**The trap is that both directions are true-sounding sentences about the same two shas**, and the
`--is-ancestor` form reads like a general "these are related" check. A base being contained in main
means only that a rebase would be *clean*; it says nothing about ff-ability. Nothing was lost here —
doyle corrected it before it reached a merge decision — but under ff-only main the same slip is how a
lane gets promised as absorbable when it needs the chain.

**Discriminator, one command, no ambiguity:** `git merge-base --is-ancestor origin/main <lane-tip>` —
main on the LEFT. True ⇒ ff-able. Anything else, including "my base is in main", is not an ff claim.
Kin: [[rule-failed-its-author-needs-a-mechanism]] (this file already said "ff requires main to be an
ancestor of the branch tip" at the time I got it backwards — a rule you have READ is not a rule you
have APPLIED), [[precise-claim-is-falsifiable-vague-is-useless]], and
[[a-predicate-without-its-tool-is-not-evidence]] — name the predicate you actually ran and do not
upgrade it in the retelling.

## ⭐⭐ THIRD FACE — A HARMLESS-LOOKING DOCS COMMIT OF YOUR OWN IS A SIBLING LANE (2026-08-17)

The sibling does not have to be someone's build lane. Mine was `b483699`, a one-file
`docs/INFRA-REGISTER.md` register row I had committed locally and was about to push to main as
tidy-up while the release candidate was in flight. It is a child of `27d40b9` — the same sha
`golden/nameplate-181` branched from — so pushing it would have moved main off the candidate's base
and left the release lane needing a rebase, i.e. a NEW sha, i.e. **tested-sha == shipped-sha
broken** for the sake of a docs row. Caught before the push with the discriminator above, run both
directions.

**Why this face is the dangerous one:** the sibling-lane rule gets applied to things that *look*
like lanes — branches, PRs, other agents' work. A docs commit you authored, on main, touching
nothing the candidate touches, does not present as a lane at all, so the rule never gets consulted.
Content-independence is irrelevant: ff cares only about ancestry, and a one-line doc change is
exactly as disqualifying as 16 commits. There is also no conflict to warn you — the rebase would
succeed cleanly, which is precisely why it is silent.

**How to apply:** while a release candidate is unmerged, main is FROZEN to anything that is not the
candidate itself. Park unrelated commits — however small, however yours — until the release ffs to
main, then push them on top. Before any push to main during a release window, run
`git merge-base --is-ancestor <the-thing-you-are-pushing> <candidate-tip>`; false means you are
about to strand the candidate. Kin: [[hold-pushes-during-the-tag-window]] is the SAME FAMILY one
step later — there a stray docs commit points the TAG at an untested sha; here it strands the
candidate's ff. Same act, two different windows, so the freeze runs from candidate-authored all the
way through tag. Also [[quoted-measurement-decays-when-you-mutate-the-subject]] — pushing mutates
the subject the whole gate result was about.

## ⭐ FOURTH FACE — TWO GREEN SIBLING PRs: LAND THE EXPENSIVE-RERUN ONE FIRST (2026-09-06)

hertz's #193 (one docs file, thin CI ≈ 30 s, lint/unit skipped by the changes filter) and todlando's
#194 (product + e2e test, unit legs on BOTH self-hosted boxes ≈ 10 min) were both children of
`9f809f8d`. #193 went green first; I ff-landed it (`68e34ae5`) and in the SAME breath told todlando
"#194's base stays valid, no rebase required, I will ff-land on top" — an affirmative ff claim made
without running the discriminator. When #194 went green 12 min later it was not ff-able; I rebased
it (`eb38b71a`, range-diff 1/1 `=`) and it re-queued for a second 10-min run that also re-occupied
the Windows box W0 was waiting on.

**The arithmetic the rule needs:** when two sibling heads are both headed for green, the one landed
FIRST strands the other into a rerun. So land the one whose rerun is EXPENSIVE first and hold the
cheap one — a 30 s docs rerun is the price you want to pay, not a 10-min two-box rerun. "Docs first
because it is ready" is exactly backwards under ff-only. My own live-context already said "count the
RE-RUNS each land order forces" and I still did not count.

**How to apply:** at the moment a PR goes green, before pushing main, list every OTHER open head
whose CI is running or queued and run `git merge-base --is-ancestor <that-head> <this-head>`; if
false, landing this one reruns that one — hold unless this one's rerun is the dearer of the two.
Never send "no rebase needed" without having run `git merge-base --is-ancestor origin/main <tip>`
main-on-the-left first (kin: [[never-send-a-claim-composed-before-its-check-ran]]).

**How to apply:** before promising an ff sweep, compute `git merge-base` for every lane pair and ask which lanes are actually ancestors of which. If any two are siblings, use ADR-0050's golden chain instead: assemble `golden/<milestone>` from main with ordinary MERGE commits (merge commits inside the chain are fine — main only has to be an ancestor of its TIP), run the full suite once on it, ff main to that tested tip. All PRs auto-close as merged. See [[golden-ci-strategy]] and [[main-baseline-procedure]]. Corollary: crossing two sibling build lanes is a merge of one into the other, never a rebase of 16 commits across it — the merge keeps each commit's tested identity and puts the resolution inside the PR where it gets reviewed ([[resolve-wrapper-conflict-toward-shared-body]]).
