Development record
The facilitator's mode gates the merge
Summary
Delivers itd-2610150602547271, the first slice of itd-97. A repository declares
facilitator.mode (human or auto) in .abcd/config.json, human by
default and human from any layer winning. The bare abcd board names the
mode and its layer. The merge gate (mergeGate in internal/core/landing,
today ruling AM1 as a stand-in) reads the mode for abcd land and the build
loop alike:
- a contributor's pull request (its author has Read or Triage, or no, permission on the repository) arms only once a maintainer's approval of its head counts, in either mode;
humanarms a maintainer's pull request only once the technical facilitator has approved its head, and says the approval is needed until then;autoarms a maintainer's pull request at once by the ruleset mirror's method, but only a head that a review receipt names.
While the mode is human, the shell guard refuses the merge commands. While
it is auto, the status line says merging is autonomous. abcd's own
repository sets auto. Every behaviour is test-first, and each forge call
is checked live and read-only before it is relied on: the forge's fields are
pinned in internal/ghcontract, and the fake forges refuse what the real one
refuses.
Footprint
- packages: internal/core/landing/config.go, internal/core/landing/config_test.go, internal/core/layered/layered.go, internal/core/layered/layered_test.go, internal/core/layered/repoat_test.go, internal/core/board/board.go, internal/core/board/board_test.go, internal/surface/cli/cli.go, internal/surface/cli/board.go, internal/surface/cli/board_status.go, internal/surface/cli/board_status_test.go, internal/core/landing/review.go, internal/core/landing/review_test.go, internal/core/landing/primitives.go, internal/surface/cli/receipts.go, internal/core/landing/facilitator.go, internal/core/landing/facilitator_test.go, internal/core/landing/land.go, internal/core/landing/land_test.go, internal/core/landing/ruleset.go, internal/core/implement/loop/land.go, internal/core/implement/loop/land_test.go, internal/core/implement/loop/land_approval_test.go, internal/core/implement/loop/hold_approval_test.go, internal/core/implement/loop/drain_finish_test.go, internal/core/implement/loop/ghcontract_test.go, internal/core/implement/loop/issue_test.go, internal/core/implement/loop/parallel_test.go, internal/core/implement/loop/parked_reserve_test.go, internal/ghcontract/rest.go, internal/ghcontract/rest-fields.txt, internal/ghcontract/ghcontract_test.go, internal/surface/cli/land.go, internal/surface/cli/land_surface_test.go, internal/core/guard/merge.go, internal/core/guard/merge_test.go, internal/core/guard/guard.go, internal/core/guard/speculate.go, internal/core/guard/unknownsites_test.go, internal/core/guard/defaults/guard.json, internal/surface/cli/guard.go, internal/surface/cli/guard_approve.go, internal/surface/cli/guard_merge_test.go, .abcd/config.json, commands/land.md, commands/guard.md, commands/build.md, commands/implement.md, .abcd/development/brief/04-surfaces/38-land.md, .abcd/development/brief/04-surfaces/17-guard.md, .abcd/development/brief/04-surfaces/27-implement.md, .abcd/development/brief/04-surfaces/34-build.md, .abcd/development/brief/04-surfaces/39-status.md, internal/core/statusline/compose.go, internal/core/statusline/render.go, internal/core/statusline/render_test.go, internal/core/statusline/defaults/statusline.json, internal/core/statusline/merging_test.go, internal/gitutil/defaultref.go, internal/gitutil/rangebase.go, internal/surface/cli/statusline.go, internal/surface/cli/statusline_merging_test.go, commands/abcd.md, .abcd/development/brief/04-surfaces/26-mode.md, .abcd/development/brief/04-surfaces/08-abcd.md
- tests: internal/core/landing, internal/core/layered, internal/core/implement/loop, internal/core/board, internal/core/guard, internal/core/statusline, internal/ghcontract, internal/gitutil, internal/surface/cli
- The packages line names the files the steps change, not their directories,
so the reservation does not hold unrelated work in the same packages. It
names every file steps 1 to 7 changed, as each was built, the ones a step
reached beyond its own packages line included (step 2's
internal/surface/cli/cli.go, step 4's loop andinternal/ghcontractfiles, step 7'sinternal/core/statusline,internal/core/layeredandinternal/gitutilfiles). Generated output (docs/reference/cli/commands.md,.abcd/development/release/surface.jsonand any other linguist-generated file) is reserved by no one: whoever lands second regenerates it.
Steps
- The declaration:
facilitator.moderead throughinternal/core/layered- packages: internal/core/landing
- tests: internal/core/landing
- Claim the
facilitatornamespace with the one keymode. - The values are
humanandauto; any other value, in any layer, is refused loudly, naming the file. - Bundled default
human;humanfrom any layer wins, read from every layer byLookupandDecode, as land's switches are. - Report the layer that decided.
- The board line
- packages: internal/core/board, internal/surface/cli
- tests: internal/core/board, internal/surface/cli
- The bare
abcdboard names the mode and the layer that set it.
- Review receipts
- packages: internal/core/landing, internal/surface/cli, commands/land.md
- tests: internal/core/landing, internal/surface/cli
- Write one receipt per commit, named by its full sha, in the local tier
beside the preflight receipts. It is found from any worktree the way
landing.Receiptfinds those. - Write it only when the review pair's verdicts for that commit pass the stop rule (no critical or major finding).
- The front door that records it is a decision to make and record in this step, as a sub-verb or a flag on an existing verb, wired to the CLI and the plugin page both.
- The merge gate reads the mode
- packages: internal/core/landing, internal/core/implement/loop
- tests: internal/core/landing, internal/core/implement/loop
- needs: 1, 3
mergeGatebecomes the one functionabcd landand the loop'slandArmask.- Who is a maintainer is one seam: the pull request author's permission
on the repository as the forge reports it (
role_name). Write, Maintain and Admin are maintainers; Read and Triage are contributors; a custom role counts as its base role; a 404 or no role is a contributor. A permission that cannot be read, or a custom role whose base cannot be read, is unverified: the gate arms nothing and says so. Pin the fields ininternal/ghcontractafter a live read-only call. - An approval counts only when it is the latest review of a maintainer
other than the author and approves the head commit, or when the
technical facilitator has recorded their approval of the head commit
through abcd's approval act. That act is a front door to decide and
record in this step (a sub-verb or a flag of
abcd land), wired to the CLI and the plugin page, writing a record for the full head sha in the local tier beside the review receipts; the shell guard refuses it to an agent (step 5). AM1 is retired as the stand-in, but this reading of approvals is kept for the two cases below. - A contributor's pull request arms nothing, in either mode, until a maintainer's approval counts, and says a maintainer's approval is needed.
humanarms a maintainer's pull request by the mirror's method once the technical facilitator's approval counts, and until then arms nothing and says the pull request waits for that approval.autoarms by the mirror's method when a review receipt names the head, and otherwise arms nothing and names the commit with no recorded review pair.- With no queue gating the default branch, an armed merge is auto-merge, and where the forge refuses that, it says so.
- abcd adds no forge approval rule or code-owner file.
- The mode is read from the default branch's committed
.abcd/config.jsonand the machine layer, never from the checkout of the branch being landed, so a branch that commitsauto, or deletes a repository-levelhuman, does not decide the gate for itself (dcn-2610230058333177). Tests: a lane whose own config saysautoover a default branch athumanarms nothing; a lane that deletes the default branch'shumanarms nothing. - A
facilitator.moderefusal is passed throughtermsafe.Sanitizewherever a front door prints it: the board (step 2),abcd landand the loop (here), the guard (step 5). Test: a hostile key carrying terminal control bytes prints escaped.
- The guard's hard rule
- packages: internal/core/guard
- tests: internal/core/guard
- needs: 1, 4
- While the mode is
human,abcd guardrefusesgh pr merge --auto,gh pr mergeaimed at a queue, and a GraphQLenqueuePullRequest. - It reads the mode through the same resolver.
- It names the setting that would allow each command.
- In any mode it refuses the approval act of step 4 when an agent runs it: approving is the person's act.
- abcd's own repository and the docs
- packages: .abcd/config.json, commands/land.md, .abcd/development/brief/04-surfaces/38-land.md
- tests: internal/surface/cli
- needs: 4, 5
- Set
facilitator.mode: autoin abcd's committed config, in the same change as step 4, so its sessions keep queueing. - Make the docs say exactly what each mode does, and who approves.
- The status line
- packages: internal/core/statusline/compose.go, internal/core/statusline/render.go, internal/core/statusline/render_test.go, internal/core/statusline/defaults/statusline.json, internal/core/statusline/merging_test.go, internal/core/landing/config.go, internal/core/layered/layered.go, internal/gitutil/defaultref.go, internal/gitutil/rangebase.go, internal/surface/cli/statusline.go, internal/surface/cli/statusline_merging_test.go, commands/abcd.md, .abcd/development/brief/04-surfaces/26-mode.md
- tests: internal/core/statusline, internal/core/landing, internal/core/layered, internal/gitutil, internal/surface/cli
- needs: 1
- While the mode is
auto, abcd's status-line row says merging is autonomous; underhumanthe row has no such element. It is switchable instatusline.jsonlike the row's other elements. - It reads the mode through the same resolver, asks the network nothing and stays inside the status line's time budget; a refusal drops the element and is said on stderr, as the row's other elements do.
- It reaches the host only through the status line the person consented to; nothing else in the host's settings calls abcd.
Frontmatter
| id | spc-2610161609350616 |
|---|---|
| type | spec |
| lifecycle | closed |
.abcd/development/specs/closed/spc-2610161609350616-a-repository-declares-whether-its.md
open on GitHub ↗ · commit history ↗
References1
- itd-2610150602547271The facilitator's mode gates the mergeimplements
mentions9
- dcn-26102222282518882026-10-22 — The technical facilitator on the product thinker's delegation (ruled by the coordinating session, abcd-ae, 2026-10-22): spc-2610161609350616 (the merge gate) narrows its Footprint packages line from directories to the files its six steps change, because a directory footprint over-reserves and serialises unrelated work (it collided with the worktree store spec spc-2609301811532881 on internal/core/implement/loop and internal/surface/cli). Files still named by both specs (internal/core/board/board.go and its test, internal/surface/cli/cli.go, internal/surface/cli/board.go) are the merge gate's first; the store sequences its steps on them after the merge gate lands. Generated output (docs/reference/cli/commands.md, .abcd/development/release/surface.json and any other linguist-generated file) is reserved by no one and regenerated by whoever lands second.
- dcn-26102223565326382026-10-22 — The merge gate's footprint is step-wise (abcd-ae's ruling as coordinator of 2026-10-23, spc-2610161609350616, itd-2610150602547271, following the worktree store's dcn-2610222227336378): the build reserves at start only the files steps 1-3, 5 and 6 change, leaving out internal/core/implement/loop/land.go and its test, which only step 4 changes and which the drain's reservation of iss-2610120138028442 holds while that lane waits for its preflight, and internal/surface/cli/cli.go, which only step 2 changes and which iss-2610171110154902 and iss-2610171200535090 hold until 2026-10-23T08:40Z; when the step that changes a left-out file begins, the spec's packages line widens to name it and a re-run of abcd build reserves the widened footprint (the verb reserves again when the spec has widened, per commands/build.md), judged for overlap like any reservation. Steps 4 and 6 still land as one change. <!-- record-lint: forward-looking -->
- dcn-26102300042637112026-10-23 — The technical facilitator on the product thinker's delegation (planning step 1 of spc-2610161609350616, abcd-43 for abcd-ae, 2026-10-23): the key stays facilitator.mode, the product thinker's own name for it in itd-2610150602547271, and the clash with abcd mode's facilitator state is settled in wording. abcd mode answers whose answer the loop is waiting on right now; facilitator.mode declares who merges. So the bare board names it by outcome, for example 'merges: a person (facilitator.mode human, from the bundled default)' or 'merges: abcd (facilitator.mode auto, from .abcd/config.json)', never a bare 'mode: human'; and commands/mode.md and commands/land.md each say in one sentence how the two differ.
- dcn-26102300583331772026-10-23 — The merge gate reads the repository's declaration from the default branch, never from the branch being landed (the technical facilitator on the product thinker's delegation, from the lane-1 review pairs of run-2610222359421510, ruled by abcd-ae as coordinator, 2026-10-23): readMode(o.Roots) takes o.Roots.Repo, which is the landing branch's own worktree when abcd land runs there, so a branch that commits facilitator.mode auto, or deletes a repository-level human, would decide the gate for itself. Step 4 therefore resolves the mode from the default branch's committed .abcd/config.json together with the machine layer, human from either winning, and tests both directions. Every front door that prints a facilitator.mode refusal (the board in step 2, land and the loop in step 4, the guard in step 5) passes it through termsafe.Sanitize, as land already does (land.go:124), because the layered reader returns a hostile key's raw bytes.
- dcn-26102308401609142026-10-23 — The technical facilitator on the product thinker's delegation (merge gate step 3, spc-2610161609350616, itd-2610150602547271, 2026-10-23): a review receipt is recorded with a repeatable --record-review <return> flag on abcd land, not a land sub-verb, because land's positional argument is a branch name and a sub-verb such as review would shadow a branch of that name; recording lands nothing. A receipt is written only when at least two returns each name the head's first nine characters and state SHIP or APPROVE, so a review of an earlier head cannot be recorded for a later one, and it lives in .abcd/.work.local/review-receipts/<full sha>, found from any worktree through the same lookup the preflight receipts use. The verdict parser in landing is the shared one the build loop's validator parser can move onto later; landing cannot import the loop.
- dcn-26102802335753542026-10-28 — Merge-gate step 2 (itd-2610150602547271, spc-2610161609350616): until step 4 wires the merge gate to read facilitator.mode, the board names a person as the merger in every repository and draws an auto as set but not yet read, never as abcd merging. Grounds: landing's mergeGate still runs ruling AM1, so every repository is in human mode today, and a board saying abcd merges would contradict what abcd land does in the same repository; the spec has step 2 name the mode and its layer, which the line still does. Step 4 changes the wording when the gate reads the mode. Found by the round-2 review pair.
- dcn-26102915240305702026-10-29 — Merge-gate step 4 (itd-2610150602547271, spc-2610161609350616), decided where the records leave it open, each the way that fails closed. (1) The facilitator's approval act is a flag of land, abcd land --approve, beside --record-review: it writes .abcd/.work.local/approvals/<full sha>, found from any worktree as the receipts are, and counts as an approval of that commit alone, for a contributor's pull request and in human mode alike. (2) A custom role's base is read from the same permission answer's legacy level (admin or write: a maintainer; read or none: a contributor), so no second call is made; any other level, a read that fails other than with 404, or an answer that does not parse is unverified and arms nothing. (3) A reviewer's approval counts only if it is that reviewer's latest submitted review of any state (a later comment or change request supersedes it, a pending draft does not), approves the head commit itself, is not the author's, and its reviewer reads as a maintainer; a page of 100 reviews counts none, since a later review may sit beyond it. (4) With no merge queue gating the default branch the arming is the forge's auto-merge as a merge commit (--merge), and a forge that refuses it is said, not retried. (5) The build loop reads the mode at the default branch's tip as just fetched, not at the lane's base, so a mode turned back to human after a run started holds; a lane the gate leaves open is parked as before and asked again on each step, so an approval or a review receipt given later arms it. (6) The loop mints no review receipt of its own: its pushed head carries the records commit its validators did not read, so in auto a lane arms only once a review receipt names that head, recorded with abcd land --record-review. Decided by the agent building steps 4 to 6 under the orchestrator's delegation, 2026-10-29.
- dcn-26102915241003252026-10-29 — Merge-gate step 5 (itd-2610150602547271, spc-2610161609350616), decided the way that fails closed. While facilitator.mode is human the shell guard refuses every gh pr merge that merges, not only one aimed at a queue: with no queue gating the default branch a plain gh pr merge merges at once, which is the same unapproved merge; only gh pr merge --disable-auto, which withdraws an arming, is let through. Beside the enqueuePullRequest mutation it refuses enablePullRequestAutoMerge, the GraphQL form of gh pr merge --auto. The guard package sits below the resolver (internal/core/landing reads the mode through the layered reader, which imports the guard), so a front door hands the mode in (guard.WithMergeMode, read through landing.ReadDefaultFacilitator); a mode that cannot be read counts as human, and a registry with no mode handed in (the bundled defaults alone) judges no merge command. The approval act, abcd land --approve, is refused in any mode: through the binary by the registry entry abcd-land-approve, which also teaches it, and through go run ./cmd/abcd by the guard's own check, which no pattern can name. A query the guard cannot read (a gh api graphql -F query=@file) is not seen, as a body held in a variable is not. Decided by the agent building steps 4 to 6 under the orchestrator's delegation, 2026-10-29.
- itd-97The Facilitator Is a Mode, Not a Person