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 repair writes every mechanical source repair — each one a state luria lint reports with this command as its remedy, so the two cannot disagree about what counts. It is idempotent: a second run changes nothing. luria index writes 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 the needs: + 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 lint reads sources. Whether a committed view is current is a question about the default branch, where views are committed, and luria index --check asks 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 main after 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. .gitattributes can 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 main is what catches a generator that did not run.
  • Keep luria index writing 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.