@@ -1733,6 +1733,32 @@ export function makeTable(options) {
17331733 // of the updates to the record to ensure consistency across the cluster
17341734 // TODO: can the previous version be older, but even more previous version be newer?
17351735 if ( audit ) {
1736+ // A re-delivered out-of-order write (full-copy audit-replay re-delivers writes) must not have
1737+ // its commutative ops re-folded. additionalAuditRefs is the record's own list of folded
1738+ // out-of-order versions, read with read-your-writes consistency, so this skips the duplicate up
1739+ // front — before the audit-log walk below, which can miss it: the walk stops at the depth cap, or
1740+ // breaks early on a not-yet-visible audit entry, before reaching txnTime, and the keyed
1741+ // transaction-log lookup it would otherwise use can lag a back-to-back re-delivery (that lag
1742+ // silently double-applied the increment — #1137). This covers the re-delivery while the ref is
1743+ // still on the record; a later in-order write rewrites the record and drops the ref (it survives
1744+ // only as previousAdditionalAuditRefs on the audit log), so that case falls back to the
1745+ // best-effort keyed lookup in the capped block below — see #1148. precedesExistingVersion(...)
1746+ // === 0 is the identity tie: same version AND same node (the local node is id 0, so an undefined
1747+ // options?.nodeId resolves to the same 0 the ref stored).
1748+ if (
1749+ existingEntry . additionalAuditRefs ?. some (
1750+ ( ref ) =>
1751+ ref . version === txnTime &&
1752+ precedesExistingVersion (
1753+ txnTime ,
1754+ { version : txnTime , localTime : txnTime , key : id , nodeId : ref . nodeId } ,
1755+ options ?. nodeId
1756+ ) === 0
1757+ )
1758+ ) {
1759+ write . skipped = true ;
1760+ return ; // out-of-order write already folded into this record
1761+ }
17361762 // incremental CRDT updates are only available with audit logging on
17371763 let localTime = existingEntry . localTime ;
17381764 let auditedVersion = existingEntry . version ;
@@ -1864,7 +1890,12 @@ export function makeTable(options) {
18641890 // retained window are not layered in — but the authoritative full-copy record restores exact
18651891 // convergence. Because we stopped before reaching txnTime, the inline duplicate detection in
18661892 // the walk never ran; full-copy audit-replay re-delivers writes, and re-applying one would
1867- // double-apply its commutative ops, so rule that out here before folding.
1893+ // double-apply its commutative ops. A re-delivered out-of-order write is already ruled out by
1894+ // the additionalAuditRefs check at the top of this block; this keyed lookup is the best-effort
1895+ // guard for the remaining case — a re-delivered write that was originally in-order (so it left
1896+ // no ref) and is now deeper than the cap. It is best-effort because the transaction-log lookup
1897+ // can intermittently miss an entry under load (tracked separately); the authoritative full-copy
1898+ // record still restores exact convergence.
18681899 logger . warn ?.(
18691900 'Out-of-order audit reconciliation exceeded depth cap; reconciling against most recent updates only' ,
18701901 {
@@ -1873,24 +1904,16 @@ export function makeTable(options) {
18731904 depth : walkSteps ,
18741905 }
18751906 ) ;
1876- // Detect a re-delivered duplicate via the record's own additionalAuditRefs rather than an
1877- // audit-log lookup at txnTime. Every out-of-order write folded into this record records its
1878- // {version, nodeId} ref (added in the walk above, RocksDB-only — the same condition as this
1879- // cap), and the record is read with read-your-writes consistency. The transaction-log query
1880- // that a keyed audit lookup would use can lag a back-to-back re-delivery — the just-committed
1881- // entry is not yet visible — which silently double-applied the commutative op (#1137).
1882- // precedesExistingVersion(...) === 0 is the identity tie: same version AND same node (the
1883- // local node is id 0, so an undefined options?.nodeId resolves to the same 0 the ref stored).
1884- const alreadyApplied = existingEntry ?. additionalAuditRefs ?. some (
1885- ( ref ) =>
1886- ref . version === txnTime &&
1887- precedesExistingVersion (
1888- txnTime ,
1889- { version : txnTime , localTime : txnTime , key : id , nodeId : ref . nodeId } ,
1890- options ?. nodeId
1891- ) === 0
1892- ) ;
1893- if ( alreadyApplied ) {
1907+ const duplicate = auditStore . get ( txnTime , tableId , id , options ?. nodeId ) ;
1908+ if (
1909+ duplicate &&
1910+ duplicate . version === txnTime &&
1911+ precedesExistingVersion (
1912+ txnTime ,
1913+ { version : txnTime , localTime : txnTime , key : id , nodeId : duplicate . nodeId } ,
1914+ options ?. nodeId
1915+ ) === 0
1916+ ) {
18941917 write . skipped = true ;
18951918 return ; // duplicate write already applied
18961919 }
0 commit comments