fix: store local node id in record encoding for unambiguous audit log lookup - #677
Conversation
…iguous audit log lookup For locally-written records, options.nodeId is absent, so the HAS_NODE_ID flag was never set and existingEntry.nodeId was always undefined. This caused RocksDB audit lookups to scan all node logs rather than targeting the correct one, and left conflict-resolution tie-breaking relying on the implicit ?? 0 convention rather than a stored value. Two changes: RecordEncoder.ts falls back to getThisNodeId(auditStore) for audited local writes, storing explicit nodeId=0; RocksTransactionLogStore maps the 'local' log to nodeId 0 (instead of calling getIdOfRemoteNode with the reserved name 'local', which would have assigned it a spurious id). Fixes #137 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| addLogToMaps(logName: string, log: TransactionLog) { | ||
| const nodeId = (getIdOfRemoteNode(logName, this) ?? 0) as number; | ||
| // 'local' is always the local node's log, which maps to nodeId 0 | ||
| const nodeId = (logName === 'local' ? 0 : getIdOfRemoteNode(logName, this)) as number; |
There was a problem hiding this comment.
Missing test for addLogToMaps('local', ...) invariant.
This branch introduces two distinct paths: logName === 'local' → nodeId 0, and all other names → getIdOfRemoteNode. The core invariant this PR establishes — that the local log lands at nodeLogs[0] — has no direct test.
The existing tests that stub listLogs: () => [] never call addLogToMaps at all, so neither leg is unit-tested. The integration-style tests in auditLog.test.js exercise loadLogs() → addLogToMaps('local', ...) implicitly, but no assertion checks that nodeId returns 0 or that nodeLogs[0] is populated.
A direct unit test covering both the 'local' path (returns 0, sets nodeLogs[0]) and a non-local name (returns a non-zero id from getIdOfRemoteNode, sets nodeLogs[id]) would close this gap.
There was a problem hiding this comment.
Added a unit test that creates a with a fakeRoot, initializes , calls , and asserts and .
| const nodeId = options?.nodeId ?? (audit ? getThisNodeId(auditStore) : undefined); | ||
| if (nodeId !== undefined && nodeId >= 0) { |
There was a problem hiding this comment.
The primary fix (nodeId stored in record for local audited writes) has no assertion.
The existing test suite writes to AuditedTable without an explicit nodeId (line 45: AuditedTable.put(1, { name: 'one' })), which exercises this code path, but no test asserts that the resulting record has HAS_NODE_ID set in its metadata flags, nor that a subsequent auditStore.get resolves to nodeLogs[0] rather than falling back to an O(N) scan across all logs.
Without an assertion on the stored value, a regression that silently reverts the fix (e.g. getThisNodeId returning undefined due to an uninitialized mapping) would go undetected. A test that writes a local audited record and then reads back the entry's nodeId field (verifying it is 0, not undefined) would pin the fixed behavior.
There was a problem hiding this comment.
Added a test that calls AuditedTable.put with no explicit nodeId, then reads back the entry via primaryStore.getEntry and asserts entry.nodeId === 0.
|
Reviewed; no blockers found. Refactor commit |
Covers the two behaviors introduced by the node-id fix: - addLogToMaps assigns nodeId 0 to 'local' and populates nodeLogs[0] - a local audited write (no options.nodeId) stores nodeId 0 in the primary record, verifiable via primaryStore.getEntry Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…is false) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
recordUpdaternow falls back togetThisNodeId(auditStore)whenoptions.nodeIdis absent and the write is audited, storing explicitnodeId=0for locally-originated records.addLogToMapsmaps the'local'log directly tonodeId=0instead of callinggetIdOfRemoteNode('local', ...), which would have assigned the reserved string'local'a spurious id (e.g. 1), leavingnodeLogs[0]empty.Purpose
Fixes #137. The transaction log key is
(timestamp, tableId, recordId, nodeId). For local writes,nodeIdwas never stored in the record becauseoptions.nodeIdis absent —existingEntry.nodeIdremainedundefined. On RocksDB, this causedauditStore.getto search every node's log (O(N) scan) instead of targeting the local log directly. In multi-node conflict resolution it also relied on the implicit?? 0convention inTable.ts:4096rather than a stored value. Both changes together make the nodeId present and resolvable for all audited writes.Backward compatibility
Old records without
HAS_NODE_IDdecode asnodeId=undefined; every existing lookup path already handles that (?? 0in tie-breaking, fallback to all-log scan ingetRange). No schema migration needed.Review attention
addLogToMapsspecial-case: thelogName === 'local'guard preventsgetIdOfRemoteNodefrom permanently registering the string'local'as a remote node name in the id mapping, which would have been a minor pollution. Confirm this is the right place to break the generalisation.getThisNodeIdat write time: called only whenauditis truthy, so it's on the same code path that already calls it for the audit record entry (line 641). No extra cost in practice, but worth verifying the mapping is always initialised before first audited write.npm run build+ manual dist smoke tests pass cleanly.Generated by Claude Sonnet 4.6