refactor(coding-agent): move the semantic-edge ledger onto the event-log substrate - #2028
Open
snimu wants to merge 8 commits into
Open
refactor(coding-agent): move the semantic-edge ledger onto the event-log substrate#2028snimu wants to merge 8 commits into
snimu wants to merge 8 commits into
Conversation
…log substrate The recorder's private append/replay/repair IO is deleted; EventLog owns it, the same move #1987 made for the RLM spawn ledger. One durability rule is unified in the substrate rather than dropped: an unterminated final line is an uncommitted append, skipped on read and truncated before the next append — never newline-completed and never surfaced to a consumer whose next append destroys it.
…atomic readSemanticEdgeLedger probed with statSync before reading through EventLog, which swallows ENOENT; a ledger deleted between the two returned [] instead of throwing. The missing-file decision now lives at the single open (replaySync missingFileThrows), so no check-then-read window exists.
The unterminated-tail contract was restated four times (module doc, replaySync doc, two test comments). It now lives once in the module doc; the method doc keeps only its own parse/missing-file semantics and the test comments reference the contract.
…es-ledger-on-event-log
…tail repair writeSync may write short (ENOSPC after a prefix); appendSync now loops until the payload is fully on disk so write-before-action callers never act on a torn record reported as success. A tail-repair failure (e.g. append-only ACL permitting O_APPEND but not r+) now propagates instead of being swallowed: writing through an unrepaired torn tail would weld it to the new record as permanent interior corruption. ENOENT and the concurrent-writer instability path keep their existing semantics.
…ng them The rlm spawn ledger is multi-writer by documented design (supervisor plus each worker over one file), so completing a short O_APPEND write with a second write could interleave with a rival append and weld two records. A short write now truncates its own torn prefix back off (only while this writer still owns the tail) and fails the append; a torn tail is read-tolerated, a weld is permanent corruption. The append fd opens a+ so the ownership check can read the tail.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit e88fc98. Configure here.
…claiming it The tail-match reclaim could truncate a rival's committed record whose final bytes coincide with our torn prefix - committed-data loss, strictly worse than the torn tail it prevented. A short write now just fails the append: the torn tail is the one tolerated shape, skipped on read and truncated by any writer's next repair (verified for both topologies: a resumed single-writer recorder repairs on its first append; every rlm-ledger writer repairs before each append).
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.

Purpose
The ACP semantic-edge ledger (#1885) and the RLM spawn ledger implement the same append-only JSONL crash-safety independently. #1987 extracted that mechanics into
event-log.tsand moved the spawn ledger onto it; this PR completes the dedup by moving the semantic-edge ledger's private append/replay/repair IO onto the same substrate.EventLogis now the single owner of torn-tail handling, repair-on-append, and line-level replay for both ledgers.What changed
SemanticEdgeRecorderkeeps its semantics (degrade-to-disabled on the first ledger failure, write-before-action ordering, replay-on-resume, retry-identity parking) and deletes its IO:_loadExisting,parseLedgerContent,_pendingRepair(truncate/terminate memo), and the mkdir/appendFileSync plumbing are gone; construction replays throughEventLogand appends go throughEventLog.appendSyncreadSemanticEdgeLedgerkeeps its contract: reads never mutate or create the file, a missing ledger throws (statSyncprobe), interior corruption stays loud with the samecorrupt semantic-edge ledger line NmessageOne durability rule unified (union, not intersection)
The two IO layers disagreed on a parseable-but-unterminated final line: the edges ledger newline-completed it (keeping the record), the substrate truncates it (#1987's review verdict: completion turns a line a strict parser rejects into permanent fail-closed interior poison). Worse, the substrate's replay still surfaced such a line while its next append destroyed it — reading data the log then disowns. This PR unifies on one coherent rule in the substrate, for both ledgers: an unterminated final line is an uncommitted append — skipped on read (with the torn-final-line log) and truncated before the next append. This state is unreachable from either ledger's own single-write appends (record and newline are one write); it matters only for crash/interference windows, where "uncommitted" is the only safe reading.
Test surface
rlm-ledger.test.ts: passes unchangedsemantic-edges.test.ts: one test edited — the pin of the old newline-completion behavior now pins the unified truncation semantics (skipped on read, truncated on append); flagged for exactly this migration in the refactor(coding-agent): extract the append-only event-log substrate from the RLM spawn ledger #1987 review. Everything else (torn-tail tolerance, repair-once, read-never-mutates, read-never-creates, corruption messages, disable-on-failure) passes unchangedevent-log.test.ts: the JSON-parseable-tail pin gains the read-side assertion; both behavior pins verified fail-unfixed against the pre-change substrateValidation
event-log,rlm-ledger(unchanged),semantic-edges(52),agent-session-semantic-edges(23, incl. the fix(coding-agent): settle late compaction slices and forward daemon subagent lineage #2021 settle-race pin),agent-traces(45, incl. the feat(coding-agent): deliver the semantic-edge ledger through the agent-traces outbox #1984 consent-gated intent and outbox pins),suite/agent-session-compaction— 187 tests, 0 failuresnpm run check(biome, tsgo, installer render, browser smoke) passes via the pre-commit hookLOC
Total src: +59/−117 (net -58); tests: +79/−5 (net +74).
Tests +11/−4, changelog +1. Classification: deletion — duplicated IO mechanics removed; the only mechanism change is the read-side half of the unified unterminated-tail rule, which removes an inconsistency rather than adding a branch.
Linear: RES-1260 https://linear.app/primeintellect/issue/RES-1260
Note
Medium Risk
Changes durable ledger I/O and tail semantics for semantic-edge sessions (parseable torn tails are dropped on resume), though normal single-write appends are unaffected; repair failures now block appends instead of being swallowed.
Overview
Routes ACP semantic-edge ledger append/replay through shared
EventLog, removing bespoke_loadExisting,parseLedgerContent, and deferred truncate/newline repair inSemanticEdgeRecorder.readSemanticEdgeLedgerreplays viaEventLogwithmissingFileThrowsso a missing file still errors for explicit readers.EventLogtightens crash semantics for both ledgers: unterminated final lines are always treated as uncommitted (skipped on replay even when JSON-valid, truncated on next append—never newline-completed).appendSyncfails on short writes instead of silently partial appends;repairTailSyncsurfaces repair failures so appends do not write through an unrepaired torn tail.Tests add fault-injection coverage for short writes and failed truncation; semantic-edge expectations shift from newline-completion to the unified tail rule.
Reviewed by Cursor Bugbot for commit d749cf9. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Move semantic-edge ledger onto
EventLogsubstrateEventLog.replaySyncandEventLog.appendSynccalls in event-log.ts, consolidating tail repair, write verification, and missing-file handling intoEventLog.EventLog.replaySyncnow skips any unterminated final line (even valid JSON) and can optionally throw on a missing file.EventLog.appendSyncwrites via a UTF-8 buffer and throws on short writes instead of silently treating the partial append as complete.EventLog.repairTailSyncpropagates open/read/truncate errors instead of swallowing them, and truncates a stable unterminated tail at the last newline.EventLog.replaySyncno longer surfaces parser errors from an unterminated final line; callers that relied on such errors will see that line skipped instead.Macroscope summarized d749cf9.