Give the notes mirror the half of the round trip it never had - #418
Merged
Conversation
`doctor --fix` configured the fetch refspec, so a clone received the mirror on any `git fetch`. Nothing ever sent one. The mirror was one-directional in the wrong direction: everyone could read what nobody could publish, and the teammate whose agent was about to revive a rejected decision was exactly the person who could not see the record. `PRD-F3` asks for a confirmed round trip between teammates and the round trip had no second half. `commitlore sync` fetches, merges the union when both sides moved, and pushes. The `pre-push` hook runs it, and that choice is the decision. Publishing on every commit would put a network call and a possible failure into an operation that had neither, on a machine that may be offline and against a remote the user was not ready to write to. `pre-push` fires exactly when the user has decided to publish, and carries the records with the code they describe. It cannot fail a push: git aborts on a non-zero pre-push, so every path here ends in 0 and anything worth saying goes to stderr. Two things were measured rather than assumed, and both changed the design. `git fetch <remote> <refspec>` applies the **configured** refspecs in addition to the one on the command line. Fetching the remote mirror to a scratch ref therefore moved the working ref underneath the comparison that scratch ref exists for. `--refmap=` isolates it. And the fetch had to land on a scratch ref at all because the working ref may hold records that have not been published. A fetch onto it discards them before anything can merge them -- which is the same overwrite the accompanying refspec change is about. Limit: git neither fetches nor pushes notes by default, so a mirror only moves when something configures or invokes it Ruled-out: pushing the mirror from the post-commit hook | it adds a network call and a failure mode to an operation that had neither, on a machine that may be offline and a remote the user has not chosen to write to Ruled-out: fetching the mirror on the read path so it is always current | `notes.ts` states that availability reads git config with no network, and the PreToolUse hook has a latency budget a fetch would be felt in on every edit Ruled-out: letting the newest side win when two clones diverge | the note is the record, so picking a winner destroys one; the union keeps both and loses nothing Warn: a note merged from two writers is graded against every identity that wrote it and keeps the floor, so it holds at claim until all of them are trusted (#409) Blast: system Undo: easy Certainty: firm Verified: eleven round-trip tests over two clones of a local bare remote -- publish, collect, union merge, idempotent re-sync, dry-run, fetch-only, unreachable remote, and an unpublished local record surviving a diverged fetch; full suite green Provenance: authored Record-Id: r-sync416
CommitLore — record lintTrailers: clean — 1 commit in Active constraints for the paths this PR touchesLimits (100)
Ruled out (231)
Truncated: 69 lines omitted — the comment hit GitHub's 65000 character limit. Trailer violations fail this check. Active constraints are informational — they are what the repository already decided, not a verdict on this PR. |
This was referenced Aug 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #416. Closes #417.
#416 — the mirror could be read but never published
doctor --fixconfigured the fetch refspec, so a clone received the mirror on anygit fetch. Nothing ever sent one. The mirror was one-directional in the wrong direction: everyone could read what nobody could publish, and the teammate whose agent was about to revive a rejected decision was exactly the person who could not see the record.PRD-F3lists "confirm round-trip notes synchronization between teammates" as an acceptance item. The round trip had no second half.commitlore syncfetches, merges the union when both sides moved, and pushes. Thepre-pushhook runs it, and that choice is the decision worth stating:pre-pushfires exactly when the user has decided to publish, and carries the records with the code they describe.pre-push, so every path ends in 0 and anything worth saying goes to stderr.initinstalls it;hooks uninstallremoves it.#417 — a plain
git fetchwas destroying recordsFound while building the above.
NOTES_REFSPECwas+refs/notes/*:refs/notes/*— forced. Everygit fetchoverwrote the local mirror with the remote's, diverged or not. A note is not a cache; the note is the record, so this destroyed records rather than invalidating a derived file — silently, and with exit 0.Measured:
Writing a record and pulling before pushing it was enough.
backfill, squash inheritance and any capture landing in the mirror all produce that state.The
+is gone. A fast-forward still applies without ceremony; the only fetch that behaves differently is one where the local ref holds commits the remote does not — exactly the fetch where forcing destroys something. The cost is a visible! [rejected], andsyncresolves it.doctorreports a forced refspec left over from an older version and--fixrewrites it, leaving the remote's unrelated refspecs alone.Two things measured rather than assumed, both of which changed the design
git fetch <remote> <refspec>applies the configured refspecs too.So fetching the remote mirror to a scratch ref moved the working ref underneath the comparison that scratch ref exists for.
--refmap=isolates it.git config --replace-alltakes a regex for the value, so a refspec passed through raw is not a literal —refs/notes/*reads as "refs/notesthen zero or more/" and does not match the asterisk in the value. A pattern that matches nothing does not fail: it appends, leaving the entry it was meant to remove in place beside a new one. A test caught it; the fix is an escape function with the reason on it.What a merged note costs, stated where a user will find it
When two clones have both written records,
syncmerges the union rather than picking a winner — the note is the record, so picking a winner destroys one. Per #409 a merged note is graded against every identity that has written it and keeps the floor, so it holds atclaimuntil all of them are trusted. That is indocs/cli.mdrather than left to be discovered.Verification
test/sync.test.ts— 11 cases over two clones of a local bare remote: publish, collect, union merge, idempotent re-sync,--dry-run,--fetch-only, unreachable remote, and an unpublished local record surviving a diverged fetch. No network.test/doctor.test.ts— the forced refspec warns and--fixunforces it, without touching the remote's other refspecs. The pre-existing "accepts a wildcard" case had the forced literal typed into it; that literal lost its+and the behaviour change is commented at the line.spec/verify.sh26 fixtures OK ·bench:verify·check-readme-numbers.mjs·tsc -p bench/tsconfig.json·dist/committed ·test/dogfood.test.tsre-run after committing, 9 passed.