Repository navigation
Resume a removed or relayed origin from a per-origin cursor - #998
Conversation
… replaying their logs from 0 After remove_node, a removed node's log on every survivor was neither a subscription source nor an excluded origin, so each resubscribe had its peer start that log at 0, resend all of it, and every node added later pulled it from 0 too. A peer that wrote nothing new also never advanced its subscribers' cursor, so a reconnect after auditRetention became a full base copy. (harper-pro#989) This is W4 (harper-pro#434) scope item 1, the per-origin cursor vector: - A new `originCursors` capability, advertised by RocksDB builds, gates all of it. A peer without it sees and sends exactly what it did before. - The receiver records, per connection, the highest origin log key it has durably applied for every origin (core stores it in the seq row's nodes[], companion harper change), and sends those cursors on SUBSCRIPTION_REQUEST[4] and with an includeNodes update. A removed origin is seeded from its own connection's row. - The sender starts each origin the subscription neither lists nor excludes ORIGIN_CURSOR_OVERLAP_MS (60 s) below its cursor: in the incremental range's startByLog, in the subscription predicate, and on the includeNodes path. An origin with no cursor still starts at 0. - A fresh base copy resumes every relayed log in append order past the key it held at the copy anchor, and the receiver keeps those keys as its cursors, so a cloned node neither pulls a removed node's log in its copy tail nor on its first restart. - The retention check no longer forces a base copy when the requested start is still a retained entry of the sender's local log and every other log in scope is covered by a cursor: rocksdb-js keeps a log's newest file past retention, so an idle peer's last entry survives. Nothing deletes or purges a transaction log, an entry, or a seq row. The 60 s overlap is not a proven bound on late commits; harper-pro#922's closed floor replaces it. Refs #989 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012bLQzwWfPVJaF1PzfyxTD7 Dispatch-Task: harper-pro-989
… re-included origin live Review round 1 fixes for the per-origin cursor vector: - The retention bypass no longer infers coverage from a log's oldest retained key, which out-of-order appends defeat. It builds a range that resumes the local log and every other in-scope log past the exact entry its cursor names (resumeAfterExactStart), probes that every boundary forms, and keeps the base copy otherwise. Logs resumed that way drop their key filter in the predicate, as do relayed logs a base copy resumes past its anchors. - An includeNodes update now wakes an idle sender, and core's addLog re-reads log membership, so a removed origin's last rows reach a survivor without a resubscribe. - COPY_START announces only the anchors the tail resumes past. The receiver records them without minting ids, under a guard, so a failure cannot strand copy finalization. - The sender applies a cursor vector or include cursors only from a peer that advertised originCursors. - The data-frame end_txn attaches its cursors from onCommit, which core now reads after awaiting it. Refs #989 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012bLQzwWfPVJaF1PzfyxTD7 Dispatch-Task: harper-pro-989
… in the replication design note Refs #989 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012bLQzwWfPVJaF1PzfyxTD7 Dispatch-Task: harper-pro-989
…er the design note Review round 2 follow-ups: a superseded sender loop no longer clears the live loop's waker, the per-origin cursor design note becomes item 26 (main added its own item 25), and it notes that a subscription listing more than one source keeps the base copy past retention. Bumps core to the commit that admits a re-included log at the next pull. Refs #989 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012bLQzwWfPVJaF1PzfyxTD7 Dispatch-Task: harper-pro-989
…still close the sender Review round 3 follow-ups: a frame almost always carries one origin, so it is held as a scalar and a second one allocates. The sender's wait for the next transaction propagates a rejection to the subscription's error path instead of swallowing it. The design note records the seeding residual (a removed origin's direct coverage is the advertised one, not a recorded one). Refs #989 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012bLQzwWfPVJaF1PzfyxTD7 Dispatch-Task: harper-pro-989
Refs #989 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012bLQzwWfPVJaF1PzfyxTD7 Dispatch-Task: harper-pro-989
…o hoist the seq updater One relayed log that could not form its exact boundary made the copy tail drop every relayed anchor, so a cloned node pulled every relayed log from 0 again on its first restart. The probe now names the logs that failed, and only those lose their anchor. Core: the seq-row updater is defined once per subscription and decided after onCommit. Dispatch-Task: harper-pro-989 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012bLQzwWfPVJaF1PzfyxTD7
…upt-frame fallback Dispatch-Task: harper-pro-989 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012bLQzwWfPVJaF1PzfyxTD7
There was a problem hiding this comment.
Code Review
This pull request implements per-origin resume cursors (harper-pro#989) to allow removed and relayed origins to resume incrementally instead of starting from zero. It introduces the originCursors capability, records origin progress on the receiver, transmits cursor vectors during subscription requests, and applies a 60-second overlap floor on the sender. Additionally, it updates retention checks to support incremental resumes for idle peers and adds comprehensive integration and unit tests. The review feedback highlights a potential crash in collectSeqRows where entry.key is accessed outside of the try-catch block, suggesting wrapping both key and value extraction to ensure robustness against decoding errors.
…nd to table scope Dispatch-Task: harper-pro-989 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012bLQzwWfPVJaF1PzfyxTD7
…ursor change harper#3080 merged as f7b766981; this moves the gitlink from its branch head to that default-branch commit. Dispatch-Task: pr-maint-cb1e21c58954232c3e525c7f2ee4b45d Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the send predicate pays one check A RocksDB subscription with an exclusion list allocated an empty floor array even with no cursors, so every sent record looked one up. Also corrects the boundary-probe docstring for a corrupt frame, and names the sender's sendsTo exclusion as a way a scope widens. Dispatch-Task: pr-maint-cb1e21c58954232c3e525c7f2ee4b45d Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Remove the retained-anchor retention bypass and its exclusive tests. Keep per-origin cursors, overlap floors and base-copy relayed anchors unchanged; idle-peer resume belongs to #922. Dispatch-Task: harper-pro-998-drop-idle-resume Co-Authored-By: GPT-5 Codex <noreply@openai.com>
Release cherry-pick
|
…eep a proven resume point A receiver's resume cursors moved only on applied frames and the sender's position trailers, so a connected origin that wrote nothing never advanced them; once a cursor was older than auditRetention the next reconnect was upgraded to a bounded base copy, on every reconnect for an idle sender (harper-pro#989 defect B, deferred to #922 by #998). Between peers that both advertise the new additive originFloors capability, a sender now carries the floor core closed for its own log (harper#3085), and the relayable floors it holds for the origins it relays, as an optional third element of SEQUENCE_ID_UPDATE. Floors are captured before a pass scans and sent after it drained, so every entry below a floor has gone out first; an idle connection is woken every ORIGIN_FLOOR_INTERVAL_MS to reach that point, with one whenNextTransaction reaction per transaction generation. The receiver validates them, records whether it receives the origin directly with full coverage, fences them in the update's own onCommit, holds them under the blob rule, and core stores them as nodes[].closedFloor beside the applied cursors. A resume request starts at max(position, the float below the floor) while the peer still certifies. Relay propagation is one hop beyond a direct full-coverage link. A peer without the capability is on the old protocol unchanged. Dispatch-Task: hp-922-phase1-origin-floors Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hhrw3yq9XSUgnNo3ymfe4B
…eep a proven resume point (#1011) * Certify origin-closed floors to peers so an idle origin's receivers keep a proven resume point A receiver's resume cursors moved only on applied frames and the sender's position trailers, so a connected origin that wrote nothing never advanced them; once a cursor was older than auditRetention the next reconnect was upgraded to a bounded base copy, on every reconnect for an idle sender (harper-pro#989 defect B, deferred to #922 by #998). Between peers that both advertise the new additive originFloors capability, a sender now carries the floor core closed for its own log (harper#3085), and the relayable floors it holds for the origins it relays, as an optional third element of SEQUENCE_ID_UPDATE. Floors are captured before a pass scans and sent after it drained, so every entry below a floor has gone out first; an idle connection is woken every ORIGIN_FLOOR_INTERVAL_MS to reach that point, with one whenNextTransaction reaction per transaction generation. The receiver validates them, records whether it receives the origin directly with full coverage, fences them in the update's own onCommit, holds them under the blob rule, and core stores them as nodes[].closedFloor beside the applied cursors. A resume request starts at max(position, the float below the floor) while the peer still certifies. Relay propagation is one hop beyond a direct full-coverage link. A peer without the capability is on the old protocol unchanged. Dispatch-Task: hp-922-phase1-origin-floors Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hhrw3yq9XSUgnNo3ymfe4B * Skip the floor parse on a sequence update that carries no vector Dispatch-Task: hp-922-phase1-origin-floors Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hhrw3yq9XSUgnNo3ymfe4B * Forward a relayed floor only while its origin certifies, drop one for an origin excluded mid-pass, and re-arm one idle timer per loop A relay forwarded an origin's stored floor indefinitely: an origin that reconnects on an older build may write below a floor it saved, so its floor is now forwarded only while its current socket advertises the capability, read from a new shared-status slot the receiver writes at NODE_NAME (zero after a restart until the origin reconnects). A floor captured before a pass is not certified when a SUBSCRIPTION_UPDATE excluded that origin during the pass, since its scan was cut short. The idle wait keeps one timer per loop and re-arms it per wait instead of allocating one per pass, returns on wsClosed as well as closed, and a range failure that latches emission off logs one warning. The outgoing vector is built from a Map so an origin named like a prototype key is still certified; the vector-less sequence update skips the parse; comments trimmed. Dispatch-Task: hp-922-phase1-origin-floors Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hhrw3yq9XSUgnNo3ymfe4B * Certify a floor that rose, not one already certified: the rewritten emission loop had the test inverted Dispatch-Task: hp-922-phase1-origin-floors Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hhrw3yq9XSUgnNo3ymfe4B * Record a peer's floor capability with its record-lock capability, recheck it at emission, and wake an idle sender on close The capability slot was written at NODE_NAME, which on an initial inbound socket runs before the database is known, so it was never set there and a downgraded origin kept being forwarded. It is now written where the record-lock capability is: once the socket's database is known and again when its subscription resolves. A relayed floor captured before a pass is rechecked against the slot at emission. The idle timer wakes the sender unconditionally and the subscription's close handler wakes it too, so a closed loop settles instead of waiting for the next transaction. Dispatch-Task: hp-922-phase1-origin-floors Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hhrw3yq9XSUgnNo3ymfe4B * Clear a retiring socket's floor-capability slot unless it was superseded Dispatch-Task: hp-922-phase1-origin-floors Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hhrw3yq9XSUgnNo3ymfe4B * Build a commit callback only for a floor-carrying update or a blob drain, and let only the subscription link own the capability slot While floors were held by the blob rule, every data frame's position trailer built an onCommit closure that core awaited on its serialized apply loop; a plain trailer now stays allocation-free, and only an update that carries floors or a blob-drain re-emit pays for the callback. The peer's floor-capability slot is now written and cleared only by this node's subscription link to the peer — the socket that receives its certificates — when its status buffer first materializes after the handshake and with the record-lock capability, never by an inbound or retrieval socket, and never after the instance retired. Dispatch-Task: hp-922-phase1-origin-floors Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hhrw3yq9XSUgnNo3ymfe4B * Let only the direct subscription link own the peer floor-capability slot, not a failover subscription through the peer Dispatch-Task: hp-922-phase1-origin-floors Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hhrw3yq9XSUgnNo3ymfe4B * Narrow the floor-emission failure guard to boundary ranges only, and skip idle-wait setup when a commit already landed Round 8's fresh full review, run after the rebase force-push, found two bugs pre-dating the rebase: the floor-emission latch treated exactStartFailures as a scan failure even on an ordinary (non-boundary) range, where core reports "missing" for an empty or not-yet-populated log as the normal, healthy case — permanently disabling certification on a connection's first reconnect scan. A framing recheck (--mode plan) found my first fix over-broad: gating failedLogs/corruptFrameStop behind the same boundary check as exactStartFailures would also disable genuine corruption/failure detection on an ordinary range, letting a floor certify past an unrecoverable gap. Only exactStartFailures needed the boundaryLogName guard; failedLogs and corruptFrameStop stay unconditional, matching the adjacent boundary-continue check's existing distinction. Also: the idle wait unconditionally rearmed the floor timer, allocated the wait Promise and entered try/finally before checking whether a commit had already rotated the transaction generation during the scan. The generation check (a reference comparison) now runs first, and `continue`s straight into a rescan on mismatch, skipping that setup entirely on a busy connection. integrationTests/cluster/idleOriginFloorResume.test.mjs: the existing post-restart reconnect case asserted base-copy avoidance and record delivery but never that floor certification kept advancing afterward — which is exactly the ordinary-range path where the first bug fires, so the test would have passed with the regression still in place. Added floor-advancement assertions for B and C after their restarts. Dispatch-Task: pr-maint-822e916f82093eb27c349f4115db8aaf Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Clear the idle-wait timer on every exit path, so an exiting pass cannot wake a superseding loop The generation-check fix left clearTimeout(floorTimer) reachable only through the do-while loop's natural exit. Both in-loop early returns (socket closed, a timed wake landing after close) and any exception reaching the outer .catch skipped it, leaving a per-incarnation, unref'd timer pending; when it later fired it called the connection-wide wakeSender, which a superseding subscription loop may have since reassigned to its own resolve, giving that later loop one spurious wake. Wrapping the loop in try/finally guarantees the clear runs on every path, matching how the existing waker-ownership check already protects wakeSender itself across supersession. DESIGN.md item 27 is corrected to match: exactStartFailures only withholds certification on a boundary range; failedLogs/corruptFrameStop withhold it on every range kind. Dispatch-Task: pr-maint-822e916f82093eb27c349f4115db8aaf Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
⊙ Problem
After
remove_node, every resubscribe on every surviving node asked its peer for the removed node's entire retained transaction log, starting at 0. That covers restarts, deploys and reconnects. Every node added later pulled the same log from 0 in its base copy's tail, then relayed it back out. Any origin a subscription neither lists nor excludes has the same gap: an origin that reaches the subscriber only through a relay has no cursor either.Idle-peer resume remains deferred to harper-pro#922, the residual of #989 defect B. A reconnect past
auditRetentionfollows the existing bounded base-copy resync. If an idle sender still retains a last local key older than that cutoff, it can copy again on every reconnect; an empty local log uses the copy's wall-clock anchor and reaches the cutoff after another retention window.💡 Solution
Every origin now has a resume position on every connection, behind a new
originCursorscapability. RocksDB builds advertise it; a peer without it does not exchange cursor vectors or apply incremental cursor floors. Exact relayed-log anchors also bound base-copy tails sent to older peers.The pieces:
[seq, peer]row'snodes[].originLogKey. It is held under the same blob rule as the durable watermark, and core merges it (companion core PR).SUBSCRIPTION_REQUEST[4], and fresh ones with anincludeNodesupdate. A removed origin is seeded from its own connection's row, when itshdb_nodesrow is deleted or physically absent.cursor − [ORIGIN_CURSOR_OVERLAP_MS](https://github.com/HarperFast/harper-pro/pull/998/changes?w=1#diff-e707e24c4bc1646c60ac7da292c9707439ee0083f363f0433fda2f851c2b20d1R2748)to each unlisted, unexcluded origin (resolveOriginFloors), in the incremental range'sstartByLog, in the subscription predicate, and on theincludeNodespath. An origin with no cursor still starts at 0.⚖️ Alternatives
nodeSubscriptionsentriesnodes[].seqId, which is the relay's local time, not the origin's key; main deliberately does not use it as a resume bound because it can sit ahead of what was applied. It would also make a removed node a subscription member againRocksTransactionLogStoreto mix exact and non-exact starts in one range, plus a per-log fallback; you chose the 60 s overlap (Q2 a)seqrowProduct and architecture tour
Per-origin cursor lifecycle
How does a subscriber learn, keep and use its position in every origin's log?
Record, send, apply
The receiver records each origin's highest applied key per peer after commit. It sends those keys on the next subscription request, and the sender bounds each unlisted origin's log by them.
sequenceDiagram participant S as Sender (peer P) participant R as Receiver participant C as core seq row [seq, P] S->>R: frame (key K, origin O) R->>C: end_txn originCursors [[O, K]] after onCommit C->>C: nodes[O].originLogKey = max(old, K) Note over R: restart or reconnect R->>S: SUBSCRIPTION_REQUEST[4] { O: K } S->>S: startByLog O = K - 60 s, predicate floor S-->>R: only O entries keyed above the floorORIGIN_CURSOR_OVERLAP_MS(60 s) below the subscriber's cursor for it; an origin with no cursor still starts at 0.Base-copy tails resume in append order
Where can the sender prove what the subscriber holds, and so avoid the 60 s assumption?
resumeAfterExactStart. The receiver keeps those anchors (COPY_START[3]) as its cursors once the copy finishes. A relayed log whose boundary does not form loses only its own anchor and starts from 0.Re-inclusion after
remove_nodeHow do a removed node's last writes reach a survivor that lacks them?
From removal to delivery
hdb_nodesrow for R is deleted, and the main thread drops R from its exclusion origins.{ includeNodes: [R], originCursors: { R: cursor } }.startByLogand callsaddLog.Files.
replication/protocolCapabilities.ts: theoriginCursorslevel, its resolution and advertisement, andsamePeerCapabilities.replication/replicationConnection.ts: the exported helpers (vector, floors, predicate, copy anchors, boundary probe), the receiver's recording and sending, and the sender's floors, copy-tail anchors and include-path wake.replication/DESIGN.md: Non-obvious behavior 26 and the capability table row.core: points at harpermain(f7b766981), where the companion core PR is merged. That commit also brings in harper#3029, harper#3035 and harper#3030, which landed on harpermainfirst; the remaining removed-origin cluster test and the harper-pro unit suite pass on it.integrationTests/cluster/fixture-origin-log/resources.js:LogEntryCountandOriginCursorsread a node's per-origin log size and persisted cursors.✅ Verification
End-to-end route:
npm run test:integration -- --concurrency=1 integrationTests/cluster/removedOriginResumeCursor.test.mjspasses all 3 cases on rebuiltdist. An uncommitted two-node smoke also confirms that reconnecting afterauditRetentionlogs a bounded base-copy resync and that a subsequent write converges. The smoke uses positive evidence (forced-copy warning and subsequent convergence); it does not exercise a retention-forced copy with relayed origins, so that combination remains a coverage gap. The retention-admission block is byte-identical to fetchedorigin/main; 12 cursor/floor/anchor functions, copy-tail code, incremental range, capabilities and the core pointer are unchanged by this scope update. The original fails-on-base evidence for per-origin resume is retained below.integrationTests/cluster/removedOriginResumeCursor.test.mjs. A, B and R form a full mesh, and Q replicates only with A, so Q reaches B only by relay.wrote record <id> … remoteNode <peer>debug line. The restart checks assert absence without a matching positive send control, and missing log files read as empty; strengthening that oracle remains open. The existing fails-on-base evidence below establishes that it observed the defect in the original runs.a3e1a9d0+ core5230512f9): 3/3 tests fail. B is resent R's OLD rows on restart, and N's copy tail carries all 20. Fixed build: green on the final head, and 3/3 runs earlier (5/5 before the include-path assertion was added).unitTests/replication/originCursors.test.mjs(new): vector construction, including per-peer rows, removed-origin seeding through the helper's membership predicate, and malformed rows. Also covers floor resolution, which rejects unknown or inherited names and never mints an id, the subscription predicate, copy anchors, the per-log boundary fallback.unitTests/replication/protocolCapabilities.test.mjs:originCursorsresolution, the fallback for an absent or malformed bag, and advertisement only with per-origin logs.unitTests/replication/replicateFalseSendPaths.test.mjs: passes the new capability argument explicitly.npm run test:unit: 1668 passing, 2 pending.npm run build,npm run typecheck, and changed-file Prettier check pass.npm run lintreports the same 24 warning signatures as fetchedorigin/main(no new warnings). Core companion verification before this scope removal:test:unit:resources4019 passing, 54 pending. Full integration gate on Node 26.2: 217 passing, 7 failing, 3 cancelled, 15 skipped. Fetchedmain(f7aacb6b, core5230512f9) reproduces the worker-placement, non-replicated-table copy, and static-redeploy failures. The other three failed files (replicationWedgeRecovery,selectiveTableSubscription,subscriptionSetupRecovery) pass on main and in a focused branch rerun (6/6 cases); the full gate is recorded as failed. Removed-origin integration also passed all 3 cases again within the full run.bench-run, CPU capped at 2 GHz, 20k frames, 3 rounds, median µs per frame):localTimeframes: 99 with no cursors (the same work as main), 99 with a cursor that did not rise, 111 with a rising cursor (one cursor write).Independent review. Claude, Gemini and Cursor Composer completed full finding passes, followed by harper-domain adjudication. The removal is faithful. The domain reviewer rates the retained session-long 60 s floor as a blocker and membership/sender-lifecycle risks as major; those paths remain unchanged under the owner instruction, and their specific promotion concerns are above. The Pro allocation cost remains unmeasured despite the existing core apply benchmark. Parallel startup failure can also strand test nodes before the suite registers them. The required planning recheck cleared the removal-only framing; review does not imply the draft is ready to merge.
CI on
cb31e647. Builds, unit tests (Node 22/24/26), typecheck, lint, YCSB, all non-cluster integration shards, copy-gap regressions, and five of six cluster shards pass. Cluster shard 1/6 fails because bothreplicateFalseFullCopysuites cannot observe a restart within 60 s; main has the same failure in shard 6/6, atf7aacb6b. Deleting the idle test shifted that file's shard assignment. No new review comments arrived during the watch.No documentation companion is needed: this scope update changes internal resume admission and adds no public API or configuration.
Refs #989
Depends-on: HarperFast/harper#3080
🤖 Generated by Claude Code (original implementation) and OpenAI Codex (scope update); posted via @kriszyp.
Related PRs: #554 overlaps (copy-tail boundaries; reconcile its checks with the relayed-anchor fallback), #717 overlaps (held frames must keep skipping onCommit, where origin cursors are noted), #815 overlaps (capability registry; admission now waits for a transaction boundary), #959 overlaps (sender loop wait and its include waker), #983 overlaps (sender loop waker; moves the core pointer), #940 overlaps (capability registry), #956 overlaps (capability registry; moves the core pointer), #995 overlaps (moves the core pointer), #981 overlaps (moves the core pointer), #992 overlaps (moves the core pointer), #997 overlaps (moves the core pointer), #988 independent, #991 independent, #996 independent, #990 independent, #943 independent (this branch is rebased on it), #987 independent, #984 independent, #986 independent, #979 independent, #985 independent, #942 independent, #955 independent, #800 independent
Complexity: complicated
Origin and current scope
The original dispatch covered #989. The current owner-directed scope addresses removed and relayed origins, preserving per-origin recording/sending, the 60 s overlap floors, includeNodes and exact relayed-log anchors for base-copy tails. It deletes no transaction-log entry, log store or sequence row. The core companion #3080 is merged and the core pointer is unchanged by this scope update.
Dispatch: task
harper-pro-989· queued by kris-session · ran by claude/opus/xhigh · worker kzyp-xps-1Review-Coverage: authored=codex; ran=cursor-composer,gemini,claude; adjudicated=domain; declined=cursor-grok,cursor-kimi,cursor-muse; rounds=11; full=5 @ cb31e64
Review-Attention: deep ~80m (critical: protocolCapabilities.ts, replicationConnection.ts; decisions: do-less-alternative, removed-origin-coverage, scope-widening-policy; raised: earlier round at this SHA) @ cb31e64