Base revision: 65992aacd (main). Found while triaging state mutation round 4.
Row metadata in one sync transaction follows last write wins, by the rules in collection-row-metadata-composition-oracle.test.ts. The rules apply to the write that applies. When the Collection reclassifies an insert, metadata follows the new classification.
A transaction begun inside an open transaction can commit first. The open transaction then applies after it, and the Collection rebuilds the open transaction against the new projection. The rebuild reclassifies each insert: an insert of a present key with an equal value is a re-insert, and a re-insert of an absent key is an insert.
sync.begin() // T1, row 1 is present
sync.metadata.row.set(1, { m: `w0` })
sync.write({ type: `insert`, value: { id: 1, v: 0 } }) // equal re-insert
sync.begin() // T2
sync.write({ type: `delete`, key: 1 })
sync.commit() // T2 applies first, so T1's insert is now an insert
sync.commit() // T1
sync.metadata.row.get(1) // main: { m: `w0` }; expected: undefinedThe reverse direction also fails. From an absent row, T1 sets metadata and inserts the row. T2 inserts the same row with metadata and commits first. T1's insert becomes a re-insert, which keeps the set value. main reads undefined.
Cause. rebuildAutomaticRowMetadataWrites skipped every key with an explicit write. It kept the write that was current when T1 wrote, which no longer follows from T1's operations.
Repair. Each explicit write records how many operations came before it. The rebuild clears the key's writes, restores the last explicit write, and replays the automatic write of each later operation. Hydrated metadata records a position after every hydrated row, so it still holds.
The round 3 record called RB2, a mutant that keeps stale automatic writes in the rebuild, equivalent within legal histories. The follow-up record then said that #2030 made it unreachable, because only the open last transaction can be canceled. Both records missed nested transactions. The invalidationError comment on PendingSyncedTransaction names the history: a later transaction begun inside an open one commits first. The metadata oracle's rebuilt lane changes the projection only through an earlier transaction, which cannot reclassify an insert that is still open.
A nested lane in the metadata composition oracle opens a transaction, T1. T1 writes up to three of set, unset, truncate, and an insert, update, or delete of one fixed row value, each with or without metadata. Then a nested write applies before T1 commits:
A nested transaction can also call metadata.row.set after its row write. A history is legal when T1's writes are legal both where T1 writes them and where they apply.
The model reclassifies T1's inserts by the row's presence where they apply, and folds T1's writes over the value that the nested write leaves. It reads no production state. The lane runs 1,925 histories: 275 for each of seven variants.
| Revision | Nested lane (of 1,925) |
|---|---|
| main | 35 fail: all three nested writes, with and without a nested set |
| this fix | pass |
| explicit write always wins (M1) | 279 fail; the rebuilt lane also fails |
| keep stale automatic writes (M2, RB2) | 21 fail |
| explicit position recorded as 0 (M4) | 267 fail; the rebuilt lane also fails |
| hydrated metadata recorded at position 0 | 7 fail, all hydration seeds |
| explicit write recorded in the first open transaction | 818 fail, all with a nested set |
| truncate keeps explicit positions | 63 fail; the immediate, held, and rebuilt lanes also fail |
The hydration mutant also fails DbClient > hydrates pending collection rows when the collection materializes.
A medium code review found no correctness bug and seven items. A simplifier pass found one item, the same as item 5.
A second medium code review, of #2048 at 9e34d6c94, found no correctness bug and seven items.
The lane covers one key. A nested truncate, a nested update, or a nested write to another key is not generated. A nested insert with a different value makes the open transaction a duplicate. The sync reentrancy oracle owns that invalidation.