# 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 repository created empty has no history,
so every check-in of its first push counts.
- 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