Skip to content

Landing trains: land.cs --train, and agent review admits a train of approved heads (#277) - #281

Open
Hafeok wants to merge 23 commits into
mainfrom
claude/nice-mendel-495j4y-step1
Open

Hafeok wants to merge 23 commits into
mainfrom
claude/nice-mendel-495j4y-step1

Conversation

@Hafeok

@Hafeok Hafeok commented Nov 11, 2026 •

Copy link
Copy Markdown
Owner

Completes: #277

What this changes, and why

Step 1 of the chain-builds work: ADR 0243 point 10, LandingTrains. It is stacked on #280 (step 0, the ADR), which stacks on #261 → #260 → #259. Its own commits are the last four: af72d1d, 12cc6c1, a0375e0 and the record.

Today each landing costs one full CI run and moves main under every other pull request. A train lands several approved heads after one run.

  • eng/land.cs -- --train N1 N2 …
    • Refuses before building anything when:
      • a member's head has no approve for its exact sha. A single landing only warns; a train refuses, because its verdict rests on each member's own;
      • a member's head is not green on every check ruleset 1 requires;
      • a member's Completes: line is unmet by its own commits (land.cs: list the issues a landing closes, and refuse a PR whose declared issue has no closing trailer #209, per member);
      • the train has fewer than two members;
      • more than one member adds a release descriptor, or the one that does is not last;
      • a descriptor is dated more than three days before landing.
    • Builds the train by git merge-tree --write-tree and git commit-tree, onto main, in the order given. Each merge carries Landing-only: merge and is dated at the newest commit it joins, so building twice gives the same head. A conflict refuses the train and prints the train without the member at fault.
    • Pushes to land/train-N1-N2-…, or to land/release-<version> when the last member carries the descriptor, so the release dry run still runs.
    • --main verifies land/<name> before it moves anything: the first-parent line down to main, each merge's trailer and tree, and the second parents equal to the members' current heads. Then it fast-forwards main and checks every closing issue is closed. When the push to main is refused, it prints the train without its last member.
    • --delegated-by <who> adds ADR 0239 point 4's trailer to the merges.
  • eng/agent-review.cs admits a train on a land/ push, by the same rule in main's own copy. Every merge's second parent must be an approved pull request's head, by the existing check-run test. On a push, the workflow checks out main with its whole history (fetch-depth: 0), so that git can read the pushed merges as data. This only takes effect once it is on main.
  • Docs: AGENTS.md's landing section, Commands, and the Landing / After landing bullets. docs/releases.md §3: a release rides last in a train, and the head guard holds for a train's head.

The release workflow, dco, issue-refs and release-pending need no change. They skip merges, or read agent review's run.

What was measured

  • land.cs --fixtures tests/fixtures/land: 22 cases, 0 judged wrong. There were 12 before. The ten new train cases are built for real, in a scratch bare repository.
    ok   pass-train-two-members.json: the train #11, #12 is 1cff507ebbf6, 2 `Landing-only: merge` merges (Merge #11 into the train for main; Merge #12 into the train for main), built twice the same, land/train-11-12
    ok   fail-train-merge-conflicts.json: #13 conflicts with the train before it (#11, #12 on main) in README.md; a train's merge is never resolved by hand. land: the train without #13: dotnet run eng/land.cs -- --train 11 12
    ok   fail-train-merge-edited-by-hand.json: … 's tree is not `git merge-tree` of its parents: it was resolved or edited by hand
    
  • The failure path: a pass- train case copied as fail- gave 23 cases, 2 judged wrong, exit 1.
  • agent-review-tamper: every part holds, including four new train cases:
    • approved members: success;
    • a member that is no approved head: failure;
    • a merge edited by hand: failure;
    • a merge without the trailer: failure.
  • A real dry run, land.cs -- --train 266 265 --print, refused both members, each for no approval of its head, checks still running, and no Completes: line.
  • licence-headers, land, issue-refs and dco pass. The full test pipeline is left to CI.

What was found

  • A train would have been red on agent review without the judge's change. It admitted a land/ head only when the head is an approved pull request's head. This was in the plan on Landing trains: several approved heads land in one run #277.
  • The release workflow needs no change: its head guard reads the agent review run, and the land push now posts that run on a train's head.

What is left

Head: 440a0cf82bba.


Checklist

  • Issue referenced: Refs #277 on each commit, and Closes #277 on the docs commit.
  • ADR cited: ADR 0243 point 10 (filed unaccepted in ADR 0243: chain builds ahead of main, and landing trains (#230, step 0; filed unaccepted) #280); ADRs 0087 and 0088 applied.
  • Specification updated, or n/a: docs/releases.md §3.
  • API baseline updated, or n/a: n/a.
  • Conformance baseline updated, or n/a: n/a.
  • Tests: fixtures on every eng/ci.cs run; tamper cases.
  • A new gate's failure path was proven by running it: quoted above.
  • Signed off: by the commit-msg hook.
  • A traceability record exists: docs/traceability/2026-11-11-issue-277-landing-trains.md, plus the session close-out docs/sessions/2026-11-chain-builds.md.
  • dotnet run eng/ci.cs passes locally: only the process gates and fixtures were run.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NbWSYNzDrbT3ENstGnmH3N

Hafeok and others added 20 commits November 10, 2026 13:29
…fier is allowed

The maintainer's ruling of 2026-10-27, which #169's gate implements,
reverses the commit-message half of ADR 0033's amendment of 2026-10-02.
A reversal is a change of meaning, so under ADR 0068 point 2 it is a
supersession: ADR 0241 supersedes that one ruling, and
ModelNamedInTheRecordNotTheCommit moves to 0241's set under its key,
filed unaccepted (ADR 0066). 0033's Status names the supersession, and
AGENTS.md (Every commit, Never) and CONTRIBUTING.md state the ruling.
The record still names the tool and the model exactly.

Refs #257
Refs #169

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk>
A section after Every commit: work starts from an issue, decisions and
blockers go on it when they arise, found work is filed at once, the pull
request lists every issue it touches, closings are checked after landing,
and abandoned work is closed as not planned. As the prompt's note of
2026-11-10 asks, "the session" in Landing and After landing is whichever
session lands: the delegate lands and checks (ADR 0239), and a cloud
session posts its head, lists the issues and asks.

Refs #257

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk>
The prompts, the plan's correction from an amendment to ADR 0241's
supersession, what changed and what was found (#258). The model is left
for the maintainer to fill in, as ADR 0033 provides.

Closes #257
Refs #169

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk>
…in moves

At both steps land.cs lists every issue a Closes, Fixes or Resolves
trailer line in main..head closes, with its title, recognising a trailer
as eng/issue-refs.cs does. A pull request's Completes: line is required:
missing, malformed, or naming an issue no pushed trailer closes, the
landing is refused, and a body it cannot read is refused too rather than
skipped. A release descriptor's trailer is a commit's trailer, so it
counts. After --main moves main, an issue a pushed trailer names that is
still open is printed with its commit and the exit is 1; GitHub unread
is 2. It closes nothing itself. Nine fixtures under tests/fixtures/land/,
run by eng/ci.cs's land-fixtures job.

Refs #209

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk>
The template opens with `Completes: none` and says what the line is and
that it is not a closing keyword (#108). AGENTS.md's Landing and After
landing bullets name the line, land.cs's refusal and its check after
main moves, so the rule and the script say the same thing.

Refs #209

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk>
…landing

Closes #209
Refs #257

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk>
…ogether

Under ADR 0224 point 2 the SBOMs are created at the descriptor's date, so
the two move together. --sbom runs eng/sbom.cs created at the current UTC
day (or --today), replaces the sbom: block with its sbom.txt and sets
date: to that day in one edit, then regenerates CHANGELOG.md. --from
takes SBOMs already written and refuses them unless each was created on
that day; --out says where eng/sbom.cs writes. A tagged version and a
descriptor with no sbom: block are refused. --draft points at it.

Gates: four fixtures under tests/fixtures/release-sbom/, run by eng/ci.cs's
release-sbom-fixtures job, and a ci.yml step that rewrites the newest
format-2 descriptor, renamed and backdated, in a scratch directory and
requires every SBOM created at the new date and eng/sbom.cs --descriptor
to match.

Refs #248

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk>
…fore landing

At both steps, each release descriptor the head adds or changes is judged
against the landing day (UTC): a date: more than three days earlier, or
none, is refused, printing the release.cs --sbom command that writes the
digests and the date again. Three fixtures join tests/fixtures/land/: four
days refused, three days passing, no date refused.

Refs #248

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk>
docs/releases.md rule 3, amended 2026-11-10 with the old text struck and
kept, and rule 11's "written, not typed" naming release.cs --sbom. ADR 0102
gets a dated amendment, add-only, saying the same, since rule 3 restates its
line on date; no ruling changes, and ADR 0224 is untouched. AGENTS.md's
close-out names the command and land.cs's refusal.

Refs #248

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk>
…s its digests

Closes #248

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk>
The three pull requests and their order, ADR 0241 and ADR 0102's
amendment, the issues opened, closed and touched, the fixture counts, the
questions for the maintainer, and #258 deferred.

Refs #257
Refs #209
Refs #248

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk>
Brings in what landed since 81fba3d, the Web UI among it, so that the
workflows main runs on the pull request find what they build.

Refs #257

Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk>
Refs #209

Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk>
Refs #248

Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk>
The maintainer's decision on #230 (accepted as written on 2026-11-06),
with his review of 2026-11-11 folded in: the four open questions settled
(a reserved header byte as the unstable-build marker, stable tags cut by
a workflow on request, packages and SBOMs only, the chain# namespace)
and seven additions (landing trains, a pinned gate, bounded trial
merges, stable implies accepted, no squash for chain branches, ADR
numbers at landing as a step of its own, chain branches opt-in). Each is
marked as settled in review, and a table lists every difference from the
issue's text.

Thirteen keys, filed unaccepted (ADR 0066). Dated amendments to 0088
(main moves by fast-forward, a merge commit made on a branch counts, and
a train is one checked head) and to 0102 (chain builds are outside a
release's scope, and the release workflow refuses chain/*).

Numbered 0243: open pull requests hold 0240, 0241 (twice, #278) and 0242.

Refs #277

Closes #230

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NbWSYNzDrbT3ENstGnmH3N
Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk>
Refs #230

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NbWSYNzDrbT3ENstGnmH3N
Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk>
`dotnet run eng/land.cs -- --train N1 N2 ...` merges the members' heads
onto main in the order given, each by `git merge-tree --write-tree` and
`git commit-tree`, in commits carrying `Landing-only: merge`, dated at
the newest commit they join so that the same members on the same main
give the same head. It pushes the head to land/train-N1-N2-..., or to
land/release-<version> when the last member carries the descriptor.

Refused before anything is built: fewer than two members; a member
whose head has no approval for its exact sha, or is not green on its own
(every check ruleset 1 requires); a member's Completes: line unmet by its
own commits; two descriptors, or one not last; a descriptor dated over
three days before landing. A conflict refuses the train and prints the
train without the member at fault. `--main` verifies land/<name> is a
train onto main of exactly the members' current heads, each merge
reproducible, and fast-forwards main; when refused it prints the train
without its last member. The closing check runs over every member.

Ten train fixtures, built for real in a scratch bare repository, run by
the land-fixtures job: 22 cases, 0 judged wrong.

ADR 0243 point 10, LandingTrains.

Refs #277

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NbWSYNzDrbT3ENstGnmH3N
Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk>
On a land/ push whose head is not an approved pull request's head,
agent review now admits a train: a head whose first-parent line down to
main is only two-parent commits carrying `Landing-only: merge`, each
one's tree the `git merge-tree` of its parents, and each one's second
parent an approved pull request's head by the same check-run test as
before; two or more of them. The approvals stay the members' own, and a
merge resolved or edited by hand fails.

The judge reads the pushed history with git, so on a push the workflow
checks main out with its whole history (fetch-depth 0): data, never
checked out, built or run. The tamper test builds a train in a scratch
repository and proves four cases: approved members pass; a member that
is no approved head, a merge edited by hand, and a merge without the
trailer each fail.

This takes effect once it is on main, because the judge is main's copy.

ADR 0243 point 10, LandingTrains.

Refs #277

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NbWSYNzDrbT3ENstGnmH3N
Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk>
AGENTS.md's landing section and commands say how several approved heads
land as one train and what refuses one; the Landing and After landing
bullets say the closing checks run per member. docs/releases.md §3 says
a release rides in a train as its last member, at most one per train,
and that the head guard holds for a train's head.

Refs #277

Closes #277

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NbWSYNzDrbT3ENstGnmH3N
Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk>
Refs #277
Refs #230

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NbWSYNzDrbT3ENstGnmH3N
Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk>
Hafeok and others added 3 commits November 11, 2026 12:35
…nder

The maintainer's review of #281, 2026-11-11: land.cs and agent review
run git merge-tree under the same settings. Attributes come from the
train so far, never from a working tree, with the global and system files
off; renames are fixed; a repository-local info/attributes refuses the
train. A tree mismatch from a different git refuses the train too.

`git merge-tree` reads the working tree's .gitattributes: a merge=union
attribute there turned a conflict into a clean merge in a test, so the
lander's checkout and the judge's could otherwise disagree.

Refs #230
Refs #277

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NbWSYNzDrbT3ENstGnmH3N
Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk>
Brings ADR 0243's pinned merge settings, which this branch implements.

Refs #277

Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk>
…amper cases

land.cs (Trains.MergeTree) and agent review (PinnedMergeTree) run git
merge-tree under the same settings, stated once in each: attributes from
the train so far (--attr-source of the first parent), never a working
tree, with the global and system attribute files off; renames on,
directory renames off, rename limits fixed. A repository-local
info/attributes refuses the train in both, because no option switches it
off. A tree mismatch says the train may have been built by a different
git, and to build it again.

Without the pin, git merge-tree read the checkout's .gitattributes: a
merge=union there turned a conflict into a clean merge.

The tamper test gains two cases, a commit between the merges on the
first-parent line and a train built on a main that has since moved, and
every train failure case now checks its reason. land-fixtures gains three:
attributes read from the train's tree, info/attributes refused, and a
failed two-member train printing the plain landing command for the one
left (the maintainer's ruling on #277). 25 cases, 0 judged wrong.

ADR 0243 point 10, LandingTrains.

Refs #277

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NbWSYNzDrbT3ENstGnmH3N
Signed-off-by: Emil Okkels Klein <emil@okkels-klein.dk>

Hafeok commented Nov 11, 2026

Copy link
Copy Markdown
Owner Author

What the tamper cases cover

The four train cases in the first head (440a0cf):

# Case Expected
1 A train of two approved heads success
2 A member whose head is no approved pull request's head failure
3 A merge whose tree was edited by hand (a file added) failure
4 A merge without the Landing-only: merge trailer failure

Neither of your two cases was among them, so both are added at d1c6d99:

# Case Fails because
5 An extra commit on the first-parent line between the merges … is not a merge of two parents
6 A train built on a main that has since moved (the stand-in's main is a commit after the one the train was built on) the walk passes the old main: … is not a merge of two parents, and main is not below it on its first-parent line

Every failure case now also requires its own reason in the judge's output, so a case cannot pass by failing for something else. All six hold under main's copy.

Also in d1c6d99:

  • the pinned merge settings, the same in land.cs and agent-review.cs (detail on Landing trains: several approved heads land in one run #277);

  • three land fixtures:

    • attributes read from the train's tree;
    • a local info/attributes refused;
    • a failed two-member train printing the plain landing command.

    land-fixtures: 25 cases, 0 judged wrong.

New head: d1c6d9911eb1. Step 0 (#280) moved to d6ffddca8f50 for the ADR's line on the pinned merge, and was merged forward into this branch (cda68dc), not rebased.

The full eng/ci.cs is running on both heads; I will post the results here and on #280.


Generated by Claude Code

Hafeok commented Nov 11, 2026

Copy link
Copy Markdown
Owner Author

dotnet run eng/ci.cs at d1c6d9911eb1: all 66 jobs pass, exit 0, 17 min 6 s

Run in this branch's checkout, with the W3C submodules initialised, on a 4-core Linux container. This is the whole pipeline: no --only, nothing skipped.

  • Process gates:
    • issue-refs: 19 commits reference an issue, and every closing keyword is a trailer;
    • dco: 19 commits checked, 4 merges skipped;
    • decision-sets and the other process gates pass.
  • Fixtures:
    • land-fixtures passes: 25 cases, 0 judged wrong;
    • agent-review-tamper passes, with its six train cases, each failing for its own stated reason;
    • land passes.
  • Build: restore, build (warnings as errors) and build-benchmarks pass.
  • Tests: all 29 test-* jobs pass, test-store taking 349 s.
  • Ratchet: conformance passes.
  • Packaging: pack and decision-report pass.

Test counts:

  • land-fixtures: 12 → 25 cases;
  • tamper train cases: 0 → 6;
  • no test project changed.

Not run here: the CI-only legs, which are GitHub's. agent review's train path takes effect only once it is on main.


Generated by Claude Code

This branch was successfully deployed

1 active deployment
gates — d1c6d991 Deployed Nov 11, 2026 by Hafeok via agent review (judge) #1117
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant