ADR 0110: The landing model — pull from any ref, land only on the trunk
Amendments.
- Vocabulary: current spellings are
accept(formerlygraduate) andupdate(formerlyintegrate); the retired product-category wording readsdiscern, the gate, or the bar, and the shared-branch label reads the trunk; the decision and reasoning are unchanged.- ADR 0153 — configuration: the single landing target now lives at
[repository].trunk; references below to[project].main_branchpreserve the original schema spelling.- ADR 0194 — landing authority: the stop-after-green consequence below is now conditional: without current-conversation consent or a machine-verified standing or effort grant, the agent relays the receipt and waits, while a verified recorded grant may route the finished branch directly to
accept. The trunk-only topology and validated-tree rules stand.- Completion-model direction (2026-09-05; settled 2026-09-12): ADR 0375, as amended by ADR 0389, attaches source authority to the source checkout's own committed tip, and ADR 0389 restores this record's landing behavior. ADR 0379 adds an owner-authorized exception with no passing Proof, implemented by the
exceptionrecord family.
Status: accepted — supersedes the configurable-destination decision of ADR 0046 (its removal of the handoff-worktree skill stands); extends the update verb of ADR 0055 / ADR 0064.
Context
§discern turns the git-worktree workflow into one coherent product, so how work accumulates is part of the product itself, not something left to raw git. Two seams had worn through:
- The shipped
accept --to branchdefault was a trap with no second act. ADR 0046 gaveaccepta configurable destination ([worktree].accept_to = "branch" | "trunk", default"branch"), landing the finished branch checked out in the main checkout "for review". But no verb could land it from there:statuson the parked checkout advised runningdiscern start, and once anything returned the main checkout to the trunk, the unlandedagent/*branch became invisible. The "review" landing also parked the main checkout off-trunk, which poisoned every new worktree created from its HEAD (fixed separately — worktrees now fork from the trunk). ADR 0046 explicitly deferred "accept an arbitrary branch name as the destination" as a distinct feature; this ADR resolves that deferral — by placing the flexibility on the other axis.
- Multi-phase and experimental work had no blessed composition path.
updatepulled only the trunk, andstartforked only from the main checkout's HEAD, so building phase two on an unlanded phase-one branch meant hand-run git merges — exactly the raw-git workflow discern exists to absorb.
Decision
§Compose from any ref on the pull axis; land only on the trunk on the push axis. The model is deliberately asymmetric:
- Push —
accept— is trunk-only. There is exactly one place work lands: the trunk ([project].main_branch), fast-forwarded to the branch tip, the worktree removed, the merged branch deleted, the trunk checkout refreshed (ADR 0098).[worktree].accept_toand--toare removed; a schema 16→17 migration drops the key from existing configs.acceptrefuses — with the way back named — when the main checkout sits on a branch other than the trunk: it never silently switches a checkout someone parked deliberately. - Pull —
start --from <ref>andupdate --from <ref>— is unrestricted. A worktree may fork from any ref and merge any ref into itself, through one shared resolver that refuses unknown and ambiguous names. Absent the flag,startforks from the trunk andupdatepulls the trunk — the everyday defaults, needing nothing passed. The ADR 0064 change summary keeps working against an arbitrary source: itsrange.mainanchor is documented as "the incoming tip" (the wire field name is kept).
The "trunk" is never a special object: it is whichever worktree is the current assembly point. Work flows up a tree of branches by being pulled together and gated — an integrator worktree start --froms a phase branch, pulls sibling branches in with update --from, resolves any conflict once with every part in view, and runs the gate on the combined tree. Only the finished whole crosses to the trunk, once.
The explicit nos:
- No arbitrary push destination. Letting
accepttarget other branches would reintroduce the divergence a single landing target exists to prevent: multiple "land here" targets are how agent-heavy repos drift into a tangle of half-landed branches. Pulling never mutates a shared target, so there is no concurrent-landing race and no fast-forwarding a branch checked out in another worktree — and the integrator gates the combined tree, the state actually worth validating. - No review landing. Review of in-flight work happens while its worktree exists (the branch is visible,
statuslists it); the review artifact is a separate concern, out of this ADR's scope. A user can always drop to raw git and merge wherever they like — the blessed path is a product, not a cage.
Consequences
§- One landing target keeps a repo coherent: every finished line of work reaches
mainthe same way, always a clean fast-forward (the acceptance gate already proves the branch contains the trunk). - The main checkout stays on the trunk in steady state, which is also what keeps
start's trunk-forking default meaningful and the fleet survey honest. - Existing installs migrate: schema 16→17 deletes
[worktree].accept_to(and its doc comment) from the config; the config schema names the dead key with a friendlydiscern upgradepointer until they do. - Composition below the trunk is now first-class, so the flexibility ADR 0046 deferred lands on the pull side: phases, experiments, and integrator worktrees are ordinary
start --from/update --fromcalls with every existing guarantee (clean-tree precondition, conflict abort to a clean tree, re-materialize, change summary) intact. - Anyone who relied on
--to branchas a review gate loses it; the equivalent review moment is the still-live worktree plus the explicit-user-request rule onaccept(unchanged: agents stop after a greendonerun and wait).