Development record
abcd records how each change landed
Press Release
abcd writes down how each change landed. When a change reaches the default branch, abcd records with it whether a person approved it, the drain landed it under its rule, or it merged automatically after its checks passed, and which rule let it through. Anyone can then ask which changes landed with no person involved, and a change that is later undone can be traced to the rule that admitted it.
Why This Matters
Typed links: builds_on
itd-82 (the drain, whose lane
this record names by the rule record it took the issue under) and
itd-2610150602547271
(the facilitator mode, shipped: In auto a maintainer's pull request is armed
only when a review receipt names its head, in human only once an approval of
the head counts, and this record says which of the two admitted each merge).
Blocks
itd-2610301750162657
(the spot-check, whose collection lane draws from this listing at each landing
once a window of time or of landed changes has passed, and whose review lane
reads an item's state when it was drawn and its state now).
Split out of itd-2610301750162657 on 2026-10-30 at the product thinker's ruling in its planning interview: The spot-check of what abcd decided alone waits for this record, and the record is useful on its own.
Today abcd cannot tell, after the fact, how a change reached the default
branch. Which issues a drain lane landed lives only in the drain's run state,
an uncommitted file in one worktree's local tier
(internal/core/implement/loop/drain.go); every merge commit carries the same
author whether a person, a lane, or the merge queue landed it; the facilitator
mode's two admitting facts, the technical facilitator's recorded approval and
the review receipt that arms a merge in auto, are files in the local tier
named by the head they vouch for (internal/core/landing/facilitator.go,
internal/core/landing/review.go); and whether a person approved a pull
request on the forge is known only to the forge. A change later reverted
cannot be traced to the rule that let it through, and nobody can list the
changes that landed with no person involved.
Mechanism
- We expect every change that lands after this ships to be classifiable from
history and the forge with no one's memory, because the lane's rule rides a
trailer on a commit the merge carries into the default branch's history, and
the facilitator mode sits in the committed configuration at the commit the
change merged onto. Shown wrong if
abcd landings, run over the landings since this shipped, lists a lane abcd opened whose rule readsunknown, or a reverted row whose revert cannot be traced to its landing's rule.
Scope Conditions
- A repository whose every change reaches the default branch through a pull request the forge merges (a merge queue or auto-merge) or through a person's direct push; abcd never pushes the default branch.
- The forge is GitHub, read through the
ghclient the landing already uses. - The default branch's merges keep the branch's commits as the merge's second
parent (the
MERGEmethod, as abcd's own queue ruleset sets it); a squash or rebase merge keeps them only on the forge. - The implement loop commits the lane's records on the lane's branch before the lane's preflight runs, and the push is refused until a preflight receipt names the lane's head.
- The facilitator mode (itd-2610150602547271) decides the arming:
autoby a review receipt naming the head,humanby a counted approval of the head; both are files in the local tier named by the head, written before the merge and read by any worktree of the checkout. - Scale: abcd's own repository, about 500 merges on the default branch in the 30 days to 2026-10-30; the listing asks the forge once per pull request.
What's Out of Scope
- Freezing a cycle's rows into the repository at
launch ship. The consumer draws per window, not per release; the release commit is the wrong boundary (the tag is made after the ship pull request merges, so a file in the release commit cannot name the cycle's last landing); and it would add a refusal to the cut. If a trace is ever wanted after the forge no longer holds the repository, it is a later issue against this record. - The drain's hand-backs and the automated facilitator's own decisions: They stay in the drain's run state and in their own records, and the spot-check reads those groups from there.
Acceptance Criteria
Written by the technical facilitator under the delegation of dcn-2610301913469955 (the acceptance criteria of an intent the product thinker asked to plan); each is testable as stated.
- Given a drain lane whose issue the drain took under the repository's
drain rule record, when the loop makes the lane's records commit,
then that commit carries
Admitted-by: drain <rule record id>beside itsResolves:andAssisted-by:trailers, and the attribution gate and the commit-msg hook accept it. - Given a build lane for a spec, when the loop makes the lane's
records commit, then it carries
Admitted-by: build <spec id>; a lane started byabcd build <iss-N>carriesAdmitted-by: build <iss-N>. - Given a lane whose landing marks no step, closes no spec, and resolves
no capture, when the loop reaches its records step, then it still
makes the records commit, changing no file, carrying the
Admitted-by:trailer, and the lane's pushed head is that commit. - Given a run started by the drain or by
abcd build, when its state is written at the start, then it names the admitting rule (admitted: the lane kind and the record id, with the drain rule record's path for a drain's run); given a run state without it, when the records step runs, then it is refused naming the field, and no commit is made. - Given a person's branch landed through
abcd land, when land gates, pushes, and arms it, then no commit has been added to the branch and no trailer written: The preflight receipt, the review receipt, and the recorded approval all name the head the person made. - Given the default branch with merges since the last release tag,
when
abcd landingsruns with the forge reachable, then it prints one row per commit on the default branch's first-parent line, newest first, each with the commit, the pull request number where the message names one, the class (person-approved,drain,automatic, orperson-push), the rule that admitted it, the lane kind, the approval it saw, and the issues and intents the branch'sResolves:andDelivers:trailers name. - Given a merged pull request whose merged head a forge review by someone
other than the author approves, not dismissed, when its row is classed,
then it is
person-approved, names the review and its time, and shows the lane kind whatever it is; the approver's login is read from the forge and printed, and no committed file carries it. - Given a merged pull request whose merged head a recorded approval names
in a worktree of the listing checkout, when its row is classed, then
it is
person-approvedby that record, and the row says the approval was read from this checkout's local tier. - Given a merged pull request with no counted approval seen whose first
parent's committed
.abcd/config.jsonsetsfacilitator.mode: human, when its row is classed, then it isperson-approvedby the mode's rule, with its approval shown asnone seen. - Given a merged pull request under
autowith no counted approval seen whose branch carriesAdmitted-by: drain <id>, when its row is classed, then it isdrainand its rule is that record, by id and path. - Given a merged pull request under
autowith no counted approval seen and no drain trailer, when its row is classed, then it isautomatic, and its rule isfacilitator.mode: autoas.abcd/config.jsonheld it at the commit the change merged onto. - Given a commit on the default branch's first-parent line that is no
merge and whose message names no pull request, when its row is classed,
then it is
person-push, with no rule and no lane kind, and its committer is named from git. - Given a pull request the forge merged by squash or rebase, when its
row is composed, then the branch's commits and their trailers are read
from the forge's listing of the pull request's commits, and with the forge
unreachable the lane kind reads
unknownand the row counts as unattended. - Given
abcd landings --unattended, when it runs, then it lists only the rows classeddrainorautomatic, and a row the evidence cannot place with a person is among them, never left out. - Given a commit reachable from the default branch since the start of the
listed range, on the first-parent line or on a merged branch, whose message
says, in git's own words,
This reverts commit <sha>, whenabcd landings <that sha>orabcd landings <the revert's sha>runs, then it prints the row of the first-parent commit that brought the named commit onto the default branch, with its class and rule, or says the commit is not on the default branch. - Given a revert the forge squashed, so that no commit says
This reverts commit, when its pull request's body saysReverts #N(orReverts <owner>/<repo>#N), then the row of pull request N is marked reverted by it; given neither line, then the revert isuntraceable, and--revertedcounts it in a group of its own. - Given rows whose change a later commit on the default branch reverts,
when
abcd landings --revertedruns, then each is marked with the reverting commit and the rows are grouped by rule with a count per rule. - Given the forge cannot be reached, when the listing runs, then
the class and rule of every row still come from history, each row's
approval reads
forge unread, no row is classedperson-approvedby a forge review, and the command says why. - Given
abcd landings --since <tag or date>, when it runs, then the first-parent line is listed from that tag or from the first commit after that date, and without the flag from the last release tag. - Given a landing from before this shipped, with no
Admitted-by:trailer, when it is listed, then its lane kind readsunknown, its class comes from the approvals and the committed configuration alone, and no history is rewritten to add the trailer. - Given
abcd landings --json, when it runs, then it prints the same rows as JSON with stable keys, andabcd landingsin any form writes nothing outside the local tier.
Decisions
Each is the technical facilitator's, under dcn-2610301913469955, grounded in
how a change lands today (internal/core/landing/land.go,
internal/core/landing/facilitator.go,
internal/core/implement/loop/land.go,
internal/core/implement/loop/pickcommit.go,
internal/core/implement/loop/drain.go):
- The unit of record is the commit on the default branch's first-parent line: The merge commit the forge makes, a squash or rebase commit, or a person's direct push, keyed by its sha. The merge queue makes the merge commit, not abcd (its committer is the forge, its message names the pull request), so nothing is ever written to that commit, and no row depends on it carrying anything but the pull request number and its parents.
- The durable source is written before the change merges, never after. A commit added to the default branch after the merge would be a machine's, which the attribution gate refuses, so the admitting evidence that can be carried is carried on the branch, in history the merge brings in.
- One carrier for every loop lane: the
Admitted-by:trailer on the landing's records commit, which is made on every lane. The records commit is the commit the loop already makes on the lane's branch after the validators and before the preflight receipt and the push (landRecords;landPushrefuses until a receipt names the lane's head), so the trailer costs no second gate run and rides the merge into history under theMERGEmethod. TodaylandPrepareskips the commit when the lane marks no step, closes no spec, and resolves no capture (RecordsDoneset with nothing to do); that case goes: The commit is made anyway, changing no file, as the record-only commit it is. The trailer's values aredrain <rule record id>(the recorddrainrule.Loadnames, whoseRecordis empty only for the bundled baseline, which a drain never runs under) andbuild <spec id>, orbuild <iss-N>for a lane a person started withabcd build <iss-N>. Chosen over the pick commit, which only a picked run's first lane makes (lane.go: The pick's entry is made whenState.Picknames the lane), so a drain lane and abuild <id>lane have none; over a committed file per landing, which would add a file to the tree for every merge; and over a trailer on the merge commit, which abcd cannot write. - The run's state names the rule the lane was admitted under. A new
field on the run's
State,admitted, with the lane kind (drainorbuild), the record id, and for a drain's run the rule record's path, is written atStartfrom what started the run: The drain passes the rule it loaded before opening the lane,abcd buildthe spec or issue it was given.landRecordswrites the trailer from it and never guesses: A state without the field is refused at the records step, naming it. The drain's own state already keepsRule, but the lane's landing reads only the run's state, which is why the field is there. - abcd adds nothing to a person's branch. The preflight receipt, the
review receipt, and the recorded approval each name the head the person
made, and a commit abcd added after them would void all three and send the
branch back through the gate. Nor could such a commit name the approval or
receipt that armed the merge: Both are of the head, and any commit that
names them is a new head they do not name. So a person's branch is classed
from history and the forge, and carries no
Admitted-by:. - What
abcd landingsreads, and from where. History holds the first-parent merges, their messages (the pull request number), their second parents' commits and trailers (Admitted-by:,Resolves:,Delivers:,Assisted-by:), the committed.abcd/config.jsonat the first parent (the facilitator mode as the gate read it at the fetched tip, dcn-2610230058333177), and everyThis reverts commitline. The forge is needed for exactly three things history cannot hold: The pull request's reviews and author (a forge approval of the head by someone other than the author), the branch's commits of a pull request merged by squash or rebase, and theReverts #Nline of a squashed revert's pull request. Two facts neither holds, the recorded approval and the review receipt: Both live in the local tier, named by the head; the listing reads them from the worktrees of the checkout it runs in, as the merge gate does, and a checkout without them reads from history and the forge alone, so a row classed by a recorded approval says so. The forge unreachable: Class and rule still come from history, every row's approval readsforge unread, a squash or rebase merge's lane kind readsunknown, and the command says why. No committed file carries a person's login: A login appears only in a row read live from the forge. - Four classes, decided in this order.
person-push: A first-parent commit that is no merge and names no pull request, since abcd never pushes the default branch.person-approved: A counted approval of the merged head is seen (a recorded approval in the listing checkout's worktrees, or a forge review by someone other than the author), or the committed mode at the first parent ishuman, under which abcd's gate arms nothing without a counted approval and a merge made in the forge's own page is a person's act; the row shows which it saw, ornone seen. Then, underauto:drainwhen the branch carriesAdmitted-by: drain, elseautomatic, whose rule isfacilitator.mode: autoat the first parent; the review receipt that armed it is not recoverable (decision 6), so the row names the mode's requirement, not the receipt.person-approvedrecords an approval the rules keep, not a proof of who approved (dcn-2610300955154851): The recorded approval is a file anything under the person's account can write. The classes hold if the agents get a forge account of their own (itd-2610300955026531): The author becomes the agent's login, the person's forge review then counts as one by someone other than the author, and no class keys on whose login did what. - A person's approval wins over the lane. A drain lane a person approved
is
person-approved, with its lane kind still shown: The spot-check draws from what no person read, and an approval is a person reading. Chosen over ranking the drain first, which would count a read change as unread. - When in doubt, unattended. Any row under
autothe evidence cannot place with a person (no approval seen, lane kindunknown, a machine-layerhumanabcd cannot see afterwards) is listed under--unattended. The listing feeds a spot-check, and the costly error is leaving an unread change out of the draw, not reading one too many. - The listing is a read-only verb,
abcd landings, with--since <tag|date>(the last release tag by default),--unattended,--reverted,--json, and a positional sha to trace one commit. Chosen over a flag onabcd land, which pushes and arms and should not also be the thing a reader runs, and over folding it intoabcd status, which lists announcements that lapse within the hour. - The forge is read, never written. The approvals come through the client the landing already uses, with the pull request query extended to the reviews it keeps. No comment, label, or commit status is posted to mark abcd's arming: The forge cannot tell abcd's arming from the person's, both made under the person's login, and the classes do not need it.
- A revert is traced through git's own words, over every commit in the
range. The trace walks every commit reachable from the default branch
since the start of the listed range, second parents included, and matches
This reverts commit <sha>, the line RS001's revert withdrawal already reads; the named commit is followed along the first-parent line to the merge that brought it in, then to that row and its rule, and a revert of a revert the same way. A revert the forge squashed carries no such line, so it is matched byReverts #Nin its pull request's body, the line the forge's own revert writes, and is otherwiseuntraceable, counted in a group of its own under--reverted, which groups the rest by rule. - No backfill. Landings before this ships carry no trailer and are
classed from the approvals and the configuration alone, lane kind
unknown; history is never rewritten. - Naming the reviewers per change stays with itd-2610240922461281,
which records each review's findings per reviewer. A row carries the
Assisted-by:values the branch's commits already name; it does not duplicate the review record. - Impact: additive. A new trailer, a new run-state field, and a new read-only verb; nothing existing users rely on changes.
- Ruled by the product thinker on 2026-10-30: A person's own branch
merged in
automode with no approval seen counts as landing with no person involved, so--unattendedlists it and the spot-check can draw it. Chosen over keeping--unattendedto the drain's lanes and the branches no person's session directed: Such a branch is mostly written by an agent in a session the person directs, and nobody reads it before it merges. Every landing is recorded either way.
Open Questions
Settled under dcn-2610301913469955 (2026-10-30): The unit of record, the
carrier and the run-state field, the classes and their evidence, what the
forge is asked for, the verb and its flags, and the revert trace, each in
## Decisions.
- resolved: whether a person's own branch, merged in
automode with no approval seen, counts as landing with no person involved: It does, by the product thinker's ruling of 2026-10-30 (decision 16).
Audit Notes
Empty. Populated by intent-auditor when intent moves to shipped/.
Grounds
- pursued: abcd's agents merge changes no person reads, and nothing afterwards says which rule let each one through; we expect writing the admitting rule where the merge carries it, with the mode in the committed configuration at the merge, to make every landing classifiable from history and the forge without anyone's memory, and we are wrong if landings made after this ships still read unknown for a lane abcd opened, or a reverted one cannot be traced to its rule (taken by the technical facilitator under dcn-2610301913469955).
Frontmatter
| id | itd-2610301850454860 |
|---|---|
| type | intent |
| lifecycle | planned |
| kind | standalone |
| severity | minor |
.abcd/development/intents/planned/itd-2610301850454860-abcd-writes-down-how-each-change-landed.md
open on GitHub ↗ · commit history ↗
References2
- itd-82`abcd drain` fixes the issues that need no decision, and hands the rest back by kindbuilds on
- itd-2610150602547271The facilitator's mode gates the mergebuilds on
Referenced by2
- itd-2610301750162657abcd's unattended work is spot-checked against specs and intentsblocks
- spc-2610301950480609abcd records how each change landedimplemented by
mentions6
- 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-26103009551548512026-10-30 — Merge approval and pull-request check rulings. Human mode's merge approval is a rule agents keep, not a proof of who approved: it stops an agent merging by mistake, not one set on getting round it, abcd says so wherever it describes the mode, and no proof mechanism enters this release. The person approves in the terminal through an abcd command (abcd land --approve today, or a dedicated verb), so the current act stands; the dashboard or a web app is one more channel for the same approval (itd-2610300955049260), not a gap. Agents working under a forge account of their own is optional work, designed from current practice to run in the cloud and locally, through an API key or a subscription (itd-2610300955026531). Requiring a security key touch for an approval is later work (itd-2610300955031405). The three checks every pull request runs, the change labeller, the dependency review and the vulnerability scan, stay non-blocking, and a failure in any of them is fixed without being asked until it passes.
- dcn-26103019134699552026-10-30 — The product thinker ruled on 2026-10-30 that they are asked only the questions they need to answer, the design decisions with an obvious trade-off, and that the technical facilitator settles everything else that is sensible. From then on the technical facilitator settles routing, typed links, wording that follows from settled rulings, review findings the record settles and the acceptance criteria of an intent the product thinker asked to plan, and records each such decision with its reason; a design choice with an obvious trade-off is still put to the product thinker. The same day the product thinker asked for iss-2610301855200679 to be planned as an intent, which is their sign-off for planning itd-2610230756259215.
- itd-2610240922461281The review set is a setting, in model classes
- itd-2610300955026531Agents can work under a forge account of their own
- spc-2610301925429250abcd's unattended work is spot-checked against specs and intents