Artifact c22ee7bd5baa6b44c94928a2e39d783cfa61017d3ae1366345d2d06b8a14e84d:
- File docs/landings.md — part of check-in [fd6b59e38c] at 2026-09-30 12:53:44 on branch default-branch-is-stable-and-closes-tickets — A default-branch check-in that says closes #N closes that ticket once, naming the check-in; landing merges and mirrors excluded (#298) (user: zeitgeist, size: 9597) [annotate] [blame] [check-ins using] [more...]
Landings
A branch is finished. There is no way to say so.
That is the gap a landing fills. zit can create the branch, commit to it, merge trunk into it and merge it back — but the act of proposing that it land leaves no trace: nothing to review, nothing to comment on, nothing to accept or reject, nothing for CI to fire on. The work goes from "on a branch" to "on trunk" with no event in between.
This is not a pull request
GitHub's pull request is a bazaar mechanism. It exists because a stranger cannot write to your repository, so they fork it and request that you pull. Every piece of its ceremony descends from that premise: the fork, the head/base repository pair, the frozen commit range.
Inside a cathedral that premise does not hold. Anyone with the check-in capability can already push a branch, and the branch is already in the store. Nothing needs to be transported, filtered or quarantined, because there is no trust boundary to cross.
So the artifact is not a request to transfer commits. It is a decision record about a branch that is already here, and the name follows the direction the work actually moves: nobody requests a pull; the branch lands.
What a landing is made of
No new artifact type. This is the constraint the whole feature hangs from, and zit states it as a rule rather than a habit: if the implementation reaches for a new card, the design is wrong.
A landing is three ordinary fossil artifacts:
| part | artifact |
|---|---|
| the record | a forum thread whose root body opens with a marker |
| every comment and state change | a reply in that thread |
| the outcome | a raw tag on the check-in that landed |
The root body carries the header:
zit-landing: 1
branch: <name>
target: <name>
<the author's message>
and a transition is a reply whose body opens with zit-landing-state: X,
where X is one of open, accepted, rejected.
Why it matters: clone a repository carrying landings with stock fossil and it sees forum threads and tagged check-ins. Fully readable, merely not actionable. That is the correct failure mode, and it is why no wire-protocol extension and no compatibility break were needed.
The lifecycle
Open
zit landing open --branch feat/x --to trunk -m "why this should land"
or New landing on the forge's landings page, which offers the
branches that exist rather than a text field that accepts a typo, and
defaults the target to the repository's own default branch. --to
defaults to trunk on the command line. Either way this mints the thread
root and leaves the branch unmerged; zit refuses a second open landing
on the same branch by name.
On a mirror the control names the upstream instead: a tracking store takes no local writes, so proposing there would fork it by accident.
Discuss
Replies in the thread, from the CLI or the record's page. The page renders them as a discussion; markdown previews through the same composer the wiki and forum use.
CI
Opening a landing is an event a pipeline can fire on, as is a new check-in on a branch with an open landing — the intended workflow keeps merging the target into the branch and testing there.
Accept, or reject
zit landing accept <id> # merge to the target, settle the record
zit landing reject <id> -m # settle it, merge nothing
The order is the contract. Accept merges and commits first, then writes the outcome tag and the transition reply. A failure after the merge leaves merged work under a still-open landing, which one command settles. The reverse order would leave an accepted record over an unmerged branch — a lie in a log that cannot retract one.
What the forge adds
Quarry reads landings out of its own index rather than the store: the record is a reading of forum posts, recomputed whenever any post in a thread folds, so a reply indexed before its root converges on the same answer.
Who may accept. Accept writes a merge check-in, so it requires
fossil's i — Write/check-in. Everything else about a landing is a
forum artifact, so opening, commenting and rejecting require 3
(WrForum). Rejecting merges nothing and therefore needs nothing more
than the capability to speak.
There is deliberately no rule against accepting your own landing. Inside a cathedral the author already holds the commit bit, and a required second reviewer is a policy fossil has no capability letter for; inventing one would be quarry policy wearing fossil's clothes.
Tickets a record touches. A landing's message may say what it does about tickets, in two verbs:
closes ae12f3 accepting the landing closes that ticket
addresses ae12f3 the landing works on that ticket, and finishes nothing
fixes and resolves are spelled differently and mean closes. The
reference must follow the verb immediately — "this closes the gap that
ae12f3 describes" declares nothing, because the token is four words
away. Cross-repository references are shown but never acted on: a
landing here has no business settling work in another store.
Both verbs LINK: the ticket page lists every landing and thread that
named it, with the landing's state, so a reader sees "something landed
for this" without opening anything. Only closes acts, and only once —
a ticket somebody already resolved keeps their resolution. The chip the
reader sees is produced by the same parser the accept path acts on, so
there is no way to write a reference that acts without appearing, or
appears without acting.
The declaration may sit in the title or the body; the title is where one-line landings put it.
Closing from a check-in. A landing is one of two paths that close
tickets. The other is fossil's: a check-in on the repository's
default branch whose comment says closes ae12f3 (or #12) closes
that ticket when the forge folds it, with the same verbs, the same
resolution and the same "already resolved stays resolved" rule. The
ticket's note names the closing check-in by hash, as a landing close
names its merge, and the change is recorded as the check-in's author.
- Only the default branch closes anything. A push to any other branch is work in progress, whatever its comment says; it closes the ticket when it lands.
- Each check-in closes what it names once. A restart or a re-fold does not repeat it, so a ticket reopened after its closing check-in stays open.
- History is not read as news: the first time the forge looks at a repository it starts from its current tip, so importing or mirroring a store closes nothing.
- A landing's own merge check-in is left to the landing — accepting it already closed what the landing named.
- A tracking mirror is never written: its tracker belongs to the upstream.
The default branch is chosen by name, never by which branch moved
last: trunk, else main, else master, else the branch the oldest
root check-in is on. A repository that renamed trunk without
cancelling the old name (tcl, tk) still resolves trunk onto main,
and there the default is main.
A moving branch. The record references the branch, never a frozen range, because the intended workflow keeps committing to it after the record is opened. So the diff is computed at view time, and a landing page shows a bounded summary with a link to the whole change.
Work nobody proposed. The landings page also reports the blind spot of its own queue: branch tips that are finished and going nowhere. A branch qualifies when it is an open leaf, is not the target, and no open landing names it — and, last, when the store says its work is not already in the target.
That last question is the one the first three cannot answer. Landing a
branch normally closes it, so the leaf test covers the usual case; a
branch landed and left open still got the offer, and taking it opened a
record that said merges up-to-date on arrival — an empty landing in
the queue and a wasted trip. It is asked last because it is the only one
that costs a zit subprocess, memoised on the pair of tips so a page pays
once per push, and capped per listing so a store with dozens of open
leaves cannot turn one page view into dozens of subprocesses. Rows past
the cap are reported unprobed.
A probe that cannot run answers not landed. The check exists to withhold an offer, and withholding one because a subprocess failed would hide real stranded work behind an infrastructure fault.
Conflicts are read, never discovered. A forge must never learn
whether a branch merges by attempting the merge, because on a server the
attempt is the damage. Quarry asks the store (zit landing show), which
answers merges clean, up-to-date, or conflict (N) with the paths.
A conflicted landing renders as blocked, names the files, and disables
accept rather than offering a control that would fail. The answer is
memoised on the pair of check-in tips, so a push invalidates it by
producing a different key.
What a landing is not
It is not a takedown, and it is not an outside-contribution path. A landing assumes the branch is already in the store. Accepting one does not unpublish anything a peer already holds, and no state here reaches across the trust boundary that bundles and imports exist for.
Sources
- zit#127 — the ceremony, and the decision to build it from artifacts fossil already has
- zit#131 — the mergeability probe and bare-store accept, which is what lets a forge with no working tree settle a record
- RFC 0004 §4 — the capability model the accept rule follows