docs(research): rr-cache is portable; replay depends on conflicted-hunk drift (#367) - #413
docs(research): rr-cache is portable; replay depends on conflicted-hunk drift (#367)#413tucktuck101 wants to merge 2 commits into
Conversation
65807b4 to
3c59da9
Compare
Updated for the handbook source-material contractForce-pushed an amended commit bringing this document into line with two conventions introduced after it was written, since it is unmerged and retrofitting after merge is the expensive case: 1. Every reference pinned to a full 40-character SHA. Fork-side claims cite Two judgement calls made while doing it, flagged so a reviewer can overrule:
2. Recommendations separated from evidence and attributed. Each No finding, figure or caveat changed. The diff is pins, section labels, and one external link moved off AI agent (Claude Opus 5) on behalf of @tucktuck101, 2026-08-22. |
…solutions reapply silently (#367) Signed-off-by: tucktuck101 <jeffreytaylorrobertson@gmail.com>
3c59da9 to
75edc27
Compare
Revised for the fork's horizon (#357)Force-pushed an amended commit adding a No evidence, figure or caveat changed. Every measurement and quotation stands exactly as reviewed. What changed is the recommendations — which are now explicitly marked as mine, so the revision is visible rather than a silent rewrite. I added the section rather than editing the original recommendations in place, so anyone who already read this document can see what moved and why. Where a recommendation of mine was wrong under the real horizon, I have said so and withdrawn it rather than softening it. The reversals are named in the section. AI agent (Claude Opus 5) on behalf of @tucktuck101, 2026-08-22. |
serina-mcfall
left a comment
There was a problem hiding this comment.
Blocker — the universal negative in §5 is refuted by experiment
The note states:
Upstream drift produces a decline, never a stale fire.
and
Experiment 4b shows that specific mechanism cannot happen.
I ran a controlled rerere experiment in a disposable repository to check this, because the claim is load-bearing: it is the basis on which the note retracts its own #362 position and tells the fork to correct ADR-0021's recorded reasoning. Two records are about to be rewritten around this mechanism.
Setup: 100-line file, fork and upstream both edit line 50, rerere.enabled=true, rerere.autoUpdate=false. A resolution is recorded that keeps the fork's line and deliberately drops upstream's. Upstream then edits line 5 — forty-five lines away from the conflict — and the merge is re-run.
conflict present: 1
Recorded resolution for 'f.txt'.
RECORDED: fork wins, upstream's line deliberately dropped
=== now merge the DRIFTED upstream ===
CONFLICT (content): Merge conflict in f.txt
Resolved 'f.txt' using previous resolution.
Automatic merge failed; fix conflicts and then commit the result.
markers left: 0
upstream line 50 present: 0
fork line 50 present: 1
Upstream's side changed, and the stored resolution replayed silently — no warning, no decline, and upstream's content dropped. That is precisely a stale fire produced by upstream drift.
The correct statement is narrower, and it is still a useful one: rerere's conflict ID is computed from the conflicted hunk, so drift inside the hunk changes the preimage and declines, while drift outside it replays silently regardless of distance.
Why this is a Blocker rather than a note
The claim is not a detail — it is the retraction. Correcting one wrong mechanism in ADR-0021 with a different wrong mechanism leaves the ledger no better off, and this one is harder to catch because it is stated as a proven negative.
High — "the window is about five lines" does not hold, and the caveat sits below the conclusion
Finding table row 4b records "upstream edits a line ~5 lines from the hunk → does not replay", and §4 is titled "Fragility: real, and the window is about five lines."
In my run a replay occurred at 45 lines. The reviewing agent that first raised this also observed replays at 5 and 3 lines. I cannot refute your specific lefthook.yml observation — a YAML conflict hunk may genuinely be wide enough that a 5-line-distant edit lands inside it — but that is the point: line distance is not the governing variable, hunk membership is, so a distance-based window will mislead whoever prices the risk next.
The caveat you do give ("I did not bisect it, so 'about five lines' is an upper bound … the real boundary is somewhere between 5 and 70") appears in Confidence and limits, after the summary, the Finding table, and two recommendation sections that have already priced the window as real. It also understates the residual uncertainty — a replay at 3 lines falls outside the 5-to-70 band it offers.
What is right, and it is the majority of the note
Five load-bearing claims reproduced, including both that the note treats as decisive:
rr-cacheis portable across clones. Fresh clone with an empty cache leaves the conflict standing; copyingrr-cachein producesResolved 'f.txt' using previous resolution.This is the result #300 needs, and it is solid.- A wrong resolution reapplies silently — byte-identical output to a correct replay. Confirmed.
- rerere never turns a conflicted merge into a clean one — every merge in my run exited non-zero with
Automatic merge failed. - With
autoUpdate=falsethe path is not staged — confirmedUUin status. - An edit far from the hunk still replays.
The structural discipline here is good: the note pins its refs, pastes real output, and separates what it observed from what it reasoned. The defect is confined to one inferred mechanism.
Reviewed at head 75edc2734. The rerere experiment above was run by me in a disposable repository, not taken from the reviewing agent's report.
🤖 Review drafted by Claude Code (claude-opus-5) for @serina-mcfall.
Signed-off-by: tucktuck101 <jeffreytaylorrobertson@gmail.com>
Summary
Adds one research document reporting five experiments on
git rerereagainst the fork's reallefthook.ymlconflict. Therr-cacheis portable, so every remedy #300 considers is viable. The fragility is real with a window of about five lines. When it declines it is loud; the genuinely silent hazard is a resolution recorded wrongly the first time, which reapplies with no warning — which corrects both ADR-0021's stated mechanism and my own earlier answer on #362. Andrererenever turns a conflicted merge into a clean one, which matters for success criterion 2.Related issue
Closes #367
Issue type
Task
Agent provenance
Objective
Add
launchpad/Research/367-rerere-portability-and-fragility.mdrecording whether anrr-cachereplays across clones and how fragile the match really is, established by experiment.Impacted components
Approach and rejected alternatives
Used the real three versions of
lefthook.ymlrather than a synthetic conflict, so the fragility result describes the file the fork actually argues about — one whose upstream side is a 66-line rewrite.Tested the fragility at two distances rather than one, because "does context drift break it" is not a yes/no question. Then, having found that upstream drift makes the match decline, went looking for the case that actually fires silently — which meant deliberately recording a wrong resolution. That experiment is the one that changes what the records should say, and I would not have run it if I had stopped at confirming the documented behaviour.
Rejected: trusting
git-rerere(1)and stopping. The documentation describes a three-way replay but does not say how much drift defeats it, whether the cache is portable, or what a wrong resolution does on reapplication. All three are load-bearing for #300 and all three needed running.Rejected: leaving my own #362 claim standing. I wrote there that the silent hazard was
rererefiring stale when upstream's intent changed underneath. Experiment 4b shows that cannot happen — drift changes the preimage and the match declines. The document says so plainly rather than quietly moving on.Rejected: bisecting the fragility window. Two distances establish that the window is narrow and that ~5 lines defeats it. Pinning the exact threshold would be more precise without changing any decision, and the document reports "about five lines" as an upper bound rather than implying a measurement it did not make.
Verification
Command run:
Raw output:
Every replay, including the correct ones, still ended
Automatic merge failed; fix conflicts and then commit the result.Not verified
I tested one file and one conflict shape.
rerere's behaviour with multiple conflicts in a single file — where practitioner accounts say it becomes stricter and may re-ask for the whole set — was not tested, and the fork'sruntime.rsconflict is exactly the case that would exercise it. I did not test theconflict-marker-sizeinteractiongit-rerere(1)warns about for files containing marker-like lines. I did not bisect the fragility window: I measured at ~70 lines and ~5 lines, so "about five lines" is an upper bound on what still fails, not a threshold — the real boundary lies between 5 and 70 and is presumably git's diff context, which I did not confirm. I tested portability by direct filesystem copy, not throughactions/cacheor a git branch; those transports reduce to the same mechanism but I have not run them. I did not test whether apostimagerecorded on one platform replays on another. Nothing was run in CI. I did not runjust ci; the diff is one markdown file.Security implications
None from the diff. One finding is a real trust consideration for #300 and is stated in the document: a shared
rr-cacheshares mistakes. A wrongly-recorded resolution reapplies with no warning and no marker, so committing one to a shared cache branch means it is applied silently on every contributor machine and in CI. That makes the sharing decision a trust decision as much as a transport one, which #300 currently frames only as transport. It also supports one concrete configuration position: withrerere.autoUpdate falsea replayed resolution sits unstaged andgit statusshows it; with it true, a silently-wrong replay is staged and one commit from the branch.Escalations
Two corrections are owed and one of them is mine. ADR-0021 says the replay "silently stops firing" on drift — it stops firing loudly, with markers and a stopped merge. And on #362 I said the silent hazard was firing stale when upstream's intent changed; experiment 4b shows that cannot happen, because drift changes the preimage and the match declines. The real silent hazard is a resolution recorded wrongly the first time. The record's conclusion is right in both cases; the fork should not carry two wrong mechanisms in support of it. I have edited neither record.
A configuration position for #300, raised not decided: do not enable
rerere.autoUpdatefor drop merges.rerereshould probably not be assigned tolefthook.ymlorruntime.rsin #294's mechanism column. Those are the two rows where upstream rewrites or restructures, so a stored resolution is least likely to survive and most likely to be wrong if it does — which is the worst combination given experiment 5.One consequence for success criterion 2 that nobody has stated.
rererenever turns a conflicted merge into a clean one; every replay above still exited "Automatic merge failed". A drop whose only conflicts arererere-replayable is not an unattended clean merge — it is a conflicted merge with the answers pre-filled. That bears on #296 and on how SC2 is judged.