@@ -1760,6 +1760,32 @@ export function makeTable(options) {
17601760 // of the updates to the record to ensure consistency across the cluster
17611761 // TODO: can the previous version be older, but even more previous version be newer?
17621762 if ( audit ) {
1763+ // A re-delivered out-of-order write (full-copy audit-replay re-delivers writes) must not have
1764+ // its commutative ops re-folded. additionalAuditRefs is the record's own list of folded
1765+ // out-of-order versions, read with read-your-writes consistency, so this skips the duplicate up
1766+ // front — before the audit-log walk below, which can miss it: the walk stops at the depth cap, or
1767+ // breaks early on a not-yet-visible audit entry, before reaching txnTime, and the keyed
1768+ // transaction-log lookup it would otherwise use can lag a back-to-back re-delivery (that lag
1769+ // silently double-applied the increment — #1137). This covers the re-delivery while the ref is
1770+ // still on the record; a later in-order write rewrites the record and drops the ref (it survives
1771+ // only as previousAdditionalAuditRefs on the audit log), so that case falls back to the
1772+ // best-effort keyed lookup in the capped block below — see #1148. precedesExistingVersion(...)
1773+ // === 0 is the identity tie: same version AND same node (the local node is id 0, so an undefined
1774+ // options?.nodeId resolves to the same 0 the ref stored).
1775+ if (
1776+ existingEntry . additionalAuditRefs ?. some (
1777+ ( ref ) =>
1778+ ref . version === txnTime &&
1779+ precedesExistingVersion (
1780+ txnTime ,
1781+ { version : txnTime , localTime : txnTime , key : id , nodeId : ref . nodeId } ,
1782+ options ?. nodeId
1783+ ) === 0
1784+ )
1785+ ) {
1786+ write . skipped = true ;
1787+ return ; // out-of-order write already folded into this record
1788+ }
17631789 // incremental CRDT updates are only available with audit logging on
17641790 let localTime = existingEntry . localTime ;
17651791 let auditedVersion = existingEntry . version ;
@@ -1891,8 +1917,12 @@ export function makeTable(options) {
18911917 // retained window are not layered in — but the authoritative full-copy record restores exact
18921918 // convergence. Because we stopped before reaching txnTime, the inline duplicate detection in
18931919 // the walk never ran; full-copy audit-replay re-delivers writes, and re-applying one would
1894- // double-apply its commutative ops, so rule that out here with a single O(1) lookup at txnTime
1895- // (RocksDB audit logs are keyed by version, and the cap is RocksDB-only).
1920+ // double-apply its commutative ops. A re-delivered out-of-order write is already ruled out by
1921+ // the additionalAuditRefs check at the top of this block; this keyed lookup is the best-effort
1922+ // guard for the remaining case — a re-delivered write that was originally in-order (so it left
1923+ // no ref) and is now deeper than the cap. It is best-effort because the transaction-log lookup
1924+ // can intermittently miss an entry under load (tracked separately); the authoritative full-copy
1925+ // record still restores exact convergence.
18961926 logger . warn ?.(
18971927 'Out-of-order audit reconciliation exceeded depth cap; reconciling against most recent updates only' ,
18981928 {
0 commit comments