ADR-068: Source repairs are committed on the branch that authored them; generated views on the default branch only
Status Active · Version 2 · Filed 2026-09-03 · Issue #141
Context
ADR-029 settled that a generated view is a committed artifact and that a
generation job is the better thing to commit it. The job it shipped runs
on every pull request as well as on push to main, committing regenerated
views onto the branch as the bot. That closed the staleness gap and opened
a different one.
Every branch that files a decision or a devlog entry regenerates the
decision index, the tag pages and the devlog book, and commits its own
copy. Two concurrent branches therefore carry two versions of the same
generated file, each correct for its own sources. When the first merges,
the second conflicts — on files no person wrote. Measured on the #141
stack: six merges, each conflicting on the same three to five generated
views, each resolved the same way: take either side, run luria index,
commit. Never a judgement, always a chore, and one that a contributor
without the habit resolves by hand, which is how a generated file drifts
from its generator.
The sources never conflict; ADR-002 saw to that with fragments. The views conflict because they are committed where branches live.
The job makes a second kind of write, and it has the opposite shape. A
source repair is a write to a source the record already implies: the
fixer’s links (ADR-005), a journal entry’s created: filled from its path
(#33), a superseded document’s successor, once that field exists. Each
touches only files the branch itself authored — the entry it filed, the
prose it wrote — so two branches never meet on one. Until now luria index made both kinds of write in one pass, so they were committed
together, in one place, and that place had to be wrong for one of them.
Decision
Repairs and views are two commands, committed at two points.
luria repairwrites every mechanical source repair — each one a stateluria lintreports with this command as its remedy, so the two cannot disagree about what counts. It is idempotent: a second run changes nothing.luria indexwrites views and nothing else.- On push to the default branch, the generation job runs
luria concretize,luria repair,luria index, commits the whole diff as the bot in one commit and pushes, and the lint job reads that commit through theneeds:+ SHA handoff. One bot commit, as before. - On a pull request, one job checks out the head branch and runs the
generate action with
views: "false":luria repair, its diff committed and pushed onto the branch; then the lint action, on the sources. No view is written. A repair lands where the review reads it and the author’s next pull carries it. A view is never committed on a branch, so two branches cannot conflict on one. On a fork the token cannot push: the action warns, the lint still runs on the repaired tree, and the default branch repairs the source after merge. luria lintreads sources. Whether a committed view is current is a question about the default branch, where views are committed, andluria index --checkasks it there, in the generation job, right after it regenerates. A branch carries the default branch’s copies and has nothing to be stale against, so the lint asks no staleness question: it checks a branch as it is, in CI and on an author’s machine, and nobody regenerates anything to check a record. A hand-written file in a view directory is still the lint’s (ADR-021), computed against the generator’s output in memory.
The views on main stay committed, so ADR-032’s reasons hold: the badges
land on real pages and a stale view on main still fails the lint.
What ADR-029 warned against is still wrong where it said so. A
checking job on the default branch that regenerates and commits nothing
discards the output and compares the generator against itself. On a pull
request that is the shape by design for the views: staleness is not a
property a branch has, because a branch does not carry them. The staleness
remedy says which is which. ADR-029 carries a history: entry; its choice
stands and the somewhere moves.
Alternatives considered
- Discard the repairs on a pull request along with the views — the
first draft of this decision. The check then passes on a tree the
reviewer never sees: the diff under review shows bare codes and missing
fields the runner had already fixed, and the bot edits sources on
mainafter merge, out of anyone’s review. Rejected once the question “what happens to the repairs?” was asked. The draft’s objection to a bot pushing to branches — that it races the author’s own pushes, the hazard ADR-002 names for the collector — is about a shared file rewritten on every merge. A repair is a small edit to a file the branch already changed, made once, and the author pulls it like any other commit. - A merge driver for generated files.
.gitattributescan name a driver that resolves a conflict by regenerating. The merge button on GitHub runs no driver, and neither does a contributor who has not configured one; the fix would work exactly where the problem is smallest. - Document the routine. “A conflict in a generated file is resolved by regenerating, never by hand.” True, cheap, and it keeps the chore. It is written down anyway, for the branches that predate this.
- Stop committing views anywhere; render on demand. Removes the class
entirely and removes what ADR-032 bought: the README badges and the
status reports link to committed pages, and the staleness lint on
mainis what catches a generator that did not run. - Keep
luria indexwriting both, and have the action commit only the sources on a pull request. The action would need its own list of which files are views, a second copy of what the generator already knows — the drift DP-4 forbids. Two commit points want two commands. - Status quo. Six conflicts per stack, resolved by ritual.
Consequences
A pull request’s diff shows sources only, repaired, which is what review
is for; the generated diff was already collapsed. The regenerated views a
reviewer might want to see are one luria index away, and the site
preview builds from the same tree.
An author whose entry needed a repair pulls the bot’s commit before
pushing again — the pre-existing cost of a bot on the branch, now paid
only when a repair happened, for a file the author was already editing.
Locally, luria repair clears every finding that names it, and
luria lint checks the branch with no view written; luria index is for
the default branch and for looking at the rendered record.
What the split gives up is one redundancy: a view on the default branch
that went stale without the generation job running would fail
luria index --check and not luria lint. The same job runs both on
every push there, so nothing is lost; the v1 shape, which had the
pull-request job regenerate in the working tree so the lint could check
staleness on a branch, was the writing side split and the checking side
not, and it cost an author a clean-up of the working tree after every
local check.
The pull-request job checks the head branch rather than the merge commit,
because a repair needs a branch to land on; the default-branch run checks
the record as it landed. A repair commit pushed with GITHUB_TOKEN gets no
workflow run of its own — the lint that ran in the same job on the same
tree is its check. A repository that requires status checks on the head
commit pushes with a token that triggers runs; the repair commit’s message
carries no skip marker for that reason, and luria repair’s idempotence is
what keeps the re-run from pushing again.
Branches that already committed regenerated views keep them until they merge, and may still conflict with each other in the meantime; the routine for those stays documented in CONTRIBUTING. New branches never carry them.
The scaffold’s workflow changes shape with this repository’s, as ADR-029 requires: adopters get the same three jobs.