Repository navigation
Conversation
The idle and max-duration deadlines, the opening time a window's duration is measured from, and a CommitRequest's attach deadline now read BranchWorker.now(), the clock the retry schedule already uses. A test can step it past a deadline and deliver the timer's wake by hand, with no sleep and no rewound deadline. The timers stay real; unrelated time.Now calls (push cooldown, apply windows, outcome retention) are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The window bookkeeping moves behind commitWindows: the open windows in opening order, each numbered by an opening sequence, and the charge each one carries. Every path says which window it means (the one a write joins or must close, the one a request attaches to or carries, the one whose deadline passed) or all of them (a resync, an apply flush, the byte cap, shutdown). The commit timer is armed for the earliest deadline of the open windows. The set allows exactly one window, so close reasons, close order, retained-byte charges and request settlement are what they were. The loop embeds the set, so loop.openWindow and loop.windowBytes still name the one window and its charge, and every existing test compiles and passes unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ommit windows A small, independent implementation of today's single-window rules runs beside a worker on a real Git server, fed the same random inputs: writes by several authors (new values, undos, no-ops, deletes), saves (attach_next, zero and non-zero attach timeouts and maxDurations, idle timeouts), outside pushes with a push hint, apply flushes, resyncs, buffer pressure, clock steps and shutdown. After every step the open window, the registered saves, the retained-byte charge, the close reasons and every save's outcome must be the model's; every published commit must have the model's author, message and documents, with no older write landing after a newer one; every save settles exactly once; and drained, Git holds the model's tree with nothing charged. The model is how the next stage, several windows open at once, will be judged (docs/design/concurrent-author-windows.md). A withheld late apply rides the next edit of the object it overwrote, and the whole-object window commits it under that author's name. The failing test is written and skipped (GITOPS_REVERSER_KNOWN_BUGS=1 runs it), and the bug is recorded in docs/TODO.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 59 minutes. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Covers the window closeWindowForPushHint now skips per window, the one line Codecov reported uncovered on this PR. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…h a commit changes Review of the reference model found two gaps. The driver derived its wakes from the window and request deadlines and ran closeOrArmWindow itself, so a worker that never armed its commit timer still passed. The loop now records the worker-clock time each of its commit and attach timers is armed for. The model checks both after every step, and the driver fires only an armed timer, through that timer's own wake, in either order when two are due at once. The clock tests deliver wakes the same way. The commit check ignored paths outside the ConfigMap folder and read only data.key. It now compares every changed path, allowing the bootstrap files only in the folder's first commit, and each document whole, parsed as YAML 1.2. The drained check compares the whole tree the same way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e clock stopCommitTimer and stopAttachTimer now clear commitTimerAt and attachTimerAt, so the recorded times hold only while their timer is armed, as their comments say. newOpenWindow no longer stamps openedAt from the wall clock: commitWindows.open sets it from the worker's clock, its only source. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… commit windows The event model now asks that a fact be named after the one decision it records, with the identities it is about and its reason, and that a change adding a decision name it in its design before anything persists facts. Concurrent author windows names the facts its stage 2 adds: WindowOpened, WriteJoinedWindow, FootprintReproved, WindowClosed and SingleWindowFallback. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rite before its commit The event model asked every new decision to be named as a fact before anything persists facts, which settles a journal vocabulary before the recovery contract chooses between durable current state and an event history. It now asks only for specific names and reasons, used in logs, close reasons and tests, and leaves that choice open. Concurrent author windows checked a write against its footprint at materialization but committed it anyway, which cannot stop an older value from landing over a newer one. The check now runs before the commit, over every path the commit changes (writeBatch.touched covers one event only), and refuses the write: worktree undone, save failed, one window from then on, and a resync of the GitTarget. The windows' decisions are names for diagnostics, independent of persistence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 28, 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.
Stage 1 of docs/design/concurrent-author-windows.md: commit-window management moves behind one type, a clock that tests can step drives the window timers, and a reference model of today's single-window rules checks the worker. There is no behavior change. Every existing test passes, and no existing test file was edited.
Stacked on #462 (docs only; it holds the design doc).
What changes
A clock for the window timers (
BranchWorker.now(), already used by the retry schedule). It now also drives the idle and max-duration deadlines, the opening time a window's duration is measured from, and aCommitRequest's attach deadline. The loop records the worker-clock time each of its commit and attach timers is armed for (commitTimerAt,attachTimerAt). A test steps the clock to that time and delivers that timer's wake by hand, asrundoes: no sleeping, and no rewinding a deadline. A deadline the worker armed no timer for is never delivered. The timers themselves stay real. Unrelatedtime.Nowcalls (push cooldown, apply windows, outcome retention) are unchanged.commitWindows(internal/git/window_set.go) holds the open windows in opening order, the sequence number each window takes when it opens, and the charge each window carries. Every caller now says which window it means, or all of them:appendLiveEvent)forWrite); the ones it must close first (blocking,identity_change)handleAttachCommitRequest, attach,saveInProgress)forRequest); a foreign one (foreignTo); the one carrying it (carrying)closeOrArmWindow)foreign_commit)finalizeOpenWindowWithReason)finalizeWindow,dropWindow)The set allows exactly one window, so close reasons, close order, retained-byte charges and request settlement are unchanged. Closing a window releases exactly that window's charge, not the whole charge as before; with one window the two are equal.
Why the set is embedded in the loop: about 150 lines across 26 test files read or assign
loop.openWindowandloop.windowBytesdirectly. Embedding keeps both names as the set's fields, so those tests compile and pass unchanged. Stage 2 replaces the single slot with an ordered list. Those tests then switch to accessors, and that change is where their meaning is reviewed.The reference model
internal/git/window_model_test.gois a small, independent implementation of today's rules. It shares only the event fixtures, the per-event charge and the close-reason names with the worker. It runs on the echo fixture: a real Git server, a GitTarget whose applier claim has a recording requester, and outside clones. The model and the worker get the same random inputs.Checked after every step:
Checked drained (after shutdown, or after the clock runs past every deadline): Git's tree is the model's, path for path and document for document, the retained charge is zero, and saves given back at shutdown have no outcome.
Inputs: writes by three authors (new values, undos, no-ops, deletes and recreates); saves (
attach_next, attach timeouts of 0s, 5s and 20s,maxDurationof 0s, 15s and 40s, with and withoutidleTimeout); outside pushes with a push hint; apply flushes (a dueRepair, answered by the test applier); resyncs; buffer pressure (a cap of 1 byte for one write); clock steps, which fire each armed timer when it comes due through that timer's own wake, in either order when two are due at once; and shutdown.Building the model corrected three wrong expectations of mine. Each is now part of the model, and none is a bug:
heldBytes; it retains nothing for Git.Sensitivity. I broke the worker eight ways in a scratch worktree and ran 30 seeds against each. The model caught seven: an attach charge left out, idle winning a tie with max-duration, a push hint closing a save's window, no
WindowMismatch, a close keeping its charge, noSaveInProgressfor a waiting save, and a flush that ignores a registered save. It did not catch reversing first-come-first-served among waiting saves. That rule cannot be observed today: one author on one GitTarget never has two saves waiting at once, because the second is refusedSaveInProgress.After review, two more: dropping the commit timer's re-arm in
closeOrArmWindow, and arming the attach timer a second late. The model and the clock tests now catch both. A commit carrying the expected change plus a stray file, or an extra data field, is rejected too.Runs: 6 seeds × 40 steps by default (about 12s), 2 seeds under
-short.WINDOW_MODEL_SEEDS,WINDOW_MODEL_STEPSandWINDOW_MODEL_SEEDwiden a run or replay one seed. A wider run of 200 seeds × 60 steps (about 8 minutes) passed with no divergence.A real bug, not fixed here
TestApplyWindow_AWithheldApplyIsNotCommittedUnderTheNextEdit(internal/git/apply_window_carry_test.go) is skipped.GITOPS_REVERSER_KNOWN_BUGS=1runs it, and both cases fail. It is recorded indocs/TODO.md.S1 withholds an apply that lands over an edit decided after the flush mark (E1,
a: a2), and aRepairis queued to put E1 back. But the apply did land on the cluster. The next edit of that object by the same author changes onlyb, yet it carries the apply'sa: a1, and the whole-object window commits it under that author's name:Either way, the
Repairthen applies the loss to the cluster. I found this from the brief's candidate with a hand-written test; the model cannot produce applier-manager writes yet (below).Inputs the model cannot express yet
identity_changeby target), and unattributed or unresolved writes.Repair(the bug above).CREATEevents (adoption makes an apply due), resync sweeps underprune.mode: Always, and resyncs while Git is ahead (F3 discards the snapshot): the driver resyncs only when Git is not ahead.whenNothingToCommit: CommitEmptyrecord commits,catchUp: AfterPush, and withdraws.For stage 2
finalizeWindowstill stops the commit timer before it closes a window. With one window that is correct. With several, a close that leaves other windows open (push hint,attach_next,identity_change) must re-arm the timer for them, ascloseOrArmWindowalready does. The model's timer check will fail on that until it does.loop.openWindowandloop.windowBytesmove to accessors.forRequestreturns the first matching window. Stage 2 keys windows by (GitTarget, author, attribution class), and matching byNamesActormay let two windows match one request.Validation
task fmt,generate,manifests,vet,lint,test: pass.task test-e2epassed locally (112 of 175 specs ran, 0 failed) before the review follow-up, which touches only tests and two recorded timer times.🤖 Generated with Claude Code