Upgrade to Gumbo 0.3.0 and fix shared-log tail reads - #20
Merged
Conversation
Gumbo 0.3.0 lands the log-layer fixes from docs/gumbo-requirements.md. The upgrade on its own changed nothing: 0.3.0 *adds* readAfterVersion rather than altering readAfter, so the old call kept compiling and kept being wrong, and the whole suite stayed green across the bump. The caller had to move. D4 is the one that was corrupting state. Catalyst's seq is a per-tag position, but readFrom passed it to readAfter, which is keyed on the log's global seqnum. The two numbers are equal only while a log holds a single execution — true in every test, false in every real deployment. With a second execution present the snapshot warm read returned the entire stream, and the reducer re-folded the snapshot's own prefix on top of itself: timeline steps, tokens, cost and attempt counters all double-counted, with no error at the seam. Measured here at 253 events returned for a tail that should have been under 100. readFrom now uses readAfterVersion and latestSeq's cold path uses getLatestVersion() instead of scanning the whole stream. Accessors move to streamVersion; localId was a fossil of Boki's per-engine id and 0.2.0's accessors still work, deprecated. The file adapter now takes an exclusive directory lock, so a second writer is refused rather than silently assigning duplicate seqs and clobbering index.dat. GumboEventLog surfaces that message at the top level instead of burying it under "Failed to open Gumbo log" — the value of the check is that it is legible. The lock releases on close and on process death, so crash → resume still reopens (M0's kill -9 demo verifies this). Tests are the point of the change, since the defect shipped precisely because every log-layer fixture used one execution in a fresh log — the single configuration where a stream version and a global seqnum are indistinguishable: - GumboEventLogTest: tail reads with a second execution sharing the log, on both the in-memory and file-backed adapters (the latter across a reopen), and the single-writer refusal - SnapshotAcceptanceTest.warmInspectMatchesColdWhenAnotherExecutionSharesTheLog: the end-to-end warm-fold corruption. Verified to fail when the seqnum-keyed read is restored, so it has teeth Full reactor green and all 15 exit demos pass against the 0.3.0 release tag. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017v6M83hQFt4KuLP6Dvk7Sg
Greptile SummaryUpgrades Catalyst to Gumbo 0.3.0 and corrects shared-log stream handling.
Confidence Score: 5/5The PR appears safe to merge, with the shared-log correction covered across tail reads, persistence reopen, snapshot folding, and writer-lock lifecycle. The changed implementation consistently uses per-execution stream versions, preserves the EventLog boundary behavior for full and exclusive tail reads, and adds targeted tests for the previously corrupting multi-execution scenario. Important Files Changed
Sequence DiagramsequenceDiagram
participant Runtime as CatalystRuntime
participant Snapshot as Snapshot Store
participant Log as GumboEventLog
participant Stream as Gumbo Execution Stream
Runtime->>Snapshot: Load snapshot through streamVersion N
Runtime->>Log: readFrom(executionId, N)
Log->>Stream: readAfterVersion(N)
Stream-->>Log: "Events with streamVersion > N"
Log-->>Runtime: Decoded execution tail
Runtime->>Runtime: Fold snapshot plus tail
Reviews (1): Last reviewed commit: "Upgrade to Gumbo 0.3.0 and adopt the ver..." | Re-trigger Greptile |
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.
Summary
Upgrade Catalyst to Gumbo 0.3.0 and fix a critical bug in
GumboEventLog.readFrom()where tail reads were keyed on the log's globalseqnuminstead of the execution's ownstreamVersion. This caused silent data corruption when multiple executions shared a log: snapshot warm reads would return the entire stream and the reducer would re-apply events already folded into the snapshot, double-counting every step, token, and cost.Key Changes
readFrom()to use version-keyed reads: ReplacereadAfter(seqnum)withreadAfterVersion(streamVersion)so tail reads are keyed on the execution's own stream position, not the log's global positionlatestSeq()cold path: UsegetLatestVersion()instead of scanning the entire streamlocalIdwithstreamVersionthroughout (0.3.0 renamed the accessor; old names still work but are deprecated)LogAlreadyOpenExceptionfrom the file adapter and surface it as a clearUncheckedIOExceptionwith the message at the top level (not buried as a cause), since a directory held by another process is a configuration mistake, not a debugging scenarioreadFromReturnsOnlyThisExecutionsTailWhenAnotherExecutionSharesTheLog: in-memory log with two interleaved executionsreadFromOnAFileBackedSharedLogReadsOnlyThisExecutionsTail: file-backed log with two executions across a reopenaSecondWriterOnTheSameDirectoryIsRefusedWithAClearMessage: verify single-writer lock and that it releases on closewarmInspectMatchesColdWhenAnotherExecutionSharesTheLog: end-to-end acceptance test reproducing the snapshot warm-fold corruption and verifying the fixImplementation Details
The bug was invisible in the existing test suite because every test used a single execution per log, where
streamVersionand globalseqnumare the same number. With a second execution present,readAfter(seqnum)would return events from the entire log tail (not just this execution's tail), and the warm snapshot fold would re-apply the snapshot's own prefix.The fix is straightforward:
readAfterVersion()is keyed on the execution tag's own stream position. The cold path forlatestSeq()now callsgetLatestVersion()directly instead of scanning, which is both faster and correct.The file adapter's exclusive lock (new in 0.3.0) is now surfaced clearly:
LogAlreadyOpenExceptionis caught and re-thrown asUncheckedIOExceptionwith the original message intact, so operators see "directory already open" rather than a cause chain. The lock is released on close, so crash→resume still works.https://claude.ai/code/session_017v6M83hQFt4KuLP6Dvk7Sg