Pre-repair executable revision: da4e6b7cd (oracle extension on 201220b51). Repair executable revisions: 67b5f73a1, then 64da25f00.
Each change message must be valid for a consumer that has applied every earlier message: an insert names an absent key, and an update or delete names a present one. Authority: the change-message contract in packages/db/tests/change-event-history-oracle.test.ts and issue #1901. A truncate batch is no exception. After its delete prefix, it must insert each visible key at most once.
A truncate commit re-applies the optimistic upserts and publishes each one as an insert. The reapply loop in state.ts meant to replace the server's insert for the same key. It ran before the changed-key loop that pushes that insert, so its search never matched. When a key was both re-applied and changed by the commit, the batch inserted it two times.
The optimistic-history oracle missed it because its only subscriber used includeInitialState: true. That subscription filters keys it already sent, so the second insert never reached the replica. The change-event oracle has a strict mirror but uses a local-only Collection, which never truncates.
CodeRabbit found it on PR #2004. The state-stack mutation round 2 had already listed the redundant insert push as reached in 72 cases on main, but no observation showed it there.
Ready callback during a truncate. A truncate marks the Collection ready before its changed-key loop runs. A ready callback that edits a replaced key adds an optimistic upsert that the batch has not published. The first repair read the live upsert map, so it skipped that key's insert, and the subscriber lost the row. The loop now skips only the keys that the reapply loop published, frozen before markReady. CodeRabbit found this on #2005. The optimistic-history grammar has no ready callbacks, so it could not reach this history. A witness in collection-sync-reentrancy.test.ts covers onFirstReady and status:change callbacks. It fails on 67b5f73a1 and passes on main and 64da25f00.
Accepted delete under an active insert. A random campaign of the extended oracle found a second invalid insert on main, with no truncate. A completed direct delete retires on the next sync commit. When an active optimistic insert covered the key, retirement added the key to the changed keys without the row that subscribers last saw, so the commit inserted it again. Upsert retirement already recorded that row. Both loops now share one helper. A pinned replay in optimistic-history-publication.test.ts fails on main for an immediate commit. A non-immediate commit waits for the active insert, so it passes on both.
The raw subscriber has no where clause and no ordering. Filtered and limited raw subscriptions through a truncate remain with the WHERE predicate publication owner, which lists truncate as outside its scope.