feat(lanetree): classify no-author conflicts as resolvable rather than fatal #165

Open
triform-admin wants to merge 2 commits from agent/lane-generated-tree-resolver into main

What

Adds the decision half of a lane conflict resolver: which conflicting paths the
lane may resolve automatically, and which it must eject. Pure logic + tests; it
is not yet wired into materialize, so this changes no lane behaviour.

Why

Ejections were being recovered by a janitor loop, not prevented. Some are not
anybody's conflict at all:

  • Racing image pins (the big one). A bot rewrites image tags on trunk
    continuously — 105 auto-bake commits on dev in three days (~35/day)
    while enrolled PRs carry their own bake of the same manifests. Two bakes on
    the same newTag/digest lines conflict by construction. Mean PR survival
    (~37 min) is roughly the gap between bakes.
  • Committed codegen output, which is a pure function of sources that merged
    cleanly. tf-multiverse already resolves this class on promotion —
    scripts/promote-parity calls generated trees "the only auto-resolvable
    conflict class"
    — but the lane never did.

Measured, not assumed

git merge-tree against live dev over 80 open PRs; 27 carry a true conflict.

rule conflicting PRs resolvable
codegen only 3 (11%)
+ racing-pin 14 (51%)

Ground truth on the four admin-app.yaml conflicts: each is a single
image: line
— one dev-side digest vs four different PR-side digests, zero
other content. And 8 consecutive bake commits changed zero lines outside
newTag/digest/image/artifact-URL.

An earlier estimate of 85% was wrong — it counted changed files rather
than conflicting ones. The real leaders are hand-written source (49%) and
deployment pins (26%); codegen is 14%. The corrected numbers are recorded in
the module header so the next reader doesn't inherit the mistake.

Fail closed

  • Every path in a conflict set must qualify; one unknown path ejects the whole
    member, exactly as today.
  • portal/src/generated/ is excluded despite its name — it has no
    generator and is a hand-maintained fork (337 files); pinning it would discard
    human edits.
  • CAPABILITIES.md / circles meta have checkers but no confirmed regenerator,
    so they eject.
  • The rev-stamp filters check-codegen-drift.sh uses are carried here too;
    without them a byte-identity assertion reports false drift on every
    regenerated PR — ejecting exactly the population this saves.

Tests

14 new (662 total, 0 failures), including the known-negatives that matter:
hand-written source still ejects, a hand-edited replica count in a bake-written
manifest still ejects, an empty/unreadable hunk still ejects.

Not in this PR

Wiring into LaneTree::materialize at the existing conflict site
(lanetree.rs:162-182, where unmerged_paths is already read before
merge --abort), and the regeneration step itself.

Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com

## What Adds the decision half of a lane conflict resolver: which conflicting paths the lane may resolve automatically, and which it must eject. Pure logic + tests; it is **not yet wired into `materialize`**, so this changes no lane behaviour. ## Why Ejections were being recovered by a janitor loop, not prevented. Some are not anybody's conflict at all: - **Racing image pins (the big one).** A bot rewrites image tags on trunk continuously — **105 `auto-bake` commits on `dev` in three days (~35/day)** — while enrolled PRs carry their own bake of the same manifests. Two bakes on the same `newTag`/`digest` lines conflict by construction. Mean PR survival (~37 min) is roughly the gap between bakes. - **Committed codegen output**, which is a pure function of sources that merged cleanly. tf-multiverse already resolves this class on promotion — `scripts/promote-parity` calls generated trees *"the only auto-resolvable conflict class"* — but the lane never did. ## Measured, not assumed `git merge-tree` against live `dev` over 80 open PRs; 27 carry a true conflict. | rule | conflicting PRs resolvable | | --- | --- | | codegen only | 3 (11%) | | + racing-pin | **14 (51%)** | Ground truth on the four `admin-app.yaml` conflicts: each is a **single `image:` line** — one dev-side digest vs four different PR-side digests, zero other content. And 8 consecutive bake commits changed **zero** lines outside `newTag`/`digest`/`image`/artifact-URL. **An earlier estimate of 85% was wrong** — it counted *changed* files rather than *conflicting* ones. The real leaders are hand-written source (49%) and deployment pins (26%); codegen is 14%. The corrected numbers are recorded in the module header so the next reader doesn't inherit the mistake. ## Fail closed - Every path in a conflict set must qualify; one unknown path ejects the whole member, exactly as today. - `portal/src/generated/` is **excluded despite its name** — it has no generator and is a hand-maintained fork (337 files); pinning it would discard human edits. - `CAPABILITIES.md` / circles meta have checkers but no confirmed regenerator, so they eject. - The rev-stamp filters `check-codegen-drift.sh` uses are carried here too; without them a byte-identity assertion reports false drift on every regenerated PR — ejecting exactly the population this saves. ## Tests 14 new (662 total, 0 failures), including the known-negatives that matter: hand-written source still ejects, a hand-edited replica count in a bake-written manifest still ejects, an empty/unreadable hunk still ejects. ## Not in this PR Wiring into `LaneTree::materialize` at the existing conflict site (`lanetree.rs:162-182`, where `unmerged_paths` is already read before `merge --abort`), and the regeneration step itself. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Some lane ejections collide only in committed codegen output — files that
are a pure function of sources that merged cleanly. tf-multiverse already
resolves this class on promotion (scripts/promote-parity calls generated
trees "the only auto-resolvable conflict class"); the lane never did.

This adds the decision half: a fail-closed allowlist of paths PROVEN
reproducible by scripts/ci/check-codegen-drift.sh, plus the two rev-stamp
filters that guard applies — without them a byte-identity assertion reports
false drift on every regenerated PR, i.e. ejects exactly the population this
saves.

Scope is deliberately narrow and measured, not assumed. Over 80 open PRs,
27 carry a true conflict; 8 of their 57 conflicting paths (14%) are
regenerable and 3 PRs (11%) conflict only in such files. An earlier estimate
of 85% counted changed files instead of conflicting ones and was wrong — the
real leaders are hand-written source (49%) and deployment pins (26%).

portal/src/generated/ is excluded on purpose: it has NO generator and is a
hand-maintained fork, so pinning it would discard human edits. CAPABILITIES.md
and circles meta have checkers but no confirmed regenerator, so they eject.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
feat(lanetree): resolve racing image-pin conflicts newer-wins
All checks were successful
ci / rust-analyzer latency harness (S1 / AC#2) (pull_request) Has been skipped
ci / fmt (pull_request) Successful in 1m17s
ci / build (pull_request) Successful in 2m57s
ci / test (pull_request) Successful in 3m39s
ci / clippy (pull_request) Successful in 3m49s
ci / lane policy mutation proof (pull_request) Successful in 7m20s
2c648ce0f6
The bigger no-author conflict class is not codegen — it is deployment pins.
A bot rewrites image tags on trunk continuously (105 auto-bake commits on dev
in three days, ~35/day) while enrolled PRs carry their own bake of the same
manifests. Two bakes on the same newTag/digest lines conflict by construction,
and mean PR survival (~37 min) is roughly the gap between bakes.

Verified safe before trusting it: 8 consecutive bake commits on dev changed
ZERO lines outside newTag/digest/image/artifact-URL. And the four real
admin-app.yaml conflicts measured via merge-tree are each a single `image:`
line — one dev-side digest against four different PR-side digests, no other
content. Newer-wins loses nothing but a stale tag.

The rule is narrow on purpose: a conflict qualifies only if EVERY conflicting
line is a pin marker AND the file is a manifest a bake writes. A hand-edited
replica count in the same file is a real conflict and still ejects
(a_hand_edit_in_the_same_manifest_still_ejects).

Measured over the same 80-PR sample: coverage of conflicting PRs goes from
11% (codegen alone) to 51%. What still ejects is genuine work — lint.rs,
test files, docs — which is the correct verdict.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
triform-admin force-pushed agent/lane-generated-tree-resolver from 2c648ce0f6
All checks were successful
ci / rust-analyzer latency harness (S1 / AC#2) (pull_request) Has been skipped
ci / fmt (pull_request) Successful in 1m17s
ci / build (pull_request) Successful in 2m57s
ci / test (pull_request) Successful in 3m39s
ci / clippy (pull_request) Successful in 3m49s
ci / lane policy mutation proof (pull_request) Successful in 7m20s
to 275c5c32e0
All checks were successful
ci / build (pull_request) Successful in 1m42s
ci / test (pull_request) Successful in 1m24s
ci / fmt (pull_request) Successful in 52s
ci / clippy (pull_request) Successful in 1m18s
ci / rust-analyzer latency harness (S1 / AC#2) (pull_request) Has been skipped
ci / lane policy mutation proof (pull_request) Successful in 4m11s
2026-08-21 08:26:21 +00:00
Compare
triform-admin force-pushed agent/lane-generated-tree-resolver from 275c5c32e0
All checks were successful
ci / build (pull_request) Successful in 1m42s
ci / test (pull_request) Successful in 1m24s
ci / fmt (pull_request) Successful in 52s
ci / clippy (pull_request) Successful in 1m18s
ci / rust-analyzer latency harness (S1 / AC#2) (pull_request) Has been skipped
ci / lane policy mutation proof (pull_request) Successful in 4m11s
to a4683b1876
All checks were successful
ci / build (pull_request) Successful in 1m53s
ci / test (pull_request) Successful in 1m44s
ci / fmt (pull_request) Successful in 38s
ci / clippy (pull_request) Successful in 51s
ci / rust-analyzer latency harness (S1 / AC#2) (pull_request) Has been skipped
ci / lane policy mutation proof (pull_request) Successful in 3m18s
2026-08-23 15:57:09 +00:00
Compare
triform-admin force-pushed agent/lane-generated-tree-resolver from a4683b1876
All checks were successful
ci / build (pull_request) Successful in 1m53s
ci / test (pull_request) Successful in 1m44s
ci / fmt (pull_request) Successful in 38s
ci / clippy (pull_request) Successful in 51s
ci / rust-analyzer latency harness (S1 / AC#2) (pull_request) Has been skipped
ci / lane policy mutation proof (pull_request) Successful in 3m18s
to 68dfc79fad
All checks were successful
ci / build (pull_request) Successful in 57s
ci / test (pull_request) Successful in 1m5s
ci / lane policy mutation proof (pull_request) Successful in 3m13s
ci / fmt (pull_request) Successful in 46s
ci / clippy (pull_request) Successful in 45s
ci / rust-analyzer latency harness (S1 / AC#2) (pull_request) Has been skipped
2026-08-24 15:10:19 +00:00
Compare
All checks were successful
ci / build (pull_request) Successful in 57s
ci / test (pull_request) Successful in 1m5s
ci / lane policy mutation proof (pull_request) Successful in 3m13s
ci / fmt (pull_request) Successful in 46s
ci / clippy (pull_request) Successful in 45s
ci / rust-analyzer latency harness (S1 / AC#2) (pull_request) Has been skipped
This pull request can be merged automatically.
This branch is out-of-date with the base branch
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin agent/lane-generated-tree-resolver:agent/lane-generated-tree-resolver
git switch agent/lane-generated-tree-resolver

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch main
git merge --no-ff agent/lane-generated-tree-resolver
git switch agent/lane-generated-tree-resolver
git rebase main
git switch main
git merge --ff-only agent/lane-generated-tree-resolver
git switch agent/lane-generated-tree-resolver
git rebase main
git switch main
git merge --no-ff agent/lane-generated-tree-resolver
git switch main
git merge --squash agent/lane-generated-tree-resolver
git switch main
git merge --ff-only agent/lane-generated-tree-resolver
git switch main
git merge agent/lane-generated-tree-resolver
git push origin main
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
triform/cargoless!165
No description provided.