Repository navigation
Confirm a transaction whose only write is a losing relocation on the stored entry, not an unwritten log key - #3183
Merged
Merged
Conversation
…stored entry, not an unwritten log key _writeRelocate's commit handler returned early when a newer version already existed without marking the write skipped, so staging recorded appendedTo for it and confirmationTarget waited on a log key nothing was appended at. Reset and set write.skipped the way the other commit handlers do. Refs #3182 Dispatch-Task: harper-3182-losing-relocate-skipped Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MquViG8a7UtvqFChsoL7qH
A relocation that loses when first staged but applies when an open iterator forces the commit to re-stage it must report the append; a losing relocation next to an applied write still confirms on the log key. Refs #3182 Dispatch-Task: harper-3182-losing-relocate-skipped Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MquViG8a7UtvqFChsoL7qH
…nded nothing" Refs #3182 Dispatch-Task: harper-3182-losing-relocate-skipped Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MquViG8a7UtvqFChsoL7qH
…ects, and fail on a missing audit entry Refs #3182 Dispatch-Task: harper-3182-losing-relocate-skipped Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MquViG8a7UtvqFChsoL7qH
Contributor
There was a problem hiding this comment.
Code Review
This pull request ensures that early-return paths in the Table commit handlers, specifically when a write precedes an existing version, correctly set write.skipped to true to prevent replication confirmation from waiting indefinitely (harper#3182). The changes include updates to resources/Table.ts to set this flag, corresponding documentation updates in resources/DESIGN.md, and new unit tests in unitTests/resources/residencyConfirmation.test.js to validate various losing relocation scenarios. There are no review comments, and I have no feedback to provide as the implementation is correct and adheres to the repository style guide.
kriszyp
marked this pull request as ready for review
October 19, 2026 00:09
kriszyp
added a commit
that referenced
this pull request
Oct 23, 2026
…lish or relocate from being skipped as a duplicate (#3499) Port of 5d94a9a to v5.3. The head-tie identity now comes from the committed record whenever another write to the key already saved unskipped, and a source-applied relocate that ties with the committed head is skipped. v5.3 has a single txnLogKey (no origin-closed floor), so main's receivedLogKey/appendedLogKey checks map to `txnLogKey === txnTime`; the relocate keeps v5.3's plain return on an older version (#3183's skipped marking is not on v5.3), and the ref check keeps v5.3's form without main's priorStaged guard. (cherry picked from commit 5d94a9a) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SbvHaBBdQ7Cp7yVDuSD5GS Dispatch-Task: cherry-resolve-kriszyp_harper_3476-123ff057
kriszyp
added a commit
that referenced
this pull request
Oct 23, 2026
…hout reading the transaction log (conflicts → v5.3) (#3478) * Skip relayed duplicates of a record's committed head without reading the transaction log A replicated write that matches the committed record's origin, version and log key (no explicit refs) is already applied. That was only recognized on a retry; on a first delivery the keyed dedup looked for the head's log entry, and when the receiver had none (a base-copied record) the exact-start lookup scanned to end-of-log and the write was applied and logged again, so every peer was re-sent it (harper#3467). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep a put that follows an unstaged same-key write out of the head-tie skip A publish or relocate earlier in the same source transaction restamps the record at the transaction's version without staging it, so the put after it tied with that restamp and was dropped. The skip now requires every earlier write to the key in the transaction to have been skipped, which still lets a re-delivered multi-write transaction skip all of its writes from the record. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Cover a put after a same-key relocate, and keep removalBefore's documentation on it Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Name the relocate case in the head-tie design note Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep only the constraint in the head-tie comment Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Resolve the v5.3 cherry-pick of #3476 v5.3 has a single log key, so the head-tie skip keeps txnLogKey === txnTime and adds priorWritesSkipped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep a replicated put that shares its transaction with a same-key publish or relocate from being skipped as a duplicate (#3499) Port of 5d94a9a to v5.3. The head-tie identity now comes from the committed record whenever another write to the key already saved unskipped, and a source-applied relocate that ties with the committed head is skipped. v5.3 has a single txnLogKey (no origin-closed floor), so main's receivedLogKey/appendedLogKey checks map to `txnLogKey === txnTime`; the relocate keeps v5.3's plain return on an older version (#3183's skipped marking is not on v5.3), and the ref check keeps v5.3's form without main's priorStaged guard. (cherry picked from commit 5d94a9a) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SbvHaBBdQ7Cp7yVDuSD5GS Dispatch-Task: cherry-resolve-kriszyp_harper_3476-123ff057 --------- Co-authored-by: Kris Zyp <kriszyp@gmail.com> 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.
⊙ Problem
A transaction that requests
replicatedConfirmationwaits on a log position that was never written when every confirmable write in it is a relocation that loses to a newer version (harper#3182). Peers never acknowledge that key, so the caller blocks until unrelated traffic replicates, or until the confirmation times out.Cause:
_writeRelocate's commit handler returned early onprecedesExistingVersion(...) < 0without settingwrite.skipped. Staging reads a falsyskippedas "this write appended an audit entry" and recordsappendedTo, soconfirmationTargetchose the committed log key instead of falling back to the stored entry's version. Main only: the log-key target came from Count only a record's residents toward replicatedConfirmation (#3148), which no release carries yet.💡 Solution
_writeRelocate's commit handler now resetswrite.skipped = falseat the start of each round and sets it totruebefore the losing return, the same way_writeInvalidateand_writeDeletedo:_writeRelocatemarks its losing returnskippedand resets it each round — the one runtime line pair to read. That is the whole runtime change. The per-round reset matters as well as the mark: when an open iterator forces the commit to re-stage its writes on a fresh transaction, a relocation that lost when first staged can apply on the re-stage, and a staletruewould hide its append.Audit of the other commit handlers in
resources/Table.ts: none had the same gap. Every version-conflict early return in_writeInvalidate,_writeUpdate,_writeDeleteand the source-resolution write already marksskipped;_writeUpdate'sreturn writeCommit(false)paths do append an audit-only entry, so leaving them unmarked is correct;_writePublishhas no early return; the reload-marker and lock-control writes setskipReplicationConfirmation.Side effect checked:
removalBeforenow passes over a losing relocation when a later delete to the same key looks for what left it empty, as it already did for a losing invalidate and as its doc comment says. The relocation stored nothing, so the stored entry is the right answer there.resources/DESIGN.mdnow says that staging readsskippedas "appended nothing", so an unmarked early return also breaksreplicatedConfirmation, not just blob cleanup.⚖️ Alternatives
Planning review:
Framing-Verdict: chosen-approach-sound.updateRecord/ the transaction log reporting that it wrote) instead of inferring it fromskipped. That removes this whole class of bug: a forgotten flag would fail safe. Not done here because it changes the bookkeeping contract for every handler and the stickyappendedAuditEntryretry dedup, which is more than this fix needs.skipReplicationConfirmationon a losing relocation. Rejected: the caller would then get no confirmation of the surviving entry, which the existing fallback gives them.✅ Verification
unitTests/resources/residencyConfirmation.test.js:origin/main'sresources/Table.tsbuilt intodist, the first test fails (confirms on the log key,version − 60000). With the fix but without the reset line, the re-staging test fails (0 == 1: no confirmation issued). Both pass with the fix.npm run test:unit:resources: 4257 passing, 0 failing (RocksDB).HARPER_STORAGE_ENGINE=lmdb,residencyConfirmation,localOnly,dualClockAuditRecord,indexOrphanStagingOrderandsubscriptionSupersededpass (45 passing).residencyConfirmationskips itself on LMDB, which keeps its own confirmation registry, so the new tests are RocksDB-only.oxlintandprettierclean on the changed files.Refs #3182
— Claude Opus 5.5
🤖 Generated with Claude Code
https://claude.ai/code/session_01MquViG8a7UtvqFChsoL7qH
🤖 Generated by Claude Opus 5.5 (Claude Code dev-agent); posted via @kriszyp.
Release target: v5.4 — the log-key confirmation target this fixes came from #3148 (v5.4); v5.3 has no
appendedTo, so the unmarked return is harmless thereRelated PRs: #2446 independent (MCP session deletion; does not touch
_writeRelocateorremovalBefore)Complexity: medium
Closes #3182
Dispatch: task
harper-3182-losing-relocate-skipped· queued by unknown · ran by claude/opus/high · worker kzyp-xps-1Review-Coverage: authored=claude; ran=gemini,codex; adjudicated=domain; declined=cursor-grok,cursor-composer,cursor-kimi,cursor-muse; rounds=2; full=1 @ db4669b
Review-Attention: study ~9m (critical: Table.ts; decisions: skipped-flag-as-append-proof, all-lost-confirmation-target) @ db4669b