Repository navigation
ledger: a pure record parser under #readSegments (#117) - #131
Merged
Merged
Conversation
#readSegments read the files and decided what their bytes meant in one loop: a torn or unterminated tail, a bad record with intact records after it, a seq that jumps, a duplicate delivery, and a record dated too far ahead. Those rules are what stop a half-synced folder from losing notes, and they could only be tested by writing real files into a temp folder. records.ts now holds the record format (encode, decode, LogEvent and Hlc, moved unchanged) and readRecords(buf, start, last, suspectAfter): a pure fold over a segment's lines that returns the records taken, the last seq, the offset just past the last good record, the latest record dated past suspectAfter, and the stall, if any, with its offset and reason. It is not exported from the package; ledger.ts re-exports encode, LogEvent and Hlc, so the public surface is the same. #readSegments keeps the listing, the reads and the move to the next segment, and applies what readRecords says to the cursor. The records taken are pushed in a loop rather than spread, since a full segment can hold more of them than a call takes arguments. The skew check uses the scan's own wall clock instead of calling Date.now() again per record. The format and every rule in multi-writer.md are unchanged, and the spec now says which rows of its table are readRecords. The ledger's existing tests pass unchanged. The new ones cover each row, a valid run cut at every byte (taken up to the last whole record, stalling at the cut, never past it), reading on from a cut taking every record once, and a bad line at every position. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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 #117.
What changed
The record format and its reading rules (
packages/ledger/records.ts, new):encode,decode,LogEventandHlcmove here fromledger.ts, unchanged.readRecords(buf, start, last, suspectAfter)is the per-segment loop that was inside#readSegments, as a pure fold over the segment's newline-ended lines. It returns:accepted: the records taken, in orderlast: theseqof the last one takenend: just past the last good record, taken or a duplicate, where the next read resumes; undefined if there was nonesuspect: the latesthlc.lamong taken records dated pastsuspectAfterstall:{ at, why }, withatas an offset in the segmentsuspectAfteris handed in.records.tsis not exported from the package (exportsis still./ledger.ts).ledger.tsre-exportsencode,LogEventandHlc, so the public surface doesn't change.#readSegments(ledger.ts) keeps the directory listing, opening and reading each segment, and moving on to the next. For each segment it callsreadRecordsand applies the result to the cursor:last,suspect, andseg/offsetwhenendis set. The cursor still moves into a new segment only once one of its records is accepted or skipped, as before.multi-writer.md: one paragraph under "Reading foreign logs" says which rows of the table arereadRecords, and that a rule changes in both places together.For the reviewer
at.wall, already taken once per scan) instead of callingDate.now()per record. The threshold can differ by however long the read took, and every record in one scan is now judged against the same time.push(...accepted). A full 16 MiB segment can hold more records than a call accepts as arguments.seqgap, the existingledger.test.tshangs instead of failing, and had to be stopped. Some test waits on an iterator for a state that never comes and isn't bounded by its timeout. Worth a look on its own.Tests
records.test.ts(12 tests, new), over records built withencode:seqthat jumps (after a record, and as the first one), a bad record with intact records after it, a checksum mismatch, a torn tail (bad, unterminated, several bad lines), offsets counted from a non-zero start, and records dated too far ahead (taken, the latest reported, a duplicate not counted)endwith thelastit gave: every record taken once, ending past the runledger.test.tsandopen-writer.test.ts(29 tests) pass unchanged.okoknpm test(213 tests across all workspaces), and the app'snpm run typecheck(which covers the ledger) andnpm run lintpass.🤖 Generated with Claude Code