@minnowdb/core 0.6.9 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/dist/block-format/block.js +254 -325
  2. package/dist/block-format/checksum.js +30 -52
  3. package/dist/block-format/codecs.js +112 -125
  4. package/dist/block-format/column.js +187 -205
  5. package/dist/block-format/index.js +5 -1
  6. package/dist/block-format/physical.js +371 -387
  7. package/dist/block-format/types.js +4 -1
  8. package/dist/block-format/unicode.js +30 -39
  9. package/dist/date-value.d.ts +5 -0
  10. package/dist/date-value.js +71 -41
  11. package/dist/engine/artifact-cache.js +106 -116
  12. package/dist/engine/batch.js +36 -44
  13. package/dist/engine/buffered-writer.js +156 -158
  14. package/dist/engine/byte-estimates.js +20 -22
  15. package/dist/engine/cache-limits.js +24 -25
  16. package/dist/engine/cancellation.js +5 -3
  17. package/dist/engine/catalog.js +64 -73
  18. package/dist/engine/client.js +715 -780
  19. package/dist/engine/database.js +15952 -18465
  20. package/dist/engine/defaults.js +104 -111
  21. package/dist/engine/errors.js +113 -128
  22. package/dist/engine/fts.js +233 -280
  23. package/dist/engine/group-index.js +287 -321
  24. package/dist/engine/index.js +23 -12
  25. package/dist/engine/join-index.js +177 -186
  26. package/dist/engine/keyed-live.js +168 -173
  27. package/dist/engine/live-api.js +0 -1
  28. package/dist/engine/live-equal.js +32 -37
  29. package/dist/engine/live.js +603 -647
  30. package/dist/engine/memory.js +109 -111
  31. package/dist/engine/optimizer.d.ts +19 -1
  32. package/dist/engine/optimizer.js +2642 -2515
  33. package/dist/engine/point-read.d.ts +4 -2
  34. package/dist/engine/point-read.js +121 -171
  35. package/dist/engine/query-api.js +12 -3
  36. package/dist/engine/query-cache.js +61 -69
  37. package/dist/engine/query.d.ts +45 -0
  38. package/dist/engine/query.js +8506 -9375
  39. package/dist/engine/result-wire.js +221 -252
  40. package/dist/engine/schema-wire.js +97 -112
  41. package/dist/engine/schema.js +1099 -1254
  42. package/dist/engine/sort-keys.js +315 -389
  43. package/dist/engine/sql-domains.d.ts +7 -1
  44. package/dist/engine/sql-domains.js +648 -734
  45. package/dist/engine/sql-driver.js +0 -1
  46. package/dist/engine/sql-functions.d.ts +11 -0
  47. package/dist/engine/sql-functions.js +1092 -0
  48. package/dist/engine/sql-json.js +199 -218
  49. package/dist/engine/sql-semantics.d.ts +25 -4
  50. package/dist/engine/sql-semantics.js +579 -513
  51. package/dist/engine/typed-live.js +271 -293
  52. package/dist/engine/vector.d.ts +7 -1
  53. package/dist/engine/vector.js +4513 -4580
  54. package/dist/engine/worker-host.d.ts +8 -63
  55. package/dist/engine/worker-host.js +28 -1130
  56. package/dist/engine/worker-indexeddb.d.ts +1 -0
  57. package/dist/engine/worker-indexeddb.js +3 -0
  58. package/dist/engine/worker-memory.d.ts +1 -0
  59. package/dist/engine/worker-memory.js +3 -0
  60. package/dist/engine/worker-opfs.d.ts +1 -0
  61. package/dist/engine/worker-opfs.js +3 -0
  62. package/dist/engine/worker-server.d.ts +93 -0
  63. package/dist/engine/worker-server.js +999 -0
  64. package/dist/engine/worker-store-indexeddb.d.ts +2 -0
  65. package/dist/engine/worker-store-indexeddb.js +11 -0
  66. package/dist/engine/worker-store-memory.d.ts +2 -0
  67. package/dist/engine/worker-store-memory.js +6 -0
  68. package/dist/engine/worker-store-opfs.d.ts +2 -0
  69. package/dist/engine/worker-store-opfs.js +9 -0
  70. package/dist/engine/worker.js +0 -12
  71. package/dist/engine/write-block-planner.js +93 -100
  72. package/dist/plan/index.js +15 -16
  73. package/dist/plan/model.d.ts +9 -1
  74. package/dist/plan/model.js +20 -27
  75. package/dist/storage/index.js +0 -12
  76. package/dist/storage/indexeddb.js +11776 -13171
  77. package/dist/storage/memory.js +1198 -1241
  78. package/dist/storage/opfs/files.js +238 -295
  79. package/dist/storage/opfs/index.js +9 -3
  80. package/dist/storage/opfs/leader.js +4386 -4856
  81. package/dist/storage/opfs/rpc.js +223 -259
  82. package/dist/storage/opfs/snapshot-ledger.js +219 -260
  83. package/dist/storage/opfs/store.js +884 -1003
  84. package/dist/storage/persistence.js +59 -74
  85. package/dist/storage/snapshot-stream.js +788 -826
  86. package/dist/storage/snapshot.js +36 -37
  87. package/dist/storage/toolkit/extents.js +444 -520
  88. package/dist/storage/toolkit/index.js +28 -29
  89. package/dist/storage/toolkit/record-core.js +5792 -6377
  90. package/dist/storage/toolkit/sync-file.js +34 -42
  91. package/dist/storage/toolkit/wal.js +92 -127
  92. package/dist/storage/toolkit/wire.js +232 -256
  93. package/dist/storage/types.js +2952 -3228
  94. package/dist/testing/block-store-conformance.js +1556 -1629
  95. package/dist/testing/index.js +310 -291
  96. package/dist/testing/opfs-shim.js +280 -312
  97. package/dist/testing/simulator.js +496 -534
  98. package/dist/testing/sqllogictest.js +425 -365
  99. package/dist/transactions/index.js +1464 -1712
  100. package/dist/worker-protocol/index.js +70 -69
  101. package/package.json +20 -2
  102. package/postgres-feature-profile.json +15 -5
  103. package/sql-feature-matrix.json +252 -2
@@ -1,1663 +1,1590 @@
1
- import { CompactionJobConflictError, LeaseConflictError, LeaseOwnerConflictError, MAX_LEVEL_ZERO_SEGMENTS, MAX_MANIFEST_BLOCK_PRESENCE_IDS, MAX_STORAGE_BULK_READ_ITEMS, PostingBuildConflictError, SnapshotManifestMissingError, TableInUseError, TableRecordConflictError, TempOwnerConflictError, TransactionRecordConflictError, UniqueKeyConflictError, secondaryUniqueKeyNamespace, WriteConflictError, } from "../storage/types.js";
2
- /** Runs every case in sequence; throws on the first failure, prefixed with the case name. */
3
- export async function runBlockStoreConformance(target) {
4
- for (const conformanceCase of blockStoreConformanceCases()) {
5
- try {
6
- await conformanceCase.run(target);
7
- }
8
- catch (error) {
9
- const message = error instanceof Error ? error.message : String(error);
10
- throw new Error(`[${conformanceCase.name}] ${message}`, { cause: error });
11
- }
1
+ import { CompactionJobConflictError, LeaseConflictError, LeaseOwnerConflictError, MAX_LEVEL_ZERO_SEGMENTS, MAX_MANIFEST_BLOCK_PRESENCE_IDS, MAX_STORAGE_BULK_READ_ITEMS, PostingBuildConflictError, SnapshotManifestMissingError, TableInUseError, TableRecordConflictError, TempOwnerConflictError, TransactionRecordConflictError, UniqueKeyConflictError, secondaryUniqueKeyNamespace, WriteConflictError } from "../storage/types.js";
2
+ async function runBlockStoreConformance(target) {
3
+ for (const conformanceCase of blockStoreConformanceCases()) {
4
+ try {
5
+ await conformanceCase.run(target);
6
+ } catch (error) {
7
+ const message = error instanceof Error ? error.message : String(error);
8
+ throw new Error(`[${conformanceCase.name}] ${message}`, { cause: error });
12
9
  }
10
+ }
13
11
  }
14
- // ------------------------------------------------------------------------------------------
15
- // Assertions — deliberately tiny and dependency-free.
16
- // ------------------------------------------------------------------------------------------
17
12
  function check(condition, message) {
18
- if (!condition)
19
- throw new Error(message);
13
+ if (!condition)
14
+ throw new Error(message);
20
15
  }
21
16
  function checkEqual(actual, expected, message) {
22
- if (!deepEqual(actual, expected)) {
23
- throw new Error(`${message}\n expected: ${render(expected)}\n actual: ${render(actual)}`);
24
- }
17
+ if (!deepEqual(actual, expected)) {
18
+ throw new Error(`${message}
19
+ expected: ${render(expected)}
20
+ actual: ${render(actual)}`);
21
+ }
25
22
  }
26
23
  async function checkThrows(work, errorClass, message) {
27
- try {
28
- await work();
29
- }
30
- catch (error) {
31
- check(error instanceof errorClass, `${message}: threw ${render(error)} instead of ${errorClass.name}. Conflicts must be ` +
32
- `thrown as the exact exported classes — the engine's retry loops match on them.`);
33
- check(error.name === errorClass.name, `${message}: the error's own name is "${error.name}", not "${errorClass.name}" ` +
34
- `— the worker client rehydrates errors by constructor name.`);
35
- return error;
36
- }
37
- throw new Error(`${message}: resolved instead of throwing ${errorClass.name}`);
24
+ try {
25
+ await work();
26
+ } catch (error) {
27
+ check(error instanceof errorClass, `${message}: threw ${render(error)} instead of ${errorClass.name}. Conflicts must be thrown as the exact exported classes \u2014 the engine's retry loops match on them.`);
28
+ check(error.name === errorClass.name, `${message}: the error's own name is "${error.name}", not "${errorClass.name}" \u2014 the worker client rehydrates errors by constructor name.`);
29
+ return error;
30
+ }
31
+ throw new Error(`${message}: resolved instead of throwing ${errorClass.name}`);
38
32
  }
39
33
  function deepEqual(left, right) {
40
- if (Object.is(left, right))
41
- return true;
42
- if (left instanceof Uint8Array && right instanceof Uint8Array) {
43
- return left.byteLength === right.byteLength && left.every((byte, at) => right[at] === byte);
44
- }
45
- if (Array.isArray(left) && Array.isArray(right)) {
46
- return left.length === right.length && left.every((entry, at) => deepEqual(entry, right[at]));
47
- }
48
- if (typeof left === "object" &&
49
- typeof right === "object" &&
50
- left !== null &&
51
- right !== null &&
52
- !Array.isArray(left) &&
53
- !Array.isArray(right)) {
54
- const leftKeys = Object.keys(left).sort();
55
- const rightKeys = Object.keys(right).sort();
56
- return (deepEqual(leftKeys, rightKeys) &&
57
- leftKeys.every((key) => deepEqual(left[key], right[key])));
58
- }
59
- return false;
34
+ if (Object.is(left, right))
35
+ return true;
36
+ if (left instanceof Uint8Array && right instanceof Uint8Array) {
37
+ return left.byteLength === right.byteLength && left.every((byte, at) => right[at] === byte);
38
+ }
39
+ if (Array.isArray(left) && Array.isArray(right)) {
40
+ return left.length === right.length && left.every((entry, at) => deepEqual(entry, right[at]));
41
+ }
42
+ if (typeof left === "object" && typeof right === "object" && left !== null && right !== null && !Array.isArray(left) && !Array.isArray(right)) {
43
+ const leftKeys = Object.keys(left).sort();
44
+ const rightKeys = Object.keys(right).sort();
45
+ return deepEqual(leftKeys, rightKeys) && leftKeys.every((key) => deepEqual(left[key], right[key]));
46
+ }
47
+ return false;
60
48
  }
61
49
  function render(value) {
62
- if (value instanceof Error)
63
- return `${value.name}: ${value.message}`;
64
- try {
65
- return JSON.stringify(value, (_key, entry) => typeof entry === "bigint"
66
- ? `${entry.toString()}n`
67
- : entry instanceof Uint8Array
68
- ? `Uint8Array(${String(entry.byteLength)})`
69
- : entry);
70
- }
71
- catch {
72
- return String(value);
73
- }
50
+ if (value instanceof Error)
51
+ return `${value.name}: ${value.message}`;
52
+ try {
53
+ return JSON.stringify(value, (_key, entry) => typeof entry === "bigint" ? `${entry.toString()}n` : entry instanceof Uint8Array ? `Uint8Array(${String(entry.byteLength)})` : entry);
54
+ } catch {
55
+ return String(value);
56
+ }
74
57
  }
75
58
  async function manifestBlockIds(store, version) {
76
- const ids = [];
77
- let afterBlockId = null;
78
- for (;;) {
79
- const page = await store.listManifestBlockPage({ version, afterBlockId, limit: 256 });
80
- ids.push(...page.records.map(({ blockId }) => blockId));
81
- if (page.nextCursor === null)
82
- return ids;
83
- afterBlockId = page.nextCursor;
84
- }
59
+ const ids = [];
60
+ let afterBlockId = null;
61
+ for (; ; ) {
62
+ const page = await store.listManifestBlockPage({ version, afterBlockId, limit: 256 });
63
+ ids.push(...page.records.map(({ blockId }) => blockId));
64
+ if (page.nextCursor === null)
65
+ return ids;
66
+ afterBlockId = page.nextCursor;
67
+ }
85
68
  }
86
69
  async function currentManifestBlockIds(store) {
87
- const version = await store.getCurrentManifestVersion();
88
- return version === null ? [] : manifestBlockIds(store, version);
70
+ const version = await store.getCurrentManifestVersion();
71
+ return version === null ? [] : manifestBlockIds(store, version);
89
72
  }
90
73
  async function tableSegments(store, tableId) {
91
- const records = [];
92
- let afterId = null;
93
- for (;;) {
94
- const page = await store.listTableSegmentPage(tableId, afterId, 256);
95
- records.push(...page.records);
96
- if (page.nextCursor === null)
97
- return records;
98
- afterId = page.nextCursor;
99
- }
74
+ const records = [];
75
+ let afterId = null;
76
+ for (; ; ) {
77
+ const page = await store.listTableSegmentPage(tableId, afterId, 256);
78
+ records.push(...page.records);
79
+ if (page.nextCursor === null)
80
+ return records;
81
+ afterId = page.nextCursor;
82
+ }
100
83
  }
101
- // ------------------------------------------------------------------------------------------
102
- // Fixtures — the smallest records that exercise each rule.
103
- // ------------------------------------------------------------------------------------------
104
84
  const T0 = "2026-01-01T00:00:00.000Z";
105
85
  const MIDDLE = "2026-01-01T00:30:00.000Z";
106
86
  const LATER = "2026-01-01T01:00:00.000Z";
107
87
  const PAST = "2000-01-01T00:00:00.000Z";
108
88
  function table(name, keyed = false) {
109
- return {
110
- id: `table-${name}`,
111
- name,
112
- columns: [
113
- { id: "col-id", name: "id", type: "number", nullable: false },
114
- { id: "col-v", name: "v", type: "string", nullable: true },
115
- ],
116
- managed: false,
117
- revision: 0,
118
- createdAt: T0,
119
- ...(keyed ? { uniqueKeyColumnId: "col-id" } : {}),
120
- };
89
+ return {
90
+ id: `table-${name}`,
91
+ name,
92
+ columns: [
93
+ { id: "col-id", name: "id", type: "number", nullable: false },
94
+ { id: "col-v", name: "v", type: "string", nullable: true }
95
+ ],
96
+ managed: false,
97
+ revision: 0,
98
+ createdAt: T0,
99
+ ...keyed ? { uniqueKeyColumnId: "col-id" } : {}
100
+ };
121
101
  }
122
102
  function transaction(id, snapshotVersion) {
123
- return {
124
- id,
125
- ownerId: `owner-${id}`,
126
- expiresAt: LATER,
127
- snapshotVersion,
128
- pendingBlockIds: [],
129
- pendingSegmentIds: [],
130
- status: "active",
131
- revision: 0,
132
- startedAt: T0,
133
- updatedAt: T0,
134
- committedVersion: null,
135
- };
103
+ return {
104
+ id,
105
+ ownerId: `owner-${id}`,
106
+ expiresAt: LATER,
107
+ snapshotVersion,
108
+ pendingBlockIds: [],
109
+ pendingSegmentIds: [],
110
+ status: "active",
111
+ revision: 0,
112
+ startedAt: T0,
113
+ updatedAt: T0,
114
+ committedVersion: null
115
+ };
136
116
  }
137
117
  function segment(id, tableId, transactionId, blockId, commitOrdinal = 0) {
138
- return {
139
- id,
140
- tableId,
141
- transactionId,
142
- rowCount: 2,
143
- rowIdStart: 1n,
144
- rowIdEndExclusive: 3n,
145
- columnBlockIds: { "col-id": [blockId], "col-v": [blockId] },
146
- kind: "insert",
147
- level: 0,
148
- logicalOrder: 0,
149
- commitOrdinal,
150
- rowIdSpans: [],
151
- createdAt: T0,
152
- };
118
+ return {
119
+ id,
120
+ tableId,
121
+ transactionId,
122
+ rowCount: 2,
123
+ rowIdStart: 1n,
124
+ rowIdEndExclusive: 3n,
125
+ columnBlockIds: { "col-id": [blockId], "col-v": [blockId] },
126
+ kind: "insert",
127
+ level: 0,
128
+ logicalOrder: 0,
129
+ commitOrdinal,
130
+ rowIdSpans: [],
131
+ createdAt: T0
132
+ };
153
133
  }
154
- /** Stages one block + one segment on a fresh transaction and commits it. */
155
134
  async function commitOne(store, suffix, options = {}) {
156
- const version = await store.getCurrentManifestVersion();
157
- const transactionId = `txn-${suffix}`;
158
- const blockId = `table/t/segment/${suffix}/part/000000`;
159
- await store.createTransaction(transaction(transactionId, version));
160
- await store.stageTransactionArtifacts({
161
- transactionId,
162
- expectedRevision: 0,
163
- blocks: [{ id: blockId, bytes: Uint8Array.of(1, 2, 3, 4) }],
164
- segments: [segment(`segment-${suffix}`, options.tableId ?? "table-t", transactionId, blockId)],
165
- updatedAt: T0,
166
- });
167
- const summary = await store.commitTransaction({
168
- transactionId,
169
- expectedTransactionRevision: 1,
170
- expectedManifestVersion: version,
171
- levelZeroSegmentLimits: [
172
- { tableId: options.tableId ?? "table-t", limit: MAX_LEVEL_ZERO_SEGMENTS },
173
- ],
174
- committedAt: LATER,
175
- ...(options.keyTokens === undefined
176
- ? {}
177
- : {
178
- uniqueKeyChanges: [
179
- {
180
- tableId: options.tableId ?? "table-t",
181
- keyTokens: options.keyTokens,
182
- requireAbsent: true,
183
- },
184
- ],
185
- }),
186
- });
187
- return { version: summary.version, blockId };
188
- }
189
- // ------------------------------------------------------------------------------------------
190
- // The cases.
191
- // ------------------------------------------------------------------------------------------
192
- export function blockStoreConformanceCases() {
193
- return [
194
- {
195
- name: "pending tables stay invisible across multi-batch reopen and publish only at commit",
196
- async run(target) {
197
- let store = await target.create();
198
- const pending = table("pending");
199
- const epoch = (await store.getCatalogProbe()).catalogEpoch;
200
- const begun = await store.beginTransaction({
201
- record: {
202
- id: "pending-owner",
203
- ownerId: "owner-pending",
204
- expiresAt: LATER,
205
- pendingBlockIds: [],
206
- pendingSegmentIds: [],
207
- status: "active",
208
- revision: 0,
209
- startedAt: T0,
210
- updatedAt: T0,
211
- committedVersion: null,
212
- },
213
- pendingTable: { record: pending, nextRowId: 1n, expectedCatalogEpoch: epoch },
214
- });
215
- check(begun.record.pendingTable?.id === pending.id, "pending reservation was not recorded");
216
- check((await store.getTable(pending.id)) === undefined, "pending table became public early");
217
- checkEqual(await store.listTables(), [], "pending table appeared in the public catalog");
218
- const firstBlock = "pending/block/1";
219
- await store.stageTransactionArtifacts({
220
- transactionId: begun.record.id,
221
- expectedRevision: begun.record.revision,
222
- blocks: [{ id: firstBlock, bytes: Uint8Array.of(1) }],
223
- segments: [segment("pending-segment-1", pending.id, begun.record.id, firstBlock)],
224
- updatedAt: MIDDLE,
225
- });
226
- if (target.reopen !== undefined)
227
- store = await target.reopen(store);
228
- check((await store.getTable(pending.id)) === undefined, "reopen exposed a pending table");
229
- const resumed = await store.getTransaction(begun.record.id);
230
- check(resumed?.revision === 1, "reopen lost the first pending-table stage");
231
- const secondBlock = "pending/block/2";
232
- await store.stageTransactionArtifacts({
233
- transactionId: begun.record.id,
234
- expectedRevision: resumed.revision,
235
- blocks: [{ id: secondBlock, bytes: Uint8Array.of(2) }],
236
- segments: [
237
- {
238
- ...segment("pending-segment-2", pending.id, begun.record.id, secondBlock),
239
- rowIdStart: 3n,
240
- rowIdEndExclusive: 5n,
241
- commitOrdinal: 1,
242
- },
243
- ],
244
- updatedAt: LATER,
245
- });
246
- const manifest = await store.commitTransaction({
247
- transactionId: begun.record.id,
248
- expectedTransactionRevision: 2,
249
- expectedManifestVersion: null,
250
- levelZeroSegmentLimits: [{ tableId: pending.id, limit: MAX_LEVEL_ZERO_SEGMENTS }],
251
- committedAt: LATER,
252
- });
253
- check(manifest.version === 0, "pending-table commit did not publish the first manifest");
254
- check((await store.getTable(pending.id))?.name === pending.name, "commit lost pending table");
255
- check((await store.getTransaction(begun.record.id))?.pendingTable === undefined, "commit retained the pending catalog reservation");
256
- store.close();
257
- },
258
- },
259
- {
260
- name: "staged blocks are immutable, copied both directions, and duplicates are refused",
261
- async run(target) {
262
- const store = await target.create();
263
- await store.createTransaction(transaction("txn-block-copy", null));
264
- const bytes = Uint8Array.of(1, 2, 3);
265
- await store.stageTransactionArtifacts({
266
- transactionId: "txn-block-copy",
267
- expectedRevision: 0,
268
- blocks: [{ id: "block-a", bytes }],
269
- segments: [],
270
- updatedAt: T0,
271
- });
272
- bytes[0] = 99; // The store must not alias the caller's buffer.
273
- const first = await store.getBlock("block-a");
274
- check(first?.[0] === 1, "stored bytes changed with the caller's buffer");
275
- first[1] = 99; // Nor may the caller's mutations reach the store.
276
- const second = await store.getBlock("block-a");
277
- check(second?.[1] === 2, "returned bytes alias the store's copy");
278
- try {
279
- await store.stageTransactionArtifacts({
280
- transactionId: "txn-block-copy",
281
- expectedRevision: 1,
282
- blocks: [{ id: "block-a", bytes: Uint8Array.of(9) }],
283
- segments: [],
284
- updatedAt: LATER,
285
- });
286
- throw new Error("a duplicate block id was accepted");
287
- }
288
- catch (error) {
289
- check(error instanceof Error, "duplicate rejection must be an Error");
290
- }
291
- checkEqual(await store.getBlocks(["missing", "block-a"]).then((all) => all[0]), undefined, "getBlocks must be positional with undefined for missing ids");
292
- store.close();
293
- },
294
- },
295
- {
296
- name: "an artifact batch with any duplicate writes nothing at all",
297
- async run(target) {
298
- const store = await target.create();
299
- await store.createTransaction(transaction("txn-block-batch", null));
300
- await store.stageTransactionArtifacts({
301
- transactionId: "txn-block-batch",
302
- expectedRevision: 0,
303
- blocks: [{ id: "block-a", bytes: Uint8Array.of(1) }],
304
- segments: [],
305
- updatedAt: T0,
306
- });
307
- try {
308
- await store.stageTransactionArtifacts({
309
- transactionId: "txn-block-batch",
310
- expectedRevision: 1,
311
- blocks: [
312
- { id: "block-b", bytes: Uint8Array.of(2) },
313
- { id: "block-a", bytes: Uint8Array.of(3) },
314
- { id: "block-c", bytes: Uint8Array.of(4) },
315
- ],
316
- segments: [],
317
- updatedAt: LATER,
318
- });
319
- throw new Error("a batch containing an existing id was accepted");
320
- }
321
- catch {
322
- // Expected; what matters is that nothing landed.
323
- }
324
- checkEqual(await store.getBlock("block-b"), undefined, "a failed batch leaked block-b");
325
- checkEqual(await store.getBlock("block-c"), undefined, "a failed batch leaked block-c");
326
- checkEqual(await store.getBlock("block-a"), Uint8Array.of(1), "the first block changed");
327
- store.close();
328
- },
329
- },
135
+ const version = await store.getCurrentManifestVersion();
136
+ const transactionId = `txn-${suffix}`;
137
+ const blockId = `table/t/segment/${suffix}/part/000000`;
138
+ await store.createTransaction(transaction(transactionId, version));
139
+ await store.stageTransactionArtifacts({
140
+ transactionId,
141
+ expectedRevision: 0,
142
+ blocks: [{ id: blockId, bytes: Uint8Array.of(1, 2, 3, 4) }],
143
+ segments: [segment(`segment-${suffix}`, options.tableId ?? "table-t", transactionId, blockId)],
144
+ updatedAt: T0
145
+ });
146
+ const summary = await store.commitTransaction({
147
+ transactionId,
148
+ expectedTransactionRevision: 1,
149
+ expectedManifestVersion: version,
150
+ levelZeroSegmentLimits: [
151
+ { tableId: options.tableId ?? "table-t", limit: MAX_LEVEL_ZERO_SEGMENTS }
152
+ ],
153
+ committedAt: LATER,
154
+ ...options.keyTokens === void 0 ? {} : {
155
+ uniqueKeyChanges: [
330
156
  {
331
- name: "version-scoped block reads are exact, bounded, and reject missing history",
332
- async run(target) {
333
- const store = await target.create();
334
- await store.addTable(table("t"));
335
- const first = await commitOne(store, "manifest-read-first");
336
- const second = await commitOne(store, "manifest-read-second");
337
- checkEqual(await store.hasManifestBlocks(first.version, [first.blockId, "not-a-member"]), [true, false], "manifest membership must be positional and exact");
338
- checkEqual(await store.readManifestBlock(first.version, first.blockId), Uint8Array.of(1, 2, 3, 4), "a readable manifest member must return its payload");
339
- checkEqual(await store.readManifestBlock(first.version, second.blockId), undefined, "a non-member block must not be readable through an older manifest");
340
- await checkThrows(() => store.hasManifestBlocks(second.version, Array.from({ length: MAX_MANIFEST_BLOCK_PRESENCE_IDS + 1 }, (_, index) => `presence-${String(index)}`)), RangeError, "manifest membership cap+1");
341
- await checkThrows(() => store.getBlocks(Array.from({ length: MAX_STORAGE_BULK_READ_ITEMS + 1 }, (_, index) => `block-${String(index)}`)), RangeError, "block read cap+1");
342
- const oversizedIds = Array.from({ length: MAX_STORAGE_BULK_READ_ITEMS + 1 }, (_, index) => `id-${String(index)}`);
343
- await checkThrows(() => store.getTransactions(oversizedIds), RangeError, "transaction read cap+1");
344
- await checkThrows(() => store.getExistingUniqueKeys("table-t", oversizedIds), RangeError, "unique-key lookup cap+1");
345
- let job = await store.createGarbageCollectionJob({
346
- id: "gc-prune-manifest-read",
347
- candidateManifestVersions: [first.version],
348
- candidateSegmentIds: [],
349
- candidateBlockIds: [],
350
- leaseCutoff: LATER,
351
- createdAt: T0,
352
- });
353
- while (job.state !== "completed") {
354
- job = (await store.runGarbageCollectionStep({
355
- jobId: job.id,
356
- expectedRevision: job.revision,
357
- maxItems: 16,
358
- updatedAt: LATER,
359
- })).job;
360
- }
361
- checkEqual(await store.readManifestBlock(first.version, first.blockId), undefined, "a pruned manifest must not expose its former member");
362
- store.close();
363
- },
364
- },
365
- {
366
- name: "table records round-trip, list sorted by name, and conflict by exact class",
367
- async run(target) {
368
- const store = await target.create();
369
- await store.addTable(table("bravo"));
370
- await store.addTable(table("alpha"));
371
- const names = (await store.listTables()).map((record) => record.name);
372
- checkEqual(names, ["alpha", "bravo"], "listTables must sort by name");
373
- const byName = await store.getTableByName("alpha");
374
- check(byName?.id === "table-alpha", "getTableByName must resolve the record");
375
- await checkThrows(() => store.updateTable("table-alpha", 7, {}), TableRecordConflictError, "updateTable with a stale revision");
376
- await checkThrows(() => store.removeTable("table-alpha", 7), TableRecordConflictError, "removeTable with a stale revision");
377
- const updated = await store.updateTable("table-alpha", 0, {});
378
- check(updated.revision === 1, "updateTable must advance the revision");
379
- store.close();
380
- },
381
- },
382
- {
383
- name: "secondary-index metadata must describe one direction per indexed column",
384
- async run(target) {
385
- const store = await target.create();
386
- const record = table("bad-secondary");
387
- try {
388
- await store.addTable({
389
- ...record,
390
- secondaryIndexes: {
391
- bad: {
392
- name: "bad_direction_arity_index",
393
- columnId: "col-v",
394
- columnIds: ["col-v"],
395
- directions: ["asc", "desc"],
396
- termEncoding: "tuple-v1",
397
- storage: "postings-v1",
398
- storageColumnId: "secondary-index:bad",
399
- locator: "row-id",
400
- state: "ready",
401
- buildFromVersion: 0,
402
- },
403
- },
404
- });
405
- throw new Error("secondary-index metadata with mismatched direction arity was accepted");
406
- }
407
- catch (error) {
408
- check(error instanceof TypeError, "invalid secondary-index metadata must be rejected");
409
- }
410
- checkEqual(await store.listTables(), [], "a rejected catalog record must leave no table");
411
- store.close();
412
- },
413
- },
414
- {
415
- name: "removing a table removes everything keyed to it",
416
- async run(target) {
417
- const store = await target.create();
418
- await store.addTable(table("t", true));
419
- const { blockId } = await commitOne(store, "one", {
420
- keyTokens: ["number:1"],
421
- tableId: "table-t",
422
- });
423
- await store.reserveRowIds("table-t", 10);
424
- const dropped = await store.dropTable({
425
- tableId: "table-t",
426
- expectedTableRevision: 0,
427
- expectedManifestVersion: 0,
428
- expectedCatalogEpoch: (await store.getCatalogProbe()).catalogEpoch,
429
- committedAt: LATER,
430
- });
431
- check(dropped.version === 1, "dropTable must publish one successor manifest");
432
- checkEqual(await currentManifestBlockIds(store), [], "dropTable must retire the table's manifest blocks");
433
- checkEqual(await tableSegments(store, "table-t"), [], "segments must go with the table");
434
- checkEqual(await store.getExistingUniqueKeys("table-t", ["number:1"]), [], "unique keys must go with the table");
435
- await store.addTable(table("t", true));
436
- const range = await store.reserveRowIds("table-t", 1);
437
- check(range.start === 1n, "row-id counters must reset with the table");
438
- check((await store.getBlock(blockId)) !== undefined, "blocks are not the catalog's to delete");
439
- store.close();
440
- },
441
- },
442
- {
443
- name: "fused table drop rejects without change and resolves without partial state",
444
- async run(target) {
445
- const store = await target.create();
446
- await store.addTable(table("t"));
447
- const source = await commitOne(store, "source");
448
- const catalogEpoch = (await store.getCatalogProbe()).catalogEpoch;
449
- const drop = () => store.dropTable({
450
- tableId: "table-t",
451
- expectedTableRevision: 0,
452
- expectedManifestVersion: source.version,
453
- expectedCatalogEpoch: catalogEpoch,
454
- committedAt: LATER,
455
- });
456
- await checkThrows(() => store.dropTable({
457
- tableId: "table-t",
458
- expectedTableRevision: 9,
459
- expectedManifestVersion: source.version,
460
- expectedCatalogEpoch: catalogEpoch,
461
- committedAt: LATER,
462
- }), TableRecordConflictError, "dropping with a stale table revision");
463
- await checkThrows(() => store.dropTable({
464
- tableId: "table-t",
465
- expectedTableRevision: 0,
466
- expectedManifestVersion: source.version - 1,
467
- expectedCatalogEpoch: catalogEpoch,
468
- committedAt: LATER,
469
- }), WriteConflictError, "dropping with a stale manifest revision");
470
- check((await store.getTable("table-t")) !== undefined &&
471
- (await store.getCurrentManifestVersion()) === source.version, "a fused drop CAS refusal changed the catalog or manifest");
472
- const active = transaction("txn-active-table", source.version);
473
- await store.createTransaction(active);
474
- const activeBlockId = "table/t/segment/active/part/000000";
475
- const activeSegment = segment("segment-active-table", "table-t", active.id, activeBlockId);
476
- const staged = await store.stageTransactionArtifacts({
477
- transactionId: active.id,
478
- expectedRevision: 0,
479
- blocks: [{ id: activeBlockId, bytes: Uint8Array.of(9, 8, 7, 6) }],
480
- segments: [activeSegment],
481
- updatedAt: LATER,
482
- });
483
- const activeRefusal = await checkThrows(drop, TableInUseError, "dropping a table with an active transaction");
484
- checkEqual({
485
- tableId: activeRefusal.tableId,
486
- ownerKind: activeRefusal.ownerKind,
487
- ownerId: activeRefusal.ownerId,
488
- }, { tableId: "table-t", ownerKind: "transaction", ownerId: active.id }, "an active-transaction refusal must identify its exact owner");
489
- check((await store.getTable("table-t")) !== undefined &&
490
- (await store.getSegment(activeSegment.id)) !== undefined &&
491
- (await store.getBlock(activeBlockId)) !== undefined &&
492
- (await store.getCurrentManifestVersion()) === source.version, "a refused table drop must leave catalog, manifest, and active artifacts unchanged");
493
- await store.updateTransaction(active.id, staged.revision, {
494
- status: "aborted",
495
- updatedAt: LATER,
496
- });
497
- await store.createCompactionJob({
498
- id: "job-table-in-use",
499
- tableId: "table-t",
500
- sourceManifestVersion: source.version,
501
- sourceSegmentIds: ["segment-source"],
502
- sourceBlockIds: [source.blockId],
503
- outputBlockIds: [],
504
- cursor: { sourceSegmentIndex: 0, sourceBlockIndex: 0 },
505
- processedRows: 0,
506
- sourceStoredBytes: 0,
507
- outputStoredBytes: 0,
508
- logicalBytes: 0,
509
- rewritePlan: { kind: "copy-v1" },
510
- outputCursor: null,
511
- memoryBudgetBytes: 0,
512
- minimumMemoryBytes: 0,
513
- level0SourceStoredBytes: 0,
514
- anchorSourceStoredBytes: 0,
515
- peakWorkingBytes: 0,
516
- outputLogicalBytes: 0,
517
- targetLevel: 1,
518
- state: "planned",
519
- transactionId: null,
520
- outputSegmentId: null,
521
- publishedVersion: null,
522
- revision: 0,
523
- createdAt: T0,
524
- updatedAt: T0,
525
- });
526
- const jobRefusal = await checkThrows(drop, TableInUseError, "dropping a table with a nonterminal compaction job");
527
- checkEqual({
528
- tableId: jobRefusal.tableId,
529
- ownerKind: jobRefusal.ownerKind,
530
- ownerId: jobRefusal.ownerId,
531
- }, {
532
- tableId: "table-t",
533
- ownerKind: "compaction job",
534
- ownerId: "job-table-in-use",
535
- }, "a compaction refusal must identify its exact owner");
536
- check((await store.getTable("table-t")) !== undefined &&
537
- (await store.getCurrentManifestVersion()) === source.version &&
538
- (await store.hasManifestBlocks(source.version, [source.blockId]))[0] === true, "a compaction refusal changed the catalog or manifest");
539
- await store.cancelCompactionJob("job-table-in-use", 0, LATER);
540
- const published = await drop();
541
- check(published.version === source.version + 1, "a fused table drop must publish exactly one successor manifest");
542
- check((await store.getTable("table-t")) === undefined, "a terminal owner blocked removal");
543
- checkEqual(await tableSegments(store, "table-t"), [], "a fused drop left table segments");
544
- checkEqual(await currentManifestBlockIds(store), [], "a fused drop left table blocks live");
545
- check((await store.getBlock(source.blockId)) !== undefined, "a fused drop physically deleted a block pinned readers may still need");
546
- store.close();
547
- },
548
- },
549
- {
550
- name: "counter reservations never overlap, even issued concurrently",
551
- async run(target) {
552
- const store = await target.create();
553
- const counterTable = table("t", true);
554
- const counterColumn = counterTable.columns[0];
555
- if (counterColumn === undefined)
556
- throw new Error("counter fixture column is missing");
557
- counterTable.columns[0] = {
558
- ...counterColumn,
559
- defaultValue: { kind: "autoincrement" },
560
- };
561
- await store.addTable(counterTable);
562
- const ranges = await Promise.all(Array.from({ length: 8 }, () => store.reserveRowIds("table-t", 5)));
563
- const starts = new Set(ranges.map((range) => range.start));
564
- check(starts.size === 8, "concurrent reservations returned overlapping ranges");
565
- for (const range of ranges) {
566
- check(range.endExclusive - range.start === 5n, "a reservation returned the wrong span");
567
- }
568
- const bumped = await store.reserveAutoIncrement("table-t", "col-id", 3, 100n);
569
- check(bumped.start >= 100n, "reserveAutoIncrement must honor atLeast");
570
- const maximumExclusive = BigInt(Number.MAX_SAFE_INTEGER) + 1n;
571
- const { snapshotVersion: _invalidSnapshot, ...invalidRecord } = transaction("txn-invalid-counter", null);
572
- void _invalidSnapshot;
573
- await checkThrows(() => store.beginTransaction({
574
- record: invalidRecord,
575
- reserveAutoIncrement: {
576
- tableId: "table-t",
577
- columnId: "col-id",
578
- count: 0,
579
- atLeast: maximumExclusive + 1n,
580
- },
581
- }), RangeError, "an invalid fused auto-increment reservation");
582
- check((await store.getTransaction(invalidRecord.id)) === undefined, "a refused fused reservation must not create its transaction");
583
- const { snapshotVersion: _validSnapshot, ...validRecord } = transaction("txn-valid-counter", null);
584
- void _validSnapshot;
585
- const valid = await store.beginTransaction({
586
- record: validRecord,
587
- reserveAutoIncrement: {
588
- tableId: "table-t",
589
- columnId: "col-id",
590
- count: 1,
591
- atLeast: 150n,
592
- },
593
- });
594
- check(valid.autoIncrementValues?.start === 150n &&
595
- valid.autoIncrementValues.endExclusive === 151n, "a valid fused reservation after refusal must use the unchanged counter");
596
- await store.updateTransaction(valid.record.id, valid.record.revision, {
597
- status: "aborted",
598
- updatedAt: LATER,
599
- });
600
- await checkThrows(() => store.reserveAutoIncrement("table-t", "col-id", 0, maximumExclusive + 1n), RangeError, "an auto-increment bump outside the safe-integer range");
601
- const afterRefusal = await store.reserveAutoIncrement("table-t", "col-id", 1, 200n);
602
- check(afterRefusal.start === 200n && afterRefusal.endExclusive === 201n, "a refused auto-increment bump must not mutate the counter");
603
- const boundary = await store.reserveAutoIncrement("table-t", "col-id", 1, maximumExclusive - 1n);
604
- check(boundary.endExclusive === maximumExclusive, "the final safe auto-increment value must remain reservable");
605
- const exhausted = await store.reserveAutoIncrement("table-t", "col-id", 0, maximumExclusive);
606
- check(exhausted.start === maximumExclusive && exhausted.endExclusive === maximumExclusive, "a zero-count bump to the exclusive boundary must be representable");
607
- await checkThrows(() => store.reserveAutoIncrement("table-t", "col-id", 1), RangeError, "an auto-increment range crossing the safe-integer boundary");
608
- store.close();
609
- },
610
- },
611
- {
612
- name: "transaction ownership renewal races expiry abort atomically",
613
- async run(target) {
614
- const store = await target.create();
615
- const record = transaction("txn-live", null);
616
- record.expiresAt = MIDDLE;
617
- await store.createTransaction(record);
618
- check(!(await store.renewTransaction({
619
- transactionId: record.id,
620
- ownerId: record.ownerId,
621
- expiresAtCutoff: MIDDLE,
622
- expiresAt: LATER,
623
- })), "ownership expired exactly at the cutoff must not be resurrected");
624
- check((await store.getTransaction(record.id))?.expiresAt === MIDDLE, "a refused expiry-boundary renewal mutated the record");
625
- check(!(await store.renewTransaction({
626
- transactionId: record.id,
627
- ownerId: "wrong-owner",
628
- expiresAtCutoff: T0,
629
- expiresAt: LATER,
630
- })), "another owner must not renew a transaction");
631
- check((await store.getTransaction(record.id))?.revision === 0, "ownership renewal must not contend on the data revision");
632
- const [renewed, aborted] = await Promise.all([
633
- store.renewTransaction({
634
- transactionId: record.id,
635
- ownerId: record.ownerId,
636
- expiresAtCutoff: T0,
637
- expiresAt: LATER,
638
- }),
639
- store.abortTransactionIfExpired({
640
- transactionId: record.id,
641
- expectedOwnerId: record.ownerId,
642
- expiresAtCutoff: MIDDLE,
643
- updatedAt: LATER,
644
- }),
645
- ]);
646
- const final = await store.getTransaction(record.id);
647
- check(final !== undefined, "the liveness race lost its transaction record");
648
- check((renewed && aborted === undefined && final.status === "active") ||
649
- (!renewed && aborted?.status === "aborted" && final.status === "aborted"), "renewal and expiry abort must have one linearized winner");
650
- if (final.status === "active") {
651
- check(final.expiresAt === LATER, "the winning renewal did not extend ownership");
652
- const expired = await store.abortTransactionIfExpired({
653
- transactionId: final.id,
654
- expectedOwnerId: final.ownerId,
655
- expiresAtCutoff: "9999-12-31T23:59:59.999Z",
656
- updatedAt: LATER,
657
- });
658
- check(expired?.status === "aborted", "an expired owner was not atomically aborted");
659
- }
660
- store.close();
661
- },
662
- },
663
- {
664
- name: "a commit is atomic and visible: manifest, record, segments, keys together",
665
- async run(target) {
666
- const store = await target.create();
667
- await store.addTable(table("t", true));
668
- const { version, blockId } = await commitOne(store, "one", {
669
- keyTokens: ["number:1"],
670
- });
671
- checkEqual(await store.getCurrentManifestVersion(), version, "the version must advance");
672
- const manifest = await store.getCurrentManifest();
673
- check(manifest !== undefined &&
674
- (await store.hasManifestBlocks(manifest.version, [blockId]))[0] === true, "the manifest must include the committed block");
675
- const record = await store.getTransaction("txn-one");
676
- check(record?.status === "committed" && record.committedVersion === version, "the transaction record must flip to committed with the published version");
677
- checkEqual(await store.getExistingUniqueKeys("table-t", ["number:1", "number:2"]), ["number:1"], "committed unique keys must be queryable");
678
- const finalized = await store.getSegment("segment-one");
679
- check(finalized?.level === 0 && finalized.logicalOrder === version, "committed segments must be finalized with level and logical order");
680
- store.close();
681
- },
682
- },
683
- {
684
- name: "commit conflicts are typed exactly, and a refused commit changes nothing",
685
- async run(target) {
686
- const store = await target.create();
687
- await store.addTable(table("t", true));
688
- await commitOne(store, "one");
689
- const staleVersion = null; // The world has moved on from the empty database.
690
- await store.createTransaction(transaction("txn-stale", staleVersion));
691
- await checkThrows(() => store.commitTransaction({
692
- transactionId: "txn-stale",
693
- expectedTransactionRevision: 0,
694
- expectedManifestVersion: staleVersion,
695
- committedAt: LATER,
696
- }), WriteConflictError, "a commit against a moved manifest");
697
- const movedVersion = await store.getCurrentManifestVersion();
698
- await checkThrows(() => store.commitTransaction({
699
- transactionId: "txn-stale",
700
- expectedTransactionRevision: 41,
701
- expectedManifestVersion: movedVersion,
702
- committedAt: LATER,
703
- }), TransactionRecordConflictError, "a commit with a stale transaction revision");
704
- const still = await store.getTransaction("txn-stale");
705
- check(still?.status === "active", "a refused commit must leave the transaction active");
706
- store.close();
707
- },
708
- },
709
- {
710
- name: "duplicate unique keys refuse with UniqueKeyConflictError and commit nothing",
711
- async run(target) {
712
- const store = await target.create();
713
- await store.addTable(table("t", true));
714
- const { version } = await commitOne(store, "one", { keyTokens: ["number:1"] });
715
- await store.createTransaction(transaction("txn-dup", version));
716
- await store.stageTransactionArtifacts({
717
- transactionId: "txn-dup",
718
- expectedRevision: 0,
719
- blocks: [{ id: "block-dup", bytes: Uint8Array.of(9) }],
720
- segments: [],
721
- updatedAt: T0,
722
- });
723
- await checkThrows(() => store.commitTransaction({
724
- transactionId: "txn-dup",
725
- expectedTransactionRevision: 1,
726
- expectedManifestVersion: version,
727
- committedAt: LATER,
728
- uniqueKeyChanges: [
729
- { tableId: "table-t", keyTokens: ["number:1"], requireAbsent: true },
730
- ],
731
- }), UniqueKeyConflictError, "a commit inserting an existing unique key");
732
- checkEqual(await store.getCurrentManifestVersion(), version, "a key conflict must not advance the version");
733
- store.close();
734
- },
735
- },
736
- {
737
- name: "leases pin versions, renew by CAS, and expire honestly",
738
- async run(target) {
739
- const store = await target.create();
740
- await store.addTable(table("t"));
741
- const { version } = await commitOne(store, "one");
742
- await store.createLease({
743
- id: "lease-a",
744
- kind: "reader",
745
- manifestVersion: version,
746
- ownerId: "owner-a",
747
- createdAt: T0,
748
- expiresAt: LATER,
749
- revision: 0,
750
- });
751
- await checkThrows(() => store.renewLease({
752
- id: "lease-a",
753
- expectedRevision: 9,
754
- expiresAtCutoff: T0,
755
- expiresAt: LATER,
756
- }), LeaseConflictError, "renewing with a stale revision");
757
- const renewed = await store.renewLease({
758
- id: "lease-a",
759
- expectedRevision: 0,
760
- expiresAtCutoff: T0,
761
- expiresAt: LATER,
762
- });
763
- check(renewed.revision === 1, "a renewal must advance the revision");
764
- await checkThrows(() => store.removeLease({ id: "lease-a", ownerId: "wrong-owner" }), LeaseOwnerConflictError, "releasing another owner's lease");
765
- check((await store.getLease("lease-a"))?.revision === 1, "a refused lease release mutated the record");
766
- checkEqual(await store.removeLeaseIfExpired("lease-a", 1, PAST), false, "an unexpired lease must not be removed");
767
- checkEqual(await store.removeLeaseIfExpired("lease-a", 1, "3000-01-01T00:00:00.000Z"), true, "an expired lease must be removed");
768
- await store.createLease({
769
- id: "lease-race",
770
- kind: "reader",
771
- manifestVersion: version,
772
- ownerId: "owner-a",
773
- createdAt: T0,
774
- expiresAt: LATER,
775
- revision: 0,
776
- });
777
- const [, release] = await Promise.allSettled([
778
- store.renewLease({
779
- id: "lease-race",
780
- expectedRevision: 0,
781
- expiresAtCutoff: T0,
782
- expiresAt: LATER,
783
- }),
784
- store.removeLease({ id: "lease-race", ownerId: "owner-a" }),
785
- ]);
786
- check(release.status === "fulfilled", "a same-owner renewal made release unsafe");
787
- check((await store.getLease("lease-race")) === undefined, "same-owner release did not linearize after/before renewal");
788
- store.close();
789
- },
790
- },
791
- {
792
- name: "garbage collection reclaims only unpinned, superseded state — atomically",
793
- async run(target) {
794
- const store = await target.create();
795
- await store.addTable(table("t"));
796
- const first = await commitOne(store, "one");
797
- // A fused table drop is the ordinary, intent-bound way to retire visible table bytes.
798
- await store.dropTable({
799
- tableId: "table-t",
800
- expectedTableRevision: 0,
801
- expectedManifestVersion: first.version,
802
- expectedCatalogEpoch: (await store.getCatalogProbe()).catalogEpoch,
803
- committedAt: LATER,
804
- });
805
- const retiredBeforeCollection = await store.listRetiredManifestBlockPage({
806
- removedThroughVersion: first.version + 1,
807
- afterBlockId: null,
808
- limit: 1,
809
- });
810
- checkEqual(retiredBeforeCollection.records.map((record) => record.blockId), [first.blockId], "retired provenance must remain independently pageable before collection");
811
- const job = await store.createGarbageCollectionJob({
812
- id: "gc-1",
813
- candidateManifestVersions: [first.version],
814
- candidateSegmentIds: [],
815
- candidateBlockIds: [first.blockId],
816
- leaseCutoff: LATER,
817
- createdAt: LATER,
818
- });
819
- await checkThrows(() => store.removeGarbageCollectionJob(job.id), Error, "removing a non-completed garbage-collection job");
820
- check((await store.getGarbageCollectionJob(job.id))?.state === "planned", "a refused garbage-collection job removal mutated the record");
821
- const step = await store.runGarbageCollectionStep({
822
- jobId: job.id,
823
- expectedRevision: job.revision,
824
- maxItems: 100,
825
- updatedAt: LATER,
826
- });
827
- checkEqual(step.prunedManifestVersions, [first.version], "the superseded version must prune");
828
- checkEqual(step.reclaimedBlockIds, [first.blockId], "the superseded block must reclaim");
829
- checkEqual(await store.getBlock(first.blockId), undefined, "reclaimed bytes must be gone");
830
- checkEqual((await store.listRetiredManifestBlockPage({
831
- removedThroughVersion: first.version + 1,
832
- afterBlockId: null,
833
- limit: 1,
834
- })).records, [], "payload reclamation must atomically remove its retired provenance");
835
- checkEqual(await store.getCurrentManifestVersion(), 1, "the successor manifest was lost");
836
- const pruned = await store.getManifest(first.version);
837
- check(pruned?.prunedAt !== undefined, "pruned manifests are tombstoned, not deleted");
838
- await store.removeGarbageCollectionJob(job.id);
839
- check((await store.getGarbageCollectionJob(job.id)) === undefined, "a completed garbage-collection job was retained");
840
- store.close();
841
- },
842
- },
843
- {
844
- name: "temp spill pages are isolated scratch; owner records conflict by exact class",
845
- async run(target) {
846
- const store = await target.create();
847
- await store.createTempOwner({
848
- ownerId: "o1",
849
- createdAt: T0,
850
- expiresAt: LATER,
851
- revision: 0,
852
- });
853
- await store.putTempRunPage({
854
- ownerId: "o1",
855
- runId: "r1",
856
- pageIndex: 0,
857
- bytes: Uint8Array.of(1),
858
- });
859
- const batched = store.putTempRunPages?.bind(store);
860
- if (batched !== undefined) {
861
- await batched([
862
- { ownerId: "o1", runId: "r1", pageIndex: 1, bytes: Uint8Array.of(2) },
863
- { ownerId: "o1", runId: "r2", pageIndex: 0, bytes: Uint8Array.of(3) },
864
- ]);
865
- checkEqual(await store.getTempRunPage("o1", "r1", 1), Uint8Array.of(2), "batched pages must read back identically");
866
- }
867
- await store.removeTempRun("o1", "r1");
868
- checkEqual(await store.getTempRunPage("o1", "r1", 0), undefined, "a removed run must be gone");
869
- await checkThrows(() => store.renewTempOwner({
870
- ownerId: "o1",
871
- expectedRevision: 9,
872
- expiresAtCutoff: T0,
873
- expiresAt: LATER,
874
- }), TempOwnerConflictError, "renewing a temp owner with a stale revision");
875
- checkEqual(await store.removeTempOwnerIfExpired("o1", PAST), false, "an unexpired owner must survive the sweep");
876
- const listed = await store.listTempOwnerIdsPage(null, 10);
877
- check(listed.records.includes("o1"), "owners must be listable");
878
- store.close();
879
- },
880
- },
881
- {
882
- name: "compaction job records update by CAS with the exact conflict class",
883
- async run(target) {
884
- const store = await target.create();
885
- await store.addTable(table("t"));
886
- const { version, blockId } = await commitOne(store, "one");
887
- await store.createCompactionJob({
888
- id: "job-1",
889
- tableId: "table-t",
890
- sourceManifestVersion: version,
891
- sourceSegmentIds: ["segment-one"],
892
- sourceBlockIds: [blockId],
893
- outputBlockIds: [],
894
- cursor: { sourceSegmentIndex: 0, sourceBlockIndex: 0 },
895
- processedRows: 0,
896
- sourceStoredBytes: 0,
897
- outputStoredBytes: 0,
898
- logicalBytes: 0,
899
- rewritePlan: { kind: "copy-v1" },
900
- outputCursor: null,
901
- memoryBudgetBytes: 0,
902
- minimumMemoryBytes: 0,
903
- level0SourceStoredBytes: 0,
904
- anchorSourceStoredBytes: 0,
905
- peakWorkingBytes: 0,
906
- outputLogicalBytes: 0,
907
- targetLevel: 1,
908
- state: "planned",
909
- transactionId: null,
910
- outputSegmentId: null,
911
- publishedVersion: null,
912
- revision: 0,
913
- createdAt: T0,
914
- updatedAt: T0,
915
- });
916
- await checkThrows(() => store.removeCompactionJob("job-1"), Error, "removing a nonterminal compaction job");
917
- check((await store.getCompactionJob("job-1"))?.state === "planned", "a refused compaction-job removal mutated the record");
918
- await checkThrows(() => store.updateCompactionJob("job-1", 9, {
919
- state: "running",
920
- transactionId: "txn-one",
921
- updatedAt: LATER,
922
- }), CompactionJobConflictError, "updating a compaction job with a stale revision");
923
- const updated = await store.updateCompactionJob("job-1", 0, { updatedAt: LATER });
924
- check(updated.revision === 1, "a job update must advance the revision");
925
- await store.cancelCompactionJob("job-1", updated.revision, LATER);
926
- check(await store.removeCompactionJob("job-1"), "terminal job was not removed");
927
- check((await store.getCompactionJob("job-1")) === undefined, "terminal job was retained");
928
- await store.createTransaction(transaction("txn-orphan-output", version));
929
- await store.stageTransactionArtifacts({
930
- transactionId: "txn-orphan-output",
931
- expectedRevision: 0,
932
- blocks: [{ id: "orphan-compaction-output", bytes: Uint8Array.of(9) }],
933
- segments: [],
934
- updatedAt: T0,
935
- });
936
- await store.updateTransaction("txn-orphan-output", 1, {
937
- status: "aborted",
938
- updatedAt: LATER,
939
- });
940
- await store.createCompactionJob({
941
- id: "job-orphan-output",
942
- tableId: "table-t",
943
- sourceManifestVersion: version,
944
- sourceSegmentIds: ["segment-one"],
945
- sourceBlockIds: [blockId],
946
- outputBlockIds: ["orphan-compaction-output"],
947
- cursor: { sourceSegmentIndex: 1, sourceBlockIndex: 0 },
948
- processedRows: 0,
949
- sourceStoredBytes: 0,
950
- outputStoredBytes: 1,
951
- logicalBytes: 1,
952
- rewritePlan: { kind: "copy-v1" },
953
- outputCursor: null,
954
- memoryBudgetBytes: 0,
955
- minimumMemoryBytes: 0,
956
- level0SourceStoredBytes: 0,
957
- anchorSourceStoredBytes: 0,
958
- peakWorkingBytes: 0,
959
- outputLogicalBytes: 1,
960
- targetLevel: 1,
961
- state: "cancelled",
962
- transactionId: null,
963
- outputSegmentId: null,
964
- publishedVersion: null,
965
- revision: 0,
966
- createdAt: T0,
967
- updatedAt: LATER,
968
- });
969
- check(await store.removeCompactionJob("job-orphan-output"), "terminal compaction metadata with independent transaction provenance was retained");
970
- check((await store.getBlock("orphan-compaction-output")) !== undefined, "removing terminal metadata deleted an independently proven output");
971
- store.close();
972
- },
973
- },
974
- {
975
- name: "full-text bases serve exact and prefix candidates with honest coverage",
976
- async run(target) {
977
- const store = await target.create();
978
- await store.addTable({
979
- ...table("t"),
980
- ftsColumns: {
981
- "col-v": {
982
- storage: "fts-chunks-v1",
983
- tokenizerVersion: 1,
984
- state: "ready",
985
- buildFromVersion: 0,
986
- },
987
- },
988
- });
989
- await store.writeFtsBase("table-t", "col-v", {
990
- coversVersion: 0,
991
- chunks: [
992
- [
993
- { term: "minnow", rowIds: [1n, 3n], tf: [1, 2] },
994
- { term: "minnows", rowIds: [2n], tf: [1] },
995
- ],
996
- [{ term: "shark", rowIds: [4n], tf: [1] }],
997
- ],
998
- totalTokens: 5,
999
- });
1000
- const exact = await store.readFtsCandidates("table-t", "col-v", [{ term: "minnow", prefix: false }], 10);
1001
- checkEqual(exact.rowIdsByTerm, [[1n, 3n]], "exact terms must match exactly");
1002
- check(exact.hasBase && exact.coversVersion === 0 && exact.totalTokens === 5, "coverage and token totals must report the stored base");
1003
- const prefix = await store.readFtsCandidates("table-t", "col-v", [{ term: "minnow", prefix: true }], 10);
1004
- checkEqual(prefix.rowIdsByTerm, [[1n, 2n, 3n]], "prefix terms must match the term range, row ids ascending and unique");
1005
- const ordered = await store.readFtsPostings("table-t", "col-v", 10);
1006
- checkEqual(ordered.postings, [
1007
- { term: "minnow", rowIds: [1n, 3n], tf: [1, 2] },
1008
- { term: "minnows", rowIds: [2n], tf: [1] },
1009
- { term: "shark", rowIds: [4n], tf: [1] },
1010
- ], "ordered postings must merge into canonical term and row-ID order");
1011
- // The read-argument contract is uniform across adapters: -1 selects the base view
1012
- // alone, anything below it (or non-integral) is refused, and the candidate limit is
1013
- // validated on every read path.
1014
- const baseOnly = await store.readFtsPostings("table-t", "col-v", -1);
1015
- check(baseOnly.hasBase && baseOnly.postings.length === 3, "an upToVersion of -1 must serve the base view alone");
1016
- for (const version of [-2, Number.NaN, 1.5]) {
1017
- try {
1018
- await store.readFtsCandidates("table-t", "col-v", [{ term: "minnow", prefix: false }], version);
1019
- throw new Error(`candidate read accepted invalid version ${String(version)}`);
1020
- }
1021
- catch (error) {
1022
- check(error instanceof RangeError, "invalid candidate versions must throw RangeError");
1023
- }
1024
- try {
1025
- await store.readFtsPostings("table-t", "col-v", version);
1026
- throw new Error(`postings read accepted invalid version ${String(version)}`);
1027
- }
1028
- catch (error) {
1029
- check(error instanceof RangeError, "invalid postings versions must throw RangeError");
1030
- }
1031
- }
1032
- try {
1033
- await store.readFtsCandidates("table-t", "col-v", [{ term: "minnow", prefix: false }], 10, 0);
1034
- throw new Error("candidate read accepted a zero row-ID limit");
1035
- }
1036
- catch (error) {
1037
- check(error instanceof RangeError, "invalid candidate limits must throw RangeError");
1038
- }
1039
- store.close();
1040
- },
1041
- },
1042
- {
1043
- name: "postings builds publish bounded generations atomically and reclaim replacements",
1044
- async run(target) {
1045
- let store = await target.create();
1046
- await store.addTable({
1047
- ...table("postings"),
1048
- ftsColumns: {
1049
- "col-v": {
1050
- storage: "fts-chunks-v1",
1051
- tokenizerVersion: 1,
1052
- state: "building",
1053
- buildFromVersion: -1,
1054
- },
1055
- },
1056
- });
1057
- await store.beginFtsBaseBuild({
1058
- tableId: "table-postings",
1059
- columnId: "col-v",
1060
- buildId: "abandoned",
1061
- ownerId: "owner-abandoned",
1062
- createdAt: "2099-01-01T00:00:00.000Z",
1063
- expiresAt: "2099-01-01T00:10:00.000Z",
1064
- });
1065
- await store.writeFtsBaseBuildChunk({
1066
- tableId: "table-postings",
1067
- columnId: "col-v",
1068
- buildId: "abandoned",
1069
- ownerId: "owner-abandoned",
1070
- expiresAtCutoff: "2099-01-01T00:00:01.000Z",
1071
- expiresAt: "2099-01-01T00:10:00.000Z",
1072
- updatedAt: "2099-01-01T00:00:01.000Z",
1073
- ordinal: 0,
1074
- chunk: [{ term: "old", rowIds: [1n], tf: [1] }],
1075
- });
1076
- await store.beginFtsBaseBuild({
1077
- tableId: "table-postings",
1078
- columnId: "col-v",
1079
- buildId: "replacement",
1080
- ownerId: "owner-replacement",
1081
- createdAt: "2099-01-01T00:10:00.000Z",
1082
- expiresAt: "2099-01-01T00:20:00.000Z",
1083
- });
1084
- const firstChunk = {
1085
- tableId: "table-postings",
1086
- columnId: "col-v",
1087
- buildId: "replacement",
1088
- ownerId: "owner-replacement",
1089
- expiresAtCutoff: "2099-01-01T00:10:01.000Z",
1090
- expiresAt: "2099-01-01T00:20:00.000Z",
1091
- updatedAt: "2099-01-01T00:10:01.000Z",
1092
- ordinal: 0,
1093
- chunk: [{ term: "alpha", rowIds: [2n], tf: [1] }],
1094
- };
1095
- await store.writeFtsBaseBuildChunk(firstChunk);
1096
- // Lost-ack replay is idempotent: retrying the identical chunk neither duplicates it nor
1097
- // advances the ordinal, while changing acknowledged bytes is an ownership conflict.
1098
- await store.writeFtsBaseBuildChunk(firstChunk);
1099
- await checkThrows(() => store.writeFtsBaseBuildChunk({
1100
- ...firstChunk,
1101
- chunk: [{ term: "alpha", rowIds: [999n], tf: [1] }],
1102
- }), PostingBuildConflictError, "replaying an acknowledged postings ordinal with changed bytes");
1103
- if (target.reopen !== undefined) {
1104
- store = await target.reopen(store);
1105
- // The active owner and completed ordinal are durable too. Replaying after a process
1106
- // restart must still be a no-op, not a duplicate allocation or a vanished build.
1107
- await store.writeFtsBaseBuildChunk(firstChunk);
1108
- }
1109
- await store.writeFtsBaseBuildChunk({
1110
- tableId: "table-postings",
1111
- columnId: "col-v",
1112
- buildId: "replacement",
1113
- ownerId: "owner-replacement",
1114
- expiresAtCutoff: "2099-01-01T00:10:02.000Z",
1115
- expiresAt: "2099-01-01T00:20:00.000Z",
1116
- updatedAt: "2099-01-01T00:10:02.000Z",
1117
- ordinal: 1,
1118
- chunk: [{ term: "omega", rowIds: [3n], tf: [1] }],
1119
- });
1120
- await store.finishFtsBaseBuild({
1121
- tableId: "table-postings",
1122
- columnId: "col-v",
1123
- buildId: "replacement",
1124
- ownerId: "owner-replacement",
1125
- expiresAtCutoff: "2099-01-01T00:10:03.000Z",
1126
- coversVersion: 4,
1127
- chunkCount: 2,
1128
- totalTokens: 2,
1129
- completedAt: "2099-01-01T00:10:03.000Z",
1130
- });
1131
- let candidates = await store.readFtsCandidates("table-postings", "col-v", [
1132
- { term: "old", prefix: false },
1133
- { lower: "alpha", lowerInclusive: true, upper: "omega", upperInclusive: true },
1134
- ], 4);
1135
- checkEqual(candidates.rowIdsByTerm, [[], [2n, 3n]], "only a finished replacement generation may become visible");
1136
- check(candidates.hasBase, "a finished generation must report a published base");
1137
- if (target.reopen !== undefined) {
1138
- store = await target.reopen(store);
1139
- candidates = await store.readFtsCandidates("table-postings", "col-v", [{ term: "omega", prefix: false }], 4);
1140
- checkEqual(candidates.rowIdsByTerm, [[3n]], "a finished generation must survive reopen");
1141
- check(candidates.hasBase, "a reopened generation must still report its base");
1142
- }
1143
- await checkThrows(() => store.removeFtsColumn("table-postings", "col-v"), Error, "removing postings still owned by a ready catalog accelerator");
1144
- const indexed = await store.getTable("table-postings");
1145
- check(indexed !== undefined, "the postings table disappeared");
1146
- await store.updateTable(indexed.id, indexed.revision, {
1147
- ftsColumns: {
1148
- ...indexed.ftsColumns,
1149
- "col-v": {
1150
- ...(indexed.ftsColumns?.["col-v"] ?? {
1151
- storage: "fts-chunks-v1",
1152
- tokenizerVersion: 1,
1153
- buildFromVersion: -1,
1154
- }),
1155
- state: "invalid",
1156
- },
1157
- },
1158
- });
1159
- await store.removeFtsColumn("table-postings", "col-v");
1160
- candidates = await store.readFtsCandidates("table-postings", "col-v", [{ term: "alpha", prefix: false }], 4);
1161
- checkEqual(candidates.rowIdsByTerm, [[]], "removing an index must remove its candidates");
1162
- check(!candidates.hasBase, "a removed base must be distinguishable from an empty base");
1163
- store.close();
1164
- },
1165
- },
1166
- {
1167
- name: "UNIQUE-index membership seeds and drops atomically with its catalog record",
1168
- async run(target) {
1169
- const store = await target.create();
1170
- const record = table("unique-secondary");
1171
- const indexId = "unique-v";
1172
- const namespaceId = secondaryUniqueKeyNamespace(record.id, indexId);
1173
- const building = {
1174
- name: "unique_secondary_v",
1175
- columnId: "col-v",
1176
- columnIds: ["col-v"],
1177
- directions: ["asc"],
1178
- unique: true,
1179
- termEncoding: "tuple-v1",
1180
- storage: "postings-v1",
1181
- storageColumnId: "secondary-index:unique-v",
1182
- locator: "row-id",
1183
- state: "building",
1184
- buildId: "builder",
1185
- buildFromVersion: -1,
1186
- };
1187
- const { buildId: _buildId, ...withoutBuilder } = building;
1188
- void _buildId;
1189
- const ready = {
1190
- ...withoutBuilder,
1191
- state: "ready",
1192
- uniqueEnforced: true,
1193
- };
1194
- await store.addTable({ ...record, secondaryIndexes: { [indexId]: building } });
1195
- try {
1196
- await store.updateTable(record.id, 0, {
1197
- secondaryIndexes: { [indexId]: ready },
1198
- expectedManifestVersion: { value: null },
1199
- uniqueKeySeed: { namespaceId: `${namespaceId}-wrong`, keyTokens: ["x"] },
1200
- });
1201
- throw new Error("a seed for the wrong namespace was accepted");
1202
- }
1203
- catch (error) {
1204
- check(error instanceof TypeError, "a mismatched UNIQUE seed must be rejected");
1205
- }
1206
- check((await store.getTable(record.id))?.revision === 0, "a mismatched seed must not mutate the catalog");
1207
- await checkThrows(() => store.updateTable(record.id, 0, {
1208
- secondaryIndexes: {
1209
- [indexId]: ready,
1210
- },
1211
- expectedManifestVersion: { value: null },
1212
- uniqueKeySeed: { namespaceId, keyTokens: ["x", "x"] },
1213
- }), UniqueKeyConflictError, "a duplicate UNIQUE seed");
1214
- check((await store.getTable(record.id))?.secondaryIndexes?.[indexId]?.state === "building", "a refused seed must not publish the ready catalog state");
1215
- await store.updateTable(record.id, 0, {
1216
- secondaryIndexes: {
1217
- [indexId]: ready,
1218
- },
1219
- expectedManifestVersion: { value: null },
1220
- uniqueKeySeed: { namespaceId, keyTokens: ["x", "y"] },
1221
- });
1222
- checkEqual(await store.getExistingUniqueKeys(namespaceId, ["x", "z"]), ["x"], "the ready catalog and membership must publish together");
1223
- await store.updateTable(record.id, 1, { secondaryIndexes: null });
1224
- checkEqual(await store.getExistingUniqueKeys(namespaceId, ["x", "y"]), [], "dropping the index must reclaim its membership namespace");
1225
- store.close();
1226
- },
1227
- },
1228
- {
1229
- name: "atomic staging and rollback keep journals and artifacts inseparable",
1230
- async run(target) {
1231
- let store = await target.create();
1232
- await store.addTable(table("t"));
1233
- {
1234
- const begun = await store.beginTransaction({
1235
- record: {
1236
- id: "txn-b",
1237
- ownerId: "txn-b/owner",
1238
- expiresAt: LATER,
1239
- pendingBlockIds: [],
1240
- pendingSegmentIds: [],
1241
- status: "active",
1242
- revision: 0,
1243
- startedAt: T0,
1244
- updatedAt: T0,
1245
- committedVersion: null,
1246
- },
1247
- reserveRowIds: { tableId: "table-t", count: 4 },
1248
- });
1249
- check(begun.record.snapshotVersion === (await store.getCurrentManifestVersion()), "beginTransaction must pin the current version");
1250
- check(begun.rowIds !== undefined && begun.rowIds.endExclusive - begun.rowIds.start === 4n, "beginTransaction must honor the reservation");
1251
- }
1252
- const stage = store.stageTransactionArtifacts.bind(store);
1253
- await store.createTransaction(transaction("txn-s", await store.getCurrentManifestVersion()));
1254
- const duplicate = segment("segment-duplicate", "table-t", "txn-s", "block-none");
1255
- await checkThrows(() => stage({
1256
- transactionId: "txn-s",
1257
- expectedRevision: 0,
1258
- blocks: [],
1259
- segments: [duplicate, duplicate],
1260
- updatedAt: LATER,
1261
- }), Error, "staging duplicate segment IDs");
1262
- await checkThrows(() => stage({
1263
- transactionId: "txn-s",
1264
- expectedRevision: 0,
1265
- blocks: [],
1266
- segments: [segment("segment-foreign", "table-t", "another-txn", "block-none")],
1267
- updatedAt: LATER,
1268
- }), Error, "staging a segment owned by another transaction");
1269
- check((await store.getSegment("segment-duplicate")) === undefined &&
1270
- (await store.getSegment("segment-foreign")) === undefined &&
1271
- (await store.getTransaction("txn-s"))?.revision === 0, "refused staging must leave the transaction and segment store unchanged");
1272
- const first = await stage({
1273
- transactionId: "txn-s",
1274
- expectedRevision: 0,
1275
- blocks: [{ id: "block-z", bytes: Uint8Array.of(7) }],
1276
- segments: [segment("segment-z", "table-t", "txn-s", "block-z")],
1277
- updatedAt: LATER,
1278
- });
1279
- await checkThrows(() => stage({
1280
- transactionId: "txn-s",
1281
- expectedRevision: first.revision,
1282
- blocks: [{ id: "block-gap", bytes: Uint8Array.of(9) }],
1283
- segments: [segment("segment-gap", "table-t", "txn-s", "block-gap", 0)],
1284
- updatedAt: LATER,
1285
- }), TypeError, "staging a segment whose commit ordinal does not continue the journal");
1286
- check((await store.getTransaction("txn-s"))?.revision === first.revision &&
1287
- (await store.getBlock("block-gap")) === undefined &&
1288
- (await store.getSegment("segment-gap")) === undefined, "an ordinal refusal must leave the record, block, and segment unchanged");
1289
- const staged = await stage({
1290
- transactionId: "txn-s",
1291
- expectedRevision: first.revision,
1292
- blocks: [{ id: "block-a", bytes: Uint8Array.of(8) }],
1293
- segments: [segment("segment-a", "table-t", "txn-s", "block-a", 1)],
1294
- updatedAt: LATER,
1295
- });
1296
- checkEqual(staged.pendingBlockIds, ["block-z", "block-a"], "staging journals blocks");
1297
- checkEqual(staged.pendingSegmentIds, ["segment-z", "segment-a"], "staging preserves the canonical segment journal order");
1298
- check((await store.getBlock("block-a")) !== undefined &&
1299
- (await store.getSegment("segment-a")) !== undefined, "staging must write bytes and segments");
1300
- const rollbackInput = {
1301
- transactionId: "txn-s",
1302
- expectedRevision: staged.revision,
1303
- pendingBlockIds: ["block-z"],
1304
- pendingSegmentIds: ["segment-z"],
1305
- removeBlockIds: ["block-a"],
1306
- removeSegmentIds: ["segment-a"],
1307
- updatedAt: LATER,
1308
- };
1309
- await checkThrows(() => store.rollbackTransactionArtifacts({
1310
- ...rollbackInput,
1311
- expectedRevision: staged.revision + 1,
1312
- }), TransactionRecordConflictError, "rollback with a stale transaction revision");
1313
- await checkThrows(() => store.rollbackTransactionArtifacts({
1314
- ...rollbackInput,
1315
- removeBlockIds: [],
1316
- }), TypeError, "rollback whose lists do not exactly partition the journal");
1317
- await checkThrows(() => store.rollbackTransactionArtifacts({
1318
- ...rollbackInput,
1319
- pendingBlockIds: ["block-a"],
1320
- pendingSegmentIds: ["segment-a"],
1321
- removeBlockIds: ["block-z"],
1322
- removeSegmentIds: ["segment-z"],
1323
- }), TypeError, "rollback that retains a non-prefix journal partition");
1324
- check((await store.getBlock("block-a")) !== undefined &&
1325
- (await store.getSegment("segment-a")) !== undefined, "a refused rollback must leave every artifact intact");
1326
- const rolledBack = await store.rollbackTransactionArtifacts(rollbackInput);
1327
- checkEqual(rolledBack.pendingBlockIds, ["block-z"], "rollback must retain checkpoint block");
1328
- check((await store.getBlock("block-a")) === undefined &&
1329
- (await store.getSegment("segment-a")) === undefined &&
1330
- (await store.getBlock("block-z")) !== undefined &&
1331
- (await store.getSegment("segment-z")) !== undefined, "rollback must delete only its exact removed partition");
1332
- if (target.reopen !== undefined) {
1333
- const reopened = await target.reopen(store);
1334
- checkEqual((await reopened.getTransaction("txn-s"))?.pendingBlockIds, ["block-z"], "rollback journal must survive reopen");
1335
- check((await reopened.getBlock("block-a")) === undefined &&
1336
- (await reopened.getBlock("block-z")) !== undefined, "rollback artifact removal must survive reopen");
1337
- store = reopened;
1338
- }
1339
- const before = await store.getCatalogProbe();
1340
- await store.addTable(table("probe-check"));
1341
- const after = await store.getCatalogProbe();
1342
- check(after.catalogEpoch > before.catalogEpoch, "a catalog mutation must advance the probe's epoch");
1343
- store.close();
1344
- },
1345
- },
1346
- {
1347
- name: "the single-shot write, when present, commits atomically and refuses typed",
1348
- async run(target) {
1349
- const store = await target.create();
1350
- const write = store.writeTransaction?.bind(store);
1351
- if (write === undefined) {
1352
- store.close();
1353
- return;
1354
- }
1355
- await store.addTable(table("t", true));
1356
- const schemaEpoch = (await store.getCatalogProbe()).schemaEpoch;
1357
- const fresh = (id) => {
1358
- const { snapshotVersion: _pinned, ...record } = transaction(id, null);
1359
- void _pinned;
1360
- return { ...record, schemaEpochGuard: schemaEpoch };
1361
- };
1362
- // A fresh record: begun, staged, and committed in the one step.
1363
- const first = await write({
1364
- transaction: { record: fresh("txn-w1") },
1365
- blocks: [{ id: "block-w1", bytes: Uint8Array.of(1) }],
1366
- segments: [segment("segment-w1", "table-t", "txn-w1", "block-w1")],
1367
- expectedManifestVersion: null,
1368
- levelZeroSegmentLimits: [{ tableId: "table-t", limit: MAX_LEVEL_ZERO_SEGMENTS }],
1369
- uniqueKeyChanges: [{ tableId: "table-t", keyTokens: ["number:1"], requireAbsent: true }],
1370
- committedAt: LATER,
1371
- });
1372
- checkEqual(first.version, 0, "the first single-shot write must publish version 0");
1373
- const committed = await store.getTransaction("txn-w1");
1374
- check(committed?.status === "committed" &&
1375
- committed.committedVersion === 0 &&
1376
- committed.snapshotVersion === null, "the fresh record must end committed, pinned at the expected version");
1377
- checkEqual(committed.pendingBlockIds, ["block-w1"], "the single-shot write must journal its blocks on the record");
1378
- check((await store.getBlock("block-w1")) !== undefined &&
1379
- (await store.getSegment("segment-w1"))?.logicalOrder === 0, "the single-shot write must land bytes and finalized segments");
1380
- checkEqual(await store.getExistingUniqueKeys("table-t", ["number:1"]), ["number:1"], "the single-shot write must apply unique-key changes");
1381
- // A stale version refuses as WriteConflictError and leaves nothing behind — not even
1382
- // the record.
1383
- await checkThrows(() => write({
1384
- transaction: { record: fresh("txn-stale") },
1385
- blocks: [{ id: "block-stale", bytes: Uint8Array.of(2) }],
1386
- segments: [segment("segment-stale", "table-t", "txn-stale", "block-stale")],
1387
- expectedManifestVersion: null,
1388
- levelZeroSegmentLimits: [{ tableId: "table-t", limit: MAX_LEVEL_ZERO_SEGMENTS }],
1389
- committedAt: LATER,
1390
- }), WriteConflictError, "a single-shot write against a moved manifest");
1391
- check((await store.getTransaction("txn-stale")) === undefined &&
1392
- (await store.getBlock("block-stale")) === undefined &&
1393
- (await store.getSegment("segment-stale")) === undefined, "a refused single-shot write must leave no record, block, or segment");
1394
- // So does a duplicate unique key.
1395
- await checkThrows(() => write({
1396
- transaction: { record: fresh("txn-dup") },
1397
- blocks: [{ id: "block-dup", bytes: Uint8Array.of(3) }],
1398
- segments: [segment("segment-dup", "table-t", "txn-dup", "block-dup")],
1399
- expectedManifestVersion: 0,
1400
- levelZeroSegmentLimits: [{ tableId: "table-t", limit: MAX_LEVEL_ZERO_SEGMENTS }],
1401
- uniqueKeyChanges: [
1402
- { tableId: "table-t", keyTokens: ["number:1"], requireAbsent: true },
1403
- ],
1404
- committedAt: LATER,
1405
- }), UniqueKeyConflictError, "a single-shot write inserting an existing unique key");
1406
- check((await store.getTransaction("txn-dup")) === undefined &&
1407
- (await store.getBlock("block-dup")) === undefined, "a key conflict must leave no record or block");
1408
- checkEqual(await store.getCurrentManifestVersion(), 0, "refusals must not advance");
1409
- // A begun transaction — the shape a reservation needs — continues into the same step.
1410
- await store.createTransaction(transaction("txn-w2", 0));
1411
- await checkThrows(() => write({
1412
- transaction: { id: "txn-w2", expectedRevision: 4 },
1413
- blocks: [{ id: "block-w2", bytes: Uint8Array.of(4) }],
1414
- segments: [segment("segment-w2", "table-t", "txn-w2", "block-w2")],
1415
- expectedManifestVersion: 0,
1416
- levelZeroSegmentLimits: [{ tableId: "table-t", limit: MAX_LEVEL_ZERO_SEGMENTS }],
1417
- committedAt: LATER,
1418
- }), TransactionRecordConflictError, "a single-shot write with a stale transaction revision");
1419
- check((await store.getBlock("block-w2")) === undefined, "a revision conflict must leave no block");
1420
- const second = await write({
1421
- transaction: { id: "txn-w2", expectedRevision: 0 },
1422
- blocks: [{ id: "block-w2", bytes: Uint8Array.of(4) }],
1423
- segments: [segment("segment-w2", "table-t", "txn-w2", "block-w2")],
1424
- expectedManifestVersion: 0,
1425
- levelZeroSegmentLimits: [{ tableId: "table-t", limit: MAX_LEVEL_ZERO_SEGMENTS }],
1426
- changedTableIds: ["table-t"],
1427
- committedAt: LATER,
1428
- });
1429
- checkEqual(second.version, 1, "the continued write must publish the next version");
1430
- checkEqual(second.changedTableIds, ["table-t"], "the summary must carry the change set");
1431
- checkEqual(await manifestBlockIds(store, 1), ["block-w1", "block-w2"], "the continued write must preserve visible blocks and add its own");
1432
- check((await store.getTransaction("txn-w2"))?.status === "committed", "the begun record must end committed");
1433
- store.close();
1434
- },
1435
- },
1436
- {
1437
- name: "compaction commit proves exact output provenance before publication",
1438
- async run(target) {
1439
- const store = await target.create();
1440
- await store.addTable({
1441
- id: "table-compact",
1442
- name: "compact",
1443
- columns: [{ id: "col-value", name: "value", type: "number", nullable: false }],
1444
- managed: false,
1445
- revision: 0,
1446
- createdAt: T0,
1447
- });
1448
- await store.createTransaction(transaction("txn-compact-source", null));
1449
- const source = await store.stageTransactionArtifacts({
1450
- transactionId: "txn-compact-source",
1451
- expectedRevision: 0,
1452
- blocks: [{ id: "compact-source-block", bytes: Uint8Array.of(1) }],
1453
- segments: [
1454
- {
1455
- ...segment("compact-source-segment", "table-compact", "txn-compact-source", "compact-source-block"),
1456
- columnBlockIds: { "col-value": ["compact-source-block"] },
1457
- },
1458
- ],
1459
- updatedAt: T0,
1460
- });
1461
- await store.commitTransaction({
1462
- transactionId: source.id,
1463
- expectedTransactionRevision: source.revision,
1464
- expectedManifestVersion: null,
1465
- levelZeroSegmentLimits: [{ tableId: "table-compact", limit: MAX_LEVEL_ZERO_SEGMENTS }],
1466
- committedAt: T0,
1467
- });
1468
- const jobId = "compact-proof-job";
1469
- const outputBlockId = `${jobId}/output/segment/000000/column/000000/part/000000`;
1470
- await store.createTransaction(transaction("txn-compact-output", 0));
1471
- const output = await store.stageTransactionArtifacts({
1472
- transactionId: "txn-compact-output",
1473
- expectedRevision: 0,
1474
- blocks: [{ id: outputBlockId, bytes: Uint8Array.of(2) }],
1475
- segments: [
1476
- {
1477
- ...segment("compact-output-segment", "table-compact", "txn-compact-output", outputBlockId),
1478
- columnBlockIds: { "col-value": [outputBlockId] },
1479
- // Structurally valid, but copy-v1 is required to publish an insert segment.
1480
- kind: "base",
1481
- level: 1,
1482
- },
1483
- ],
1484
- updatedAt: LATER,
1485
- });
1486
- const job = {
1487
- id: jobId,
1488
- tableId: "table-compact",
1489
- sourceManifestVersion: 0,
1490
- sourceSegmentIds: ["compact-source-segment"],
1491
- sourceBlockIds: ["compact-source-block"],
1492
- outputBlockIds: [outputBlockId],
1493
- cursor: { sourceSegmentIndex: 1, sourceBlockIndex: 0 },
1494
- processedRows: 2,
1495
- sourceStoredBytes: 1,
1496
- outputStoredBytes: 1,
1497
- logicalBytes: 1,
1498
- rewritePlan: { kind: "copy-v1" },
1499
- outputCursor: null,
1500
- memoryBudgetBytes: 0,
1501
- minimumMemoryBytes: 0,
1502
- level0SourceStoredBytes: 1,
1503
- anchorSourceStoredBytes: 0,
1504
- peakWorkingBytes: 0,
1505
- outputLogicalBytes: 1,
1506
- targetLevel: 1,
1507
- state: "ready",
1508
- transactionId: output.id,
1509
- outputSegmentId: "compact-output-segment",
1510
- publishedVersion: null,
1511
- revision: 0,
1512
- createdAt: T0,
1513
- updatedAt: LATER,
1514
- };
1515
- await store.createCompactionJob(job);
1516
- await checkThrows(() => store.commitTransaction({
1517
- transactionId: output.id,
1518
- expectedTransactionRevision: output.revision,
1519
- expectedManifestVersion: 0,
1520
- compactionJobId: job.id,
1521
- removedBlockIds: ["compact-source-block"],
1522
- committedAt: LATER,
1523
- }), Error, "a structurally valid compaction segment that differs from the immutable plan");
1524
- checkEqual(await store.getCurrentManifestVersion(), 0, "a provenance refusal must not publish a manifest");
1525
- check((await store.getTransaction(output.id))?.status === "active" &&
1526
- (await store.getSegment("compact-output-segment"))?.kind === "base" &&
1527
- (await store.getBlock(outputBlockId)) !== undefined &&
1528
- (await store.getCompactionJob(job.id))?.state === "ready", "a provenance refusal must leave the transaction, artifacts, and job unchanged");
1529
- checkEqual(await manifestBlockIds(store, 0), ["compact-source-block"], "a provenance refusal must retain every source block");
1530
- store.close();
1531
- },
1532
- },
157
+ tableId: options.tableId ?? "table-t",
158
+ keyTokens: options.keyTokens,
159
+ requireAbsent: true
160
+ }
161
+ ]
162
+ }
163
+ });
164
+ return { version: summary.version, blockId };
165
+ }
166
+ function blockStoreConformanceCases() {
167
+ return [
168
+ {
169
+ name: "pending tables stay invisible across multi-batch reopen and publish only at commit",
170
+ async run(target) {
171
+ let store = await target.create();
172
+ const pending = table("pending");
173
+ const epoch = (await store.getCatalogProbe()).catalogEpoch;
174
+ const begun = await store.beginTransaction({
175
+ record: {
176
+ id: "pending-owner",
177
+ ownerId: "owner-pending",
178
+ expiresAt: LATER,
179
+ pendingBlockIds: [],
180
+ pendingSegmentIds: [],
181
+ status: "active",
182
+ revision: 0,
183
+ startedAt: T0,
184
+ updatedAt: T0,
185
+ committedVersion: null
186
+ },
187
+ pendingTable: { record: pending, nextRowId: 1n, expectedCatalogEpoch: epoch }
188
+ });
189
+ check(begun.record.pendingTable?.id === pending.id, "pending reservation was not recorded");
190
+ check(await store.getTable(pending.id) === void 0, "pending table became public early");
191
+ checkEqual(await store.listTables(), [], "pending table appeared in the public catalog");
192
+ const firstBlock = "pending/block/1";
193
+ await store.stageTransactionArtifacts({
194
+ transactionId: begun.record.id,
195
+ expectedRevision: begun.record.revision,
196
+ blocks: [{ id: firstBlock, bytes: Uint8Array.of(1) }],
197
+ segments: [segment("pending-segment-1", pending.id, begun.record.id, firstBlock)],
198
+ updatedAt: MIDDLE
199
+ });
200
+ if (target.reopen !== void 0)
201
+ store = await target.reopen(store);
202
+ check(await store.getTable(pending.id) === void 0, "reopen exposed a pending table");
203
+ const resumed = await store.getTransaction(begun.record.id);
204
+ check(resumed?.revision === 1, "reopen lost the first pending-table stage");
205
+ const secondBlock = "pending/block/2";
206
+ await store.stageTransactionArtifacts({
207
+ transactionId: begun.record.id,
208
+ expectedRevision: resumed.revision,
209
+ blocks: [{ id: secondBlock, bytes: Uint8Array.of(2) }],
210
+ segments: [
211
+ {
212
+ ...segment("pending-segment-2", pending.id, begun.record.id, secondBlock),
213
+ rowIdStart: 3n,
214
+ rowIdEndExclusive: 5n,
215
+ commitOrdinal: 1
216
+ }
217
+ ],
218
+ updatedAt: LATER
219
+ });
220
+ const manifest = await store.commitTransaction({
221
+ transactionId: begun.record.id,
222
+ expectedTransactionRevision: 2,
223
+ expectedManifestVersion: null,
224
+ levelZeroSegmentLimits: [{ tableId: pending.id, limit: MAX_LEVEL_ZERO_SEGMENTS }],
225
+ committedAt: LATER
226
+ });
227
+ check(manifest.version === 0, "pending-table commit did not publish the first manifest");
228
+ check((await store.getTable(pending.id))?.name === pending.name, "commit lost pending table");
229
+ check((await store.getTransaction(begun.record.id))?.pendingTable === void 0, "commit retained the pending catalog reservation");
230
+ store.close();
231
+ }
232
+ },
233
+ {
234
+ name: "staged blocks are immutable, copied both directions, and duplicates are refused",
235
+ async run(target) {
236
+ const store = await target.create();
237
+ await store.createTransaction(transaction("txn-block-copy", null));
238
+ const bytes = Uint8Array.of(1, 2, 3);
239
+ await store.stageTransactionArtifacts({
240
+ transactionId: "txn-block-copy",
241
+ expectedRevision: 0,
242
+ blocks: [{ id: "block-a", bytes }],
243
+ segments: [],
244
+ updatedAt: T0
245
+ });
246
+ bytes[0] = 99;
247
+ const first = await store.getBlock("block-a");
248
+ check(first?.[0] === 1, "stored bytes changed with the caller's buffer");
249
+ first[1] = 99;
250
+ const second = await store.getBlock("block-a");
251
+ check(second?.[1] === 2, "returned bytes alias the store's copy");
252
+ try {
253
+ await store.stageTransactionArtifacts({
254
+ transactionId: "txn-block-copy",
255
+ expectedRevision: 1,
256
+ blocks: [{ id: "block-a", bytes: Uint8Array.of(9) }],
257
+ segments: [],
258
+ updatedAt: LATER
259
+ });
260
+ throw new Error("a duplicate block id was accepted");
261
+ } catch (error) {
262
+ check(error instanceof Error, "duplicate rejection must be an Error");
263
+ }
264
+ checkEqual(await store.getBlocks(["missing", "block-a"]).then((all) => all[0]), void 0, "getBlocks must be positional with undefined for missing ids");
265
+ store.close();
266
+ }
267
+ },
268
+ {
269
+ name: "an artifact batch with any duplicate writes nothing at all",
270
+ async run(target) {
271
+ const store = await target.create();
272
+ await store.createTransaction(transaction("txn-block-batch", null));
273
+ await store.stageTransactionArtifacts({
274
+ transactionId: "txn-block-batch",
275
+ expectedRevision: 0,
276
+ blocks: [{ id: "block-a", bytes: Uint8Array.of(1) }],
277
+ segments: [],
278
+ updatedAt: T0
279
+ });
280
+ try {
281
+ await store.stageTransactionArtifacts({
282
+ transactionId: "txn-block-batch",
283
+ expectedRevision: 1,
284
+ blocks: [
285
+ { id: "block-b", bytes: Uint8Array.of(2) },
286
+ { id: "block-a", bytes: Uint8Array.of(3) },
287
+ { id: "block-c", bytes: Uint8Array.of(4) }
288
+ ],
289
+ segments: [],
290
+ updatedAt: LATER
291
+ });
292
+ throw new Error("a batch containing an existing id was accepted");
293
+ } catch {
294
+ }
295
+ checkEqual(await store.getBlock("block-b"), void 0, "a failed batch leaked block-b");
296
+ checkEqual(await store.getBlock("block-c"), void 0, "a failed batch leaked block-c");
297
+ checkEqual(await store.getBlock("block-a"), Uint8Array.of(1), "the first block changed");
298
+ store.close();
299
+ }
300
+ },
301
+ {
302
+ name: "version-scoped block reads are exact, bounded, and reject missing history",
303
+ async run(target) {
304
+ const store = await target.create();
305
+ await store.addTable(table("t"));
306
+ const first = await commitOne(store, "manifest-read-first");
307
+ const second = await commitOne(store, "manifest-read-second");
308
+ checkEqual(await store.hasManifestBlocks(first.version, [first.blockId, "not-a-member"]), [true, false], "manifest membership must be positional and exact");
309
+ checkEqual(await store.readManifestBlock(first.version, first.blockId), Uint8Array.of(1, 2, 3, 4), "a readable manifest member must return its payload");
310
+ checkEqual(await store.readManifestBlock(first.version, second.blockId), void 0, "a non-member block must not be readable through an older manifest");
311
+ await checkThrows(() => store.hasManifestBlocks(second.version, Array.from({ length: MAX_MANIFEST_BLOCK_PRESENCE_IDS + 1 }, (_, index) => `presence-${String(index)}`)), RangeError, "manifest membership cap+1");
312
+ await checkThrows(() => store.getBlocks(Array.from({ length: MAX_STORAGE_BULK_READ_ITEMS + 1 }, (_, index) => `block-${String(index)}`)), RangeError, "block read cap+1");
313
+ const oversizedIds = Array.from({ length: MAX_STORAGE_BULK_READ_ITEMS + 1 }, (_, index) => `id-${String(index)}`);
314
+ await checkThrows(() => store.getTransactions(oversizedIds), RangeError, "transaction read cap+1");
315
+ await checkThrows(() => store.getExistingUniqueKeys("table-t", oversizedIds), RangeError, "unique-key lookup cap+1");
316
+ let job = await store.createGarbageCollectionJob({
317
+ id: "gc-prune-manifest-read",
318
+ candidateManifestVersions: [first.version],
319
+ candidateSegmentIds: [],
320
+ candidateBlockIds: [],
321
+ leaseCutoff: LATER,
322
+ createdAt: T0
323
+ });
324
+ while (job.state !== "completed") {
325
+ job = (await store.runGarbageCollectionStep({
326
+ jobId: job.id,
327
+ expectedRevision: job.revision,
328
+ maxItems: 16,
329
+ updatedAt: LATER
330
+ })).job;
331
+ }
332
+ checkEqual(await store.readManifestBlock(first.version, first.blockId), void 0, "a pruned manifest must not expose its former member");
333
+ store.close();
334
+ }
335
+ },
336
+ {
337
+ name: "table records round-trip, list sorted by name, and conflict by exact class",
338
+ async run(target) {
339
+ const store = await target.create();
340
+ await store.addTable(table("bravo"));
341
+ await store.addTable(table("alpha"));
342
+ const names = (await store.listTables()).map((record) => record.name);
343
+ checkEqual(names, ["alpha", "bravo"], "listTables must sort by name");
344
+ const byName = await store.getTableByName("alpha");
345
+ check(byName?.id === "table-alpha", "getTableByName must resolve the record");
346
+ await checkThrows(() => store.updateTable("table-alpha", 7, {}), TableRecordConflictError, "updateTable with a stale revision");
347
+ await checkThrows(() => store.removeTable("table-alpha", 7), TableRecordConflictError, "removeTable with a stale revision");
348
+ const updated = await store.updateTable("table-alpha", 0, {});
349
+ check(updated.revision === 1, "updateTable must advance the revision");
350
+ store.close();
351
+ }
352
+ },
353
+ {
354
+ name: "secondary-index metadata must describe one direction per indexed column",
355
+ async run(target) {
356
+ const store = await target.create();
357
+ const record = table("bad-secondary");
358
+ try {
359
+ await store.addTable({
360
+ ...record,
361
+ secondaryIndexes: {
362
+ bad: {
363
+ name: "bad_direction_arity_index",
364
+ columnId: "col-v",
365
+ columnIds: ["col-v"],
366
+ directions: ["asc", "desc"],
367
+ termEncoding: "tuple-v1",
368
+ storage: "postings-v1",
369
+ storageColumnId: "secondary-index:bad",
370
+ locator: "row-id",
371
+ state: "ready",
372
+ buildFromVersion: 0
373
+ }
374
+ }
375
+ });
376
+ throw new Error("secondary-index metadata with mismatched direction arity was accepted");
377
+ } catch (error) {
378
+ check(error instanceof TypeError, "invalid secondary-index metadata must be rejected");
379
+ }
380
+ checkEqual(await store.listTables(), [], "a rejected catalog record must leave no table");
381
+ store.close();
382
+ }
383
+ },
384
+ {
385
+ name: "removing a table removes everything keyed to it",
386
+ async run(target) {
387
+ const store = await target.create();
388
+ await store.addTable(table("t", true));
389
+ const { blockId } = await commitOne(store, "one", {
390
+ keyTokens: ["number:1"],
391
+ tableId: "table-t"
392
+ });
393
+ await store.reserveRowIds("table-t", 10);
394
+ const dropped = await store.dropTable({
395
+ tableId: "table-t",
396
+ expectedTableRevision: 0,
397
+ expectedManifestVersion: 0,
398
+ expectedCatalogEpoch: (await store.getCatalogProbe()).catalogEpoch,
399
+ committedAt: LATER
400
+ });
401
+ check(dropped.version === 1, "dropTable must publish one successor manifest");
402
+ checkEqual(await currentManifestBlockIds(store), [], "dropTable must retire the table's manifest blocks");
403
+ checkEqual(await tableSegments(store, "table-t"), [], "segments must go with the table");
404
+ checkEqual(await store.getExistingUniqueKeys("table-t", ["number:1"]), [], "unique keys must go with the table");
405
+ await store.addTable(table("t", true));
406
+ const range = await store.reserveRowIds("table-t", 1);
407
+ check(range.start === 1n, "row-id counters must reset with the table");
408
+ check(await store.getBlock(blockId) !== void 0, "blocks are not the catalog's to delete");
409
+ store.close();
410
+ }
411
+ },
412
+ {
413
+ name: "fused table drop rejects without change and resolves without partial state",
414
+ async run(target) {
415
+ const store = await target.create();
416
+ await store.addTable(table("t"));
417
+ const source = await commitOne(store, "source");
418
+ const catalogEpoch = (await store.getCatalogProbe()).catalogEpoch;
419
+ const drop = () => store.dropTable({
420
+ tableId: "table-t",
421
+ expectedTableRevision: 0,
422
+ expectedManifestVersion: source.version,
423
+ expectedCatalogEpoch: catalogEpoch,
424
+ committedAt: LATER
425
+ });
426
+ await checkThrows(() => store.dropTable({
427
+ tableId: "table-t",
428
+ expectedTableRevision: 9,
429
+ expectedManifestVersion: source.version,
430
+ expectedCatalogEpoch: catalogEpoch,
431
+ committedAt: LATER
432
+ }), TableRecordConflictError, "dropping with a stale table revision");
433
+ await checkThrows(() => store.dropTable({
434
+ tableId: "table-t",
435
+ expectedTableRevision: 0,
436
+ expectedManifestVersion: source.version - 1,
437
+ expectedCatalogEpoch: catalogEpoch,
438
+ committedAt: LATER
439
+ }), WriteConflictError, "dropping with a stale manifest revision");
440
+ check(await store.getTable("table-t") !== void 0 && await store.getCurrentManifestVersion() === source.version, "a fused drop CAS refusal changed the catalog or manifest");
441
+ const active = transaction("txn-active-table", source.version);
442
+ await store.createTransaction(active);
443
+ const activeBlockId = "table/t/segment/active/part/000000";
444
+ const activeSegment = segment("segment-active-table", "table-t", active.id, activeBlockId);
445
+ const staged = await store.stageTransactionArtifacts({
446
+ transactionId: active.id,
447
+ expectedRevision: 0,
448
+ blocks: [{ id: activeBlockId, bytes: Uint8Array.of(9, 8, 7, 6) }],
449
+ segments: [activeSegment],
450
+ updatedAt: LATER
451
+ });
452
+ const activeRefusal = await checkThrows(drop, TableInUseError, "dropping a table with an active transaction");
453
+ checkEqual({
454
+ tableId: activeRefusal.tableId,
455
+ ownerKind: activeRefusal.ownerKind,
456
+ ownerId: activeRefusal.ownerId
457
+ }, { tableId: "table-t", ownerKind: "transaction", ownerId: active.id }, "an active-transaction refusal must identify its exact owner");
458
+ check(await store.getTable("table-t") !== void 0 && await store.getSegment(activeSegment.id) !== void 0 && await store.getBlock(activeBlockId) !== void 0 && await store.getCurrentManifestVersion() === source.version, "a refused table drop must leave catalog, manifest, and active artifacts unchanged");
459
+ await store.updateTransaction(active.id, staged.revision, {
460
+ status: "aborted",
461
+ updatedAt: LATER
462
+ });
463
+ await store.createCompactionJob({
464
+ id: "job-table-in-use",
465
+ tableId: "table-t",
466
+ sourceManifestVersion: source.version,
467
+ sourceSegmentIds: ["segment-source"],
468
+ sourceBlockIds: [source.blockId],
469
+ outputBlockIds: [],
470
+ cursor: { sourceSegmentIndex: 0, sourceBlockIndex: 0 },
471
+ processedRows: 0,
472
+ sourceStoredBytes: 0,
473
+ outputStoredBytes: 0,
474
+ logicalBytes: 0,
475
+ rewritePlan: { kind: "copy-v1" },
476
+ outputCursor: null,
477
+ memoryBudgetBytes: 0,
478
+ minimumMemoryBytes: 0,
479
+ level0SourceStoredBytes: 0,
480
+ anchorSourceStoredBytes: 0,
481
+ peakWorkingBytes: 0,
482
+ outputLogicalBytes: 0,
483
+ targetLevel: 1,
484
+ state: "planned",
485
+ transactionId: null,
486
+ outputSegmentId: null,
487
+ publishedVersion: null,
488
+ revision: 0,
489
+ createdAt: T0,
490
+ updatedAt: T0
491
+ });
492
+ const jobRefusal = await checkThrows(drop, TableInUseError, "dropping a table with a nonterminal compaction job");
493
+ checkEqual({
494
+ tableId: jobRefusal.tableId,
495
+ ownerKind: jobRefusal.ownerKind,
496
+ ownerId: jobRefusal.ownerId
497
+ }, {
498
+ tableId: "table-t",
499
+ ownerKind: "compaction job",
500
+ ownerId: "job-table-in-use"
501
+ }, "a compaction refusal must identify its exact owner");
502
+ check(await store.getTable("table-t") !== void 0 && await store.getCurrentManifestVersion() === source.version && (await store.hasManifestBlocks(source.version, [source.blockId]))[0] === true, "a compaction refusal changed the catalog or manifest");
503
+ await store.cancelCompactionJob("job-table-in-use", 0, LATER);
504
+ const published = await drop();
505
+ check(published.version === source.version + 1, "a fused table drop must publish exactly one successor manifest");
506
+ check(await store.getTable("table-t") === void 0, "a terminal owner blocked removal");
507
+ checkEqual(await tableSegments(store, "table-t"), [], "a fused drop left table segments");
508
+ checkEqual(await currentManifestBlockIds(store), [], "a fused drop left table blocks live");
509
+ check(await store.getBlock(source.blockId) !== void 0, "a fused drop physically deleted a block pinned readers may still need");
510
+ store.close();
511
+ }
512
+ },
513
+ {
514
+ name: "counter reservations never overlap, even issued concurrently",
515
+ async run(target) {
516
+ const store = await target.create();
517
+ const counterTable = table("t", true);
518
+ const counterColumn = counterTable.columns[0];
519
+ if (counterColumn === void 0)
520
+ throw new Error("counter fixture column is missing");
521
+ counterTable.columns[0] = {
522
+ ...counterColumn,
523
+ defaultValue: { kind: "autoincrement" }
524
+ };
525
+ await store.addTable(counterTable);
526
+ const ranges = await Promise.all(Array.from({ length: 8 }, () => store.reserveRowIds("table-t", 5)));
527
+ const starts = new Set(ranges.map((range) => range.start));
528
+ check(starts.size === 8, "concurrent reservations returned overlapping ranges");
529
+ for (const range of ranges) {
530
+ check(range.endExclusive - range.start === 5n, "a reservation returned the wrong span");
531
+ }
532
+ const bumped = await store.reserveAutoIncrement("table-t", "col-id", 3, 100n);
533
+ check(bumped.start >= 100n, "reserveAutoIncrement must honor atLeast");
534
+ const maximumExclusive = BigInt(Number.MAX_SAFE_INTEGER) + 1n;
535
+ const { snapshotVersion: _invalidSnapshot, ...invalidRecord } = transaction("txn-invalid-counter", null);
536
+ void _invalidSnapshot;
537
+ await checkThrows(() => store.beginTransaction({
538
+ record: invalidRecord,
539
+ reserveAutoIncrement: {
540
+ tableId: "table-t",
541
+ columnId: "col-id",
542
+ count: 0,
543
+ atLeast: maximumExclusive + 1n
544
+ }
545
+ }), RangeError, "an invalid fused auto-increment reservation");
546
+ check(await store.getTransaction(invalidRecord.id) === void 0, "a refused fused reservation must not create its transaction");
547
+ const { snapshotVersion: _validSnapshot, ...validRecord } = transaction("txn-valid-counter", null);
548
+ void _validSnapshot;
549
+ const valid = await store.beginTransaction({
550
+ record: validRecord,
551
+ reserveAutoIncrement: {
552
+ tableId: "table-t",
553
+ columnId: "col-id",
554
+ count: 1,
555
+ atLeast: 150n
556
+ }
557
+ });
558
+ check(valid.autoIncrementValues?.start === 150n && valid.autoIncrementValues.endExclusive === 151n, "a valid fused reservation after refusal must use the unchanged counter");
559
+ await store.updateTransaction(valid.record.id, valid.record.revision, {
560
+ status: "aborted",
561
+ updatedAt: LATER
562
+ });
563
+ await checkThrows(() => store.reserveAutoIncrement("table-t", "col-id", 0, maximumExclusive + 1n), RangeError, "an auto-increment bump outside the safe-integer range");
564
+ const afterRefusal = await store.reserveAutoIncrement("table-t", "col-id", 1, 200n);
565
+ check(afterRefusal.start === 200n && afterRefusal.endExclusive === 201n, "a refused auto-increment bump must not mutate the counter");
566
+ const boundary = await store.reserveAutoIncrement("table-t", "col-id", 1, maximumExclusive - 1n);
567
+ check(boundary.endExclusive === maximumExclusive, "the final safe auto-increment value must remain reservable");
568
+ const exhausted = await store.reserveAutoIncrement("table-t", "col-id", 0, maximumExclusive);
569
+ check(exhausted.start === maximumExclusive && exhausted.endExclusive === maximumExclusive, "a zero-count bump to the exclusive boundary must be representable");
570
+ await checkThrows(() => store.reserveAutoIncrement("table-t", "col-id", 1), RangeError, "an auto-increment range crossing the safe-integer boundary");
571
+ store.close();
572
+ }
573
+ },
574
+ {
575
+ name: "transaction ownership renewal races expiry abort atomically",
576
+ async run(target) {
577
+ const store = await target.create();
578
+ const record = transaction("txn-live", null);
579
+ record.expiresAt = MIDDLE;
580
+ await store.createTransaction(record);
581
+ check(!await store.renewTransaction({
582
+ transactionId: record.id,
583
+ ownerId: record.ownerId,
584
+ expiresAtCutoff: MIDDLE,
585
+ expiresAt: LATER
586
+ }), "ownership expired exactly at the cutoff must not be resurrected");
587
+ check((await store.getTransaction(record.id))?.expiresAt === MIDDLE, "a refused expiry-boundary renewal mutated the record");
588
+ check(!await store.renewTransaction({
589
+ transactionId: record.id,
590
+ ownerId: "wrong-owner",
591
+ expiresAtCutoff: T0,
592
+ expiresAt: LATER
593
+ }), "another owner must not renew a transaction");
594
+ check((await store.getTransaction(record.id))?.revision === 0, "ownership renewal must not contend on the data revision");
595
+ const [renewed, aborted] = await Promise.all([
596
+ store.renewTransaction({
597
+ transactionId: record.id,
598
+ ownerId: record.ownerId,
599
+ expiresAtCutoff: T0,
600
+ expiresAt: LATER
601
+ }),
602
+ store.abortTransactionIfExpired({
603
+ transactionId: record.id,
604
+ expectedOwnerId: record.ownerId,
605
+ expiresAtCutoff: MIDDLE,
606
+ updatedAt: LATER
607
+ })
608
+ ]);
609
+ const final = await store.getTransaction(record.id);
610
+ check(final !== void 0, "the liveness race lost its transaction record");
611
+ check(renewed && aborted === void 0 && final.status === "active" || !renewed && aborted?.status === "aborted" && final.status === "aborted", "renewal and expiry abort must have one linearized winner");
612
+ if (final.status === "active") {
613
+ check(final.expiresAt === LATER, "the winning renewal did not extend ownership");
614
+ const expired = await store.abortTransactionIfExpired({
615
+ transactionId: final.id,
616
+ expectedOwnerId: final.ownerId,
617
+ expiresAtCutoff: "9999-12-31T23:59:59.999Z",
618
+ updatedAt: LATER
619
+ });
620
+ check(expired?.status === "aborted", "an expired owner was not atomically aborted");
621
+ }
622
+ store.close();
623
+ }
624
+ },
625
+ {
626
+ name: "a commit is atomic and visible: manifest, record, segments, keys together",
627
+ async run(target) {
628
+ const store = await target.create();
629
+ await store.addTable(table("t", true));
630
+ const { version, blockId } = await commitOne(store, "one", {
631
+ keyTokens: ["number:1"]
632
+ });
633
+ checkEqual(await store.getCurrentManifestVersion(), version, "the version must advance");
634
+ const manifest = await store.getCurrentManifest();
635
+ check(manifest !== void 0 && (await store.hasManifestBlocks(manifest.version, [blockId]))[0] === true, "the manifest must include the committed block");
636
+ const record = await store.getTransaction("txn-one");
637
+ check(record?.status === "committed" && record.committedVersion === version, "the transaction record must flip to committed with the published version");
638
+ checkEqual(await store.getExistingUniqueKeys("table-t", ["number:1", "number:2"]), ["number:1"], "committed unique keys must be queryable");
639
+ const finalized = await store.getSegment("segment-one");
640
+ check(finalized?.level === 0 && finalized.logicalOrder === version, "committed segments must be finalized with level and logical order");
641
+ store.close();
642
+ }
643
+ },
644
+ {
645
+ name: "commit conflicts are typed exactly, and a refused commit changes nothing",
646
+ async run(target) {
647
+ const store = await target.create();
648
+ await store.addTable(table("t", true));
649
+ await commitOne(store, "one");
650
+ const staleVersion = null;
651
+ await store.createTransaction(transaction("txn-stale", staleVersion));
652
+ await checkThrows(() => store.commitTransaction({
653
+ transactionId: "txn-stale",
654
+ expectedTransactionRevision: 0,
655
+ expectedManifestVersion: staleVersion,
656
+ committedAt: LATER
657
+ }), WriteConflictError, "a commit against a moved manifest");
658
+ const movedVersion = await store.getCurrentManifestVersion();
659
+ await checkThrows(() => store.commitTransaction({
660
+ transactionId: "txn-stale",
661
+ expectedTransactionRevision: 41,
662
+ expectedManifestVersion: movedVersion,
663
+ committedAt: LATER
664
+ }), TransactionRecordConflictError, "a commit with a stale transaction revision");
665
+ const still = await store.getTransaction("txn-stale");
666
+ check(still?.status === "active", "a refused commit must leave the transaction active");
667
+ store.close();
668
+ }
669
+ },
670
+ {
671
+ name: "duplicate unique keys refuse with UniqueKeyConflictError and commit nothing",
672
+ async run(target) {
673
+ const store = await target.create();
674
+ await store.addTable(table("t", true));
675
+ const { version } = await commitOne(store, "one", { keyTokens: ["number:1"] });
676
+ await store.createTransaction(transaction("txn-dup", version));
677
+ await store.stageTransactionArtifacts({
678
+ transactionId: "txn-dup",
679
+ expectedRevision: 0,
680
+ blocks: [{ id: "block-dup", bytes: Uint8Array.of(9) }],
681
+ segments: [],
682
+ updatedAt: T0
683
+ });
684
+ await checkThrows(() => store.commitTransaction({
685
+ transactionId: "txn-dup",
686
+ expectedTransactionRevision: 1,
687
+ expectedManifestVersion: version,
688
+ committedAt: LATER,
689
+ uniqueKeyChanges: [
690
+ { tableId: "table-t", keyTokens: ["number:1"], requireAbsent: true }
691
+ ]
692
+ }), UniqueKeyConflictError, "a commit inserting an existing unique key");
693
+ checkEqual(await store.getCurrentManifestVersion(), version, "a key conflict must not advance the version");
694
+ store.close();
695
+ }
696
+ },
697
+ {
698
+ name: "leases pin versions, renew by CAS, and expire honestly",
699
+ async run(target) {
700
+ const store = await target.create();
701
+ await store.addTable(table("t"));
702
+ const { version } = await commitOne(store, "one");
703
+ await store.createLease({
704
+ id: "lease-a",
705
+ kind: "reader",
706
+ manifestVersion: version,
707
+ ownerId: "owner-a",
708
+ createdAt: T0,
709
+ expiresAt: LATER,
710
+ revision: 0
711
+ });
712
+ await checkThrows(() => store.renewLease({
713
+ id: "lease-a",
714
+ expectedRevision: 9,
715
+ expiresAtCutoff: T0,
716
+ expiresAt: LATER
717
+ }), LeaseConflictError, "renewing with a stale revision");
718
+ const renewed = await store.renewLease({
719
+ id: "lease-a",
720
+ expectedRevision: 0,
721
+ expiresAtCutoff: T0,
722
+ expiresAt: LATER
723
+ });
724
+ check(renewed.revision === 1, "a renewal must advance the revision");
725
+ await checkThrows(() => store.removeLease({ id: "lease-a", ownerId: "wrong-owner" }), LeaseOwnerConflictError, "releasing another owner's lease");
726
+ check((await store.getLease("lease-a"))?.revision === 1, "a refused lease release mutated the record");
727
+ checkEqual(await store.removeLeaseIfExpired("lease-a", 1, PAST), false, "an unexpired lease must not be removed");
728
+ checkEqual(await store.removeLeaseIfExpired("lease-a", 1, "3000-01-01T00:00:00.000Z"), true, "an expired lease must be removed");
729
+ await store.createLease({
730
+ id: "lease-race",
731
+ kind: "reader",
732
+ manifestVersion: version,
733
+ ownerId: "owner-a",
734
+ createdAt: T0,
735
+ expiresAt: LATER,
736
+ revision: 0
737
+ });
738
+ const [, release] = await Promise.allSettled([
739
+ store.renewLease({
740
+ id: "lease-race",
741
+ expectedRevision: 0,
742
+ expiresAtCutoff: T0,
743
+ expiresAt: LATER
744
+ }),
745
+ store.removeLease({ id: "lease-race", ownerId: "owner-a" })
746
+ ]);
747
+ check(release.status === "fulfilled", "a same-owner renewal made release unsafe");
748
+ check(await store.getLease("lease-race") === void 0, "same-owner release did not linearize after/before renewal");
749
+ store.close();
750
+ }
751
+ },
752
+ {
753
+ name: "garbage collection reclaims only unpinned, superseded state \u2014 atomically",
754
+ async run(target) {
755
+ const store = await target.create();
756
+ await store.addTable(table("t"));
757
+ const first = await commitOne(store, "one");
758
+ await store.dropTable({
759
+ tableId: "table-t",
760
+ expectedTableRevision: 0,
761
+ expectedManifestVersion: first.version,
762
+ expectedCatalogEpoch: (await store.getCatalogProbe()).catalogEpoch,
763
+ committedAt: LATER
764
+ });
765
+ const retiredBeforeCollection = await store.listRetiredManifestBlockPage({
766
+ removedThroughVersion: first.version + 1,
767
+ afterBlockId: null,
768
+ limit: 1
769
+ });
770
+ checkEqual(retiredBeforeCollection.records.map((record) => record.blockId), [first.blockId], "retired provenance must remain independently pageable before collection");
771
+ const job = await store.createGarbageCollectionJob({
772
+ id: "gc-1",
773
+ candidateManifestVersions: [first.version],
774
+ candidateSegmentIds: [],
775
+ candidateBlockIds: [first.blockId],
776
+ leaseCutoff: LATER,
777
+ createdAt: LATER
778
+ });
779
+ await checkThrows(() => store.removeGarbageCollectionJob(job.id), Error, "removing a non-completed garbage-collection job");
780
+ check((await store.getGarbageCollectionJob(job.id))?.state === "planned", "a refused garbage-collection job removal mutated the record");
781
+ const step = await store.runGarbageCollectionStep({
782
+ jobId: job.id,
783
+ expectedRevision: job.revision,
784
+ maxItems: 100,
785
+ updatedAt: LATER
786
+ });
787
+ checkEqual(step.prunedManifestVersions, [first.version], "the superseded version must prune");
788
+ checkEqual(step.reclaimedBlockIds, [first.blockId], "the superseded block must reclaim");
789
+ checkEqual(await store.getBlock(first.blockId), void 0, "reclaimed bytes must be gone");
790
+ checkEqual((await store.listRetiredManifestBlockPage({
791
+ removedThroughVersion: first.version + 1,
792
+ afterBlockId: null,
793
+ limit: 1
794
+ })).records, [], "payload reclamation must atomically remove its retired provenance");
795
+ checkEqual(await store.getCurrentManifestVersion(), 1, "the successor manifest was lost");
796
+ const pruned = await store.getManifest(first.version);
797
+ check(pruned?.prunedAt !== void 0, "pruned manifests are tombstoned, not deleted");
798
+ await store.removeGarbageCollectionJob(job.id);
799
+ check(await store.getGarbageCollectionJob(job.id) === void 0, "a completed garbage-collection job was retained");
800
+ store.close();
801
+ }
802
+ },
803
+ {
804
+ name: "temp spill pages are isolated scratch; owner records conflict by exact class",
805
+ async run(target) {
806
+ const store = await target.create();
807
+ await store.createTempOwner({
808
+ ownerId: "o1",
809
+ createdAt: T0,
810
+ expiresAt: LATER,
811
+ revision: 0
812
+ });
813
+ await store.putTempRunPage({
814
+ ownerId: "o1",
815
+ runId: "r1",
816
+ pageIndex: 0,
817
+ bytes: Uint8Array.of(1)
818
+ });
819
+ const batched = store.putTempRunPages?.bind(store);
820
+ if (batched !== void 0) {
821
+ await batched([
822
+ { ownerId: "o1", runId: "r1", pageIndex: 1, bytes: Uint8Array.of(2) },
823
+ { ownerId: "o1", runId: "r2", pageIndex: 0, bytes: Uint8Array.of(3) }
824
+ ]);
825
+ checkEqual(await store.getTempRunPage("o1", "r1", 1), Uint8Array.of(2), "batched pages must read back identically");
826
+ }
827
+ await store.removeTempRun("o1", "r1");
828
+ checkEqual(await store.getTempRunPage("o1", "r1", 0), void 0, "a removed run must be gone");
829
+ await checkThrows(() => store.renewTempOwner({
830
+ ownerId: "o1",
831
+ expectedRevision: 9,
832
+ expiresAtCutoff: T0,
833
+ expiresAt: LATER
834
+ }), TempOwnerConflictError, "renewing a temp owner with a stale revision");
835
+ checkEqual(await store.removeTempOwnerIfExpired("o1", PAST), false, "an unexpired owner must survive the sweep");
836
+ const listed = await store.listTempOwnerIdsPage(null, 10);
837
+ check(listed.records.includes("o1"), "owners must be listable");
838
+ store.close();
839
+ }
840
+ },
841
+ {
842
+ name: "compaction job records update by CAS with the exact conflict class",
843
+ async run(target) {
844
+ const store = await target.create();
845
+ await store.addTable(table("t"));
846
+ const { version, blockId } = await commitOne(store, "one");
847
+ await store.createCompactionJob({
848
+ id: "job-1",
849
+ tableId: "table-t",
850
+ sourceManifestVersion: version,
851
+ sourceSegmentIds: ["segment-one"],
852
+ sourceBlockIds: [blockId],
853
+ outputBlockIds: [],
854
+ cursor: { sourceSegmentIndex: 0, sourceBlockIndex: 0 },
855
+ processedRows: 0,
856
+ sourceStoredBytes: 0,
857
+ outputStoredBytes: 0,
858
+ logicalBytes: 0,
859
+ rewritePlan: { kind: "copy-v1" },
860
+ outputCursor: null,
861
+ memoryBudgetBytes: 0,
862
+ minimumMemoryBytes: 0,
863
+ level0SourceStoredBytes: 0,
864
+ anchorSourceStoredBytes: 0,
865
+ peakWorkingBytes: 0,
866
+ outputLogicalBytes: 0,
867
+ targetLevel: 1,
868
+ state: "planned",
869
+ transactionId: null,
870
+ outputSegmentId: null,
871
+ publishedVersion: null,
872
+ revision: 0,
873
+ createdAt: T0,
874
+ updatedAt: T0
875
+ });
876
+ await checkThrows(() => store.removeCompactionJob("job-1"), Error, "removing a nonterminal compaction job");
877
+ check((await store.getCompactionJob("job-1"))?.state === "planned", "a refused compaction-job removal mutated the record");
878
+ await checkThrows(() => store.updateCompactionJob("job-1", 9, {
879
+ state: "running",
880
+ transactionId: "txn-one",
881
+ updatedAt: LATER
882
+ }), CompactionJobConflictError, "updating a compaction job with a stale revision");
883
+ const updated = await store.updateCompactionJob("job-1", 0, { updatedAt: LATER });
884
+ check(updated.revision === 1, "a job update must advance the revision");
885
+ await store.cancelCompactionJob("job-1", updated.revision, LATER);
886
+ check(await store.removeCompactionJob("job-1"), "terminal job was not removed");
887
+ check(await store.getCompactionJob("job-1") === void 0, "terminal job was retained");
888
+ await store.createTransaction(transaction("txn-orphan-output", version));
889
+ await store.stageTransactionArtifacts({
890
+ transactionId: "txn-orphan-output",
891
+ expectedRevision: 0,
892
+ blocks: [{ id: "orphan-compaction-output", bytes: Uint8Array.of(9) }],
893
+ segments: [],
894
+ updatedAt: T0
895
+ });
896
+ await store.updateTransaction("txn-orphan-output", 1, {
897
+ status: "aborted",
898
+ updatedAt: LATER
899
+ });
900
+ await store.createCompactionJob({
901
+ id: "job-orphan-output",
902
+ tableId: "table-t",
903
+ sourceManifestVersion: version,
904
+ sourceSegmentIds: ["segment-one"],
905
+ sourceBlockIds: [blockId],
906
+ outputBlockIds: ["orphan-compaction-output"],
907
+ cursor: { sourceSegmentIndex: 1, sourceBlockIndex: 0 },
908
+ processedRows: 0,
909
+ sourceStoredBytes: 0,
910
+ outputStoredBytes: 1,
911
+ logicalBytes: 1,
912
+ rewritePlan: { kind: "copy-v1" },
913
+ outputCursor: null,
914
+ memoryBudgetBytes: 0,
915
+ minimumMemoryBytes: 0,
916
+ level0SourceStoredBytes: 0,
917
+ anchorSourceStoredBytes: 0,
918
+ peakWorkingBytes: 0,
919
+ outputLogicalBytes: 1,
920
+ targetLevel: 1,
921
+ state: "cancelled",
922
+ transactionId: null,
923
+ outputSegmentId: null,
924
+ publishedVersion: null,
925
+ revision: 0,
926
+ createdAt: T0,
927
+ updatedAt: LATER
928
+ });
929
+ check(await store.removeCompactionJob("job-orphan-output"), "terminal compaction metadata with independent transaction provenance was retained");
930
+ check(await store.getBlock("orphan-compaction-output") !== void 0, "removing terminal metadata deleted an independently proven output");
931
+ store.close();
932
+ }
933
+ },
934
+ {
935
+ name: "full-text bases serve exact and prefix candidates with honest coverage",
936
+ async run(target) {
937
+ const store = await target.create();
938
+ await store.addTable({
939
+ ...table("t"),
940
+ ftsColumns: {
941
+ "col-v": {
942
+ storage: "fts-chunks-v1",
943
+ tokenizerVersion: 1,
944
+ state: "ready",
945
+ buildFromVersion: 0
946
+ }
947
+ }
948
+ });
949
+ await store.writeFtsBase("table-t", "col-v", {
950
+ coversVersion: 0,
951
+ chunks: [
952
+ [
953
+ { term: "minnow", rowIds: [1n, 3n], tf: [1, 2] },
954
+ { term: "minnows", rowIds: [2n], tf: [1] }
955
+ ],
956
+ [{ term: "shark", rowIds: [4n], tf: [1] }]
957
+ ],
958
+ totalTokens: 5
959
+ });
960
+ const exact = await store.readFtsCandidates("table-t", "col-v", [{ term: "minnow", prefix: false }], 10);
961
+ checkEqual(exact.rowIdsByTerm, [[1n, 3n]], "exact terms must match exactly");
962
+ check(exact.hasBase && exact.coversVersion === 0 && exact.totalTokens === 5, "coverage and token totals must report the stored base");
963
+ const prefix = await store.readFtsCandidates("table-t", "col-v", [{ term: "minnow", prefix: true }], 10);
964
+ checkEqual(prefix.rowIdsByTerm, [[1n, 2n, 3n]], "prefix terms must match the term range, row ids ascending and unique");
965
+ const ordered = await store.readFtsPostings("table-t", "col-v", 10);
966
+ checkEqual(ordered.postings, [
967
+ { term: "minnow", rowIds: [1n, 3n], tf: [1, 2] },
968
+ { term: "minnows", rowIds: [2n], tf: [1] },
969
+ { term: "shark", rowIds: [4n], tf: [1] }
970
+ ], "ordered postings must merge into canonical term and row-ID order");
971
+ const baseOnly = await store.readFtsPostings("table-t", "col-v", -1);
972
+ check(baseOnly.hasBase && baseOnly.postings.length === 3, "an upToVersion of -1 must serve the base view alone");
973
+ for (const version of [-2, Number.NaN, 1.5]) {
974
+ try {
975
+ await store.readFtsCandidates("table-t", "col-v", [{ term: "minnow", prefix: false }], version);
976
+ throw new Error(`candidate read accepted invalid version ${String(version)}`);
977
+ } catch (error) {
978
+ check(error instanceof RangeError, "invalid candidate versions must throw RangeError");
979
+ }
980
+ try {
981
+ await store.readFtsPostings("table-t", "col-v", version);
982
+ throw new Error(`postings read accepted invalid version ${String(version)}`);
983
+ } catch (error) {
984
+ check(error instanceof RangeError, "invalid postings versions must throw RangeError");
985
+ }
986
+ }
987
+ try {
988
+ await store.readFtsCandidates("table-t", "col-v", [{ term: "minnow", prefix: false }], 10, 0);
989
+ throw new Error("candidate read accepted a zero row-ID limit");
990
+ } catch (error) {
991
+ check(error instanceof RangeError, "invalid candidate limits must throw RangeError");
992
+ }
993
+ store.close();
994
+ }
995
+ },
996
+ {
997
+ name: "postings builds publish bounded generations atomically and reclaim replacements",
998
+ async run(target) {
999
+ let store = await target.create();
1000
+ await store.addTable({
1001
+ ...table("postings"),
1002
+ ftsColumns: {
1003
+ "col-v": {
1004
+ storage: "fts-chunks-v1",
1005
+ tokenizerVersion: 1,
1006
+ state: "building",
1007
+ buildFromVersion: -1
1008
+ }
1009
+ }
1010
+ });
1011
+ await store.beginFtsBaseBuild({
1012
+ tableId: "table-postings",
1013
+ columnId: "col-v",
1014
+ buildId: "abandoned",
1015
+ ownerId: "owner-abandoned",
1016
+ createdAt: "2099-01-01T00:00:00.000Z",
1017
+ expiresAt: "2099-01-01T00:10:00.000Z"
1018
+ });
1019
+ await store.writeFtsBaseBuildChunk({
1020
+ tableId: "table-postings",
1021
+ columnId: "col-v",
1022
+ buildId: "abandoned",
1023
+ ownerId: "owner-abandoned",
1024
+ expiresAtCutoff: "2099-01-01T00:00:01.000Z",
1025
+ expiresAt: "2099-01-01T00:10:00.000Z",
1026
+ updatedAt: "2099-01-01T00:00:01.000Z",
1027
+ ordinal: 0,
1028
+ chunk: [{ term: "old", rowIds: [1n], tf: [1] }]
1029
+ });
1030
+ await store.beginFtsBaseBuild({
1031
+ tableId: "table-postings",
1032
+ columnId: "col-v",
1033
+ buildId: "replacement",
1034
+ ownerId: "owner-replacement",
1035
+ createdAt: "2099-01-01T00:10:00.000Z",
1036
+ expiresAt: "2099-01-01T00:20:00.000Z"
1037
+ });
1038
+ const firstChunk = {
1039
+ tableId: "table-postings",
1040
+ columnId: "col-v",
1041
+ buildId: "replacement",
1042
+ ownerId: "owner-replacement",
1043
+ expiresAtCutoff: "2099-01-01T00:10:01.000Z",
1044
+ expiresAt: "2099-01-01T00:20:00.000Z",
1045
+ updatedAt: "2099-01-01T00:10:01.000Z",
1046
+ ordinal: 0,
1047
+ chunk: [{ term: "alpha", rowIds: [2n], tf: [1] }]
1048
+ };
1049
+ await store.writeFtsBaseBuildChunk(firstChunk);
1050
+ await store.writeFtsBaseBuildChunk(firstChunk);
1051
+ await checkThrows(() => store.writeFtsBaseBuildChunk({
1052
+ ...firstChunk,
1053
+ chunk: [{ term: "alpha", rowIds: [999n], tf: [1] }]
1054
+ }), PostingBuildConflictError, "replaying an acknowledged postings ordinal with changed bytes");
1055
+ if (target.reopen !== void 0) {
1056
+ store = await target.reopen(store);
1057
+ await store.writeFtsBaseBuildChunk(firstChunk);
1058
+ }
1059
+ await store.writeFtsBaseBuildChunk({
1060
+ tableId: "table-postings",
1061
+ columnId: "col-v",
1062
+ buildId: "replacement",
1063
+ ownerId: "owner-replacement",
1064
+ expiresAtCutoff: "2099-01-01T00:10:02.000Z",
1065
+ expiresAt: "2099-01-01T00:20:00.000Z",
1066
+ updatedAt: "2099-01-01T00:10:02.000Z",
1067
+ ordinal: 1,
1068
+ chunk: [{ term: "omega", rowIds: [3n], tf: [1] }]
1069
+ });
1070
+ await store.finishFtsBaseBuild({
1071
+ tableId: "table-postings",
1072
+ columnId: "col-v",
1073
+ buildId: "replacement",
1074
+ ownerId: "owner-replacement",
1075
+ expiresAtCutoff: "2099-01-01T00:10:03.000Z",
1076
+ coversVersion: 4,
1077
+ chunkCount: 2,
1078
+ totalTokens: 2,
1079
+ completedAt: "2099-01-01T00:10:03.000Z"
1080
+ });
1081
+ let candidates = await store.readFtsCandidates("table-postings", "col-v", [
1082
+ { term: "old", prefix: false },
1083
+ { lower: "alpha", lowerInclusive: true, upper: "omega", upperInclusive: true }
1084
+ ], 4);
1085
+ checkEqual(candidates.rowIdsByTerm, [[], [2n, 3n]], "only a finished replacement generation may become visible");
1086
+ check(candidates.hasBase, "a finished generation must report a published base");
1087
+ if (target.reopen !== void 0) {
1088
+ store = await target.reopen(store);
1089
+ candidates = await store.readFtsCandidates("table-postings", "col-v", [{ term: "omega", prefix: false }], 4);
1090
+ checkEqual(candidates.rowIdsByTerm, [[3n]], "a finished generation must survive reopen");
1091
+ check(candidates.hasBase, "a reopened generation must still report its base");
1092
+ }
1093
+ await checkThrows(() => store.removeFtsColumn("table-postings", "col-v"), Error, "removing postings still owned by a ready catalog accelerator");
1094
+ const indexed = await store.getTable("table-postings");
1095
+ check(indexed !== void 0, "the postings table disappeared");
1096
+ await store.updateTable(indexed.id, indexed.revision, {
1097
+ ftsColumns: {
1098
+ ...indexed.ftsColumns,
1099
+ "col-v": {
1100
+ ...indexed.ftsColumns?.["col-v"] ?? {
1101
+ storage: "fts-chunks-v1",
1102
+ tokenizerVersion: 1,
1103
+ buildFromVersion: -1
1104
+ },
1105
+ state: "invalid"
1106
+ }
1107
+ }
1108
+ });
1109
+ await store.removeFtsColumn("table-postings", "col-v");
1110
+ candidates = await store.readFtsCandidates("table-postings", "col-v", [{ term: "alpha", prefix: false }], 4);
1111
+ checkEqual(candidates.rowIdsByTerm, [[]], "removing an index must remove its candidates");
1112
+ check(!candidates.hasBase, "a removed base must be distinguishable from an empty base");
1113
+ store.close();
1114
+ }
1115
+ },
1116
+ {
1117
+ name: "UNIQUE-index membership seeds and drops atomically with its catalog record",
1118
+ async run(target) {
1119
+ const store = await target.create();
1120
+ const record = table("unique-secondary");
1121
+ const indexId = "unique-v";
1122
+ const namespaceId = secondaryUniqueKeyNamespace(record.id, indexId);
1123
+ const building = {
1124
+ name: "unique_secondary_v",
1125
+ columnId: "col-v",
1126
+ columnIds: ["col-v"],
1127
+ directions: ["asc"],
1128
+ unique: true,
1129
+ termEncoding: "tuple-v1",
1130
+ storage: "postings-v1",
1131
+ storageColumnId: "secondary-index:unique-v",
1132
+ locator: "row-id",
1133
+ state: "building",
1134
+ buildId: "builder",
1135
+ buildFromVersion: -1
1136
+ };
1137
+ const { buildId: _buildId, ...withoutBuilder } = building;
1138
+ void _buildId;
1139
+ const ready = {
1140
+ ...withoutBuilder,
1141
+ state: "ready",
1142
+ uniqueEnforced: true
1143
+ };
1144
+ await store.addTable({ ...record, secondaryIndexes: { [indexId]: building } });
1145
+ try {
1146
+ await store.updateTable(record.id, 0, {
1147
+ secondaryIndexes: { [indexId]: ready },
1148
+ expectedManifestVersion: { value: null },
1149
+ uniqueKeySeed: { namespaceId: `${namespaceId}-wrong`, keyTokens: ["x"] }
1150
+ });
1151
+ throw new Error("a seed for the wrong namespace was accepted");
1152
+ } catch (error) {
1153
+ check(error instanceof TypeError, "a mismatched UNIQUE seed must be rejected");
1154
+ }
1155
+ check((await store.getTable(record.id))?.revision === 0, "a mismatched seed must not mutate the catalog");
1156
+ await checkThrows(() => store.updateTable(record.id, 0, {
1157
+ secondaryIndexes: {
1158
+ [indexId]: ready
1159
+ },
1160
+ expectedManifestVersion: { value: null },
1161
+ uniqueKeySeed: { namespaceId, keyTokens: ["x", "x"] }
1162
+ }), UniqueKeyConflictError, "a duplicate UNIQUE seed");
1163
+ check((await store.getTable(record.id))?.secondaryIndexes?.[indexId]?.state === "building", "a refused seed must not publish the ready catalog state");
1164
+ await store.updateTable(record.id, 0, {
1165
+ secondaryIndexes: {
1166
+ [indexId]: ready
1167
+ },
1168
+ expectedManifestVersion: { value: null },
1169
+ uniqueKeySeed: { namespaceId, keyTokens: ["x", "y"] }
1170
+ });
1171
+ checkEqual(await store.getExistingUniqueKeys(namespaceId, ["x", "z"]), ["x"], "the ready catalog and membership must publish together");
1172
+ await store.updateTable(record.id, 1, { secondaryIndexes: null });
1173
+ checkEqual(await store.getExistingUniqueKeys(namespaceId, ["x", "y"]), [], "dropping the index must reclaim its membership namespace");
1174
+ store.close();
1175
+ }
1176
+ },
1177
+ {
1178
+ name: "atomic staging and rollback keep journals and artifacts inseparable",
1179
+ async run(target) {
1180
+ let store = await target.create();
1181
+ await store.addTable(table("t"));
1533
1182
  {
1534
- name: "the lease move, when present, re-pins in place and refuses typed",
1535
- async run(target) {
1536
- const store = await target.create();
1537
- const move = store.moveLease?.bind(store);
1538
- if (move === undefined) {
1539
- store.close();
1540
- return;
1541
- }
1542
- await store.addTable(table("t"));
1543
- const first = await commitOne(store, "one");
1544
- const second = await commitOne(store, "two");
1545
- await store.createLease({
1546
- id: "lease-m",
1547
- kind: "reader",
1548
- manifestVersion: first.version,
1549
- ownerId: "owner-m",
1550
- createdAt: T0,
1551
- expiresAt: LATER,
1552
- revision: 0,
1553
- });
1554
- await checkThrows(() => move({
1555
- id: "lease-m",
1556
- expectedRevision: 9,
1557
- manifestVersion: second.version,
1558
- expiresAtCutoff: T0,
1559
- expiresAt: LATER,
1560
- }), LeaseConflictError, "moving with a stale revision");
1561
- await checkThrows(() => move({
1562
- id: "lease-m",
1563
- expectedRevision: 0,
1564
- manifestVersion: 999,
1565
- expiresAtCutoff: T0,
1566
- expiresAt: LATER,
1567
- }), SnapshotManifestMissingError, "moving to a version with no manifest");
1568
- checkEqual(await store.getLease("lease-m"), {
1569
- id: "lease-m",
1570
- kind: "reader",
1571
- manifestVersion: first.version,
1572
- ownerId: "owner-m",
1573
- createdAt: T0,
1574
- expiresAt: LATER,
1575
- revision: 0,
1576
- }, "a refused move must leave the lease exactly as it was");
1577
- const moved = await move({
1578
- id: "lease-m",
1579
- expectedRevision: 0,
1580
- manifestVersion: second.version,
1581
- expiresAtCutoff: T0,
1582
- expiresAt: LATER,
1583
- });
1584
- checkEqual(moved, {
1585
- id: "lease-m",
1586
- kind: "reader",
1587
- manifestVersion: second.version,
1588
- ownerId: "owner-m",
1589
- createdAt: T0,
1590
- expiresAt: LATER,
1591
- revision: 1,
1592
- }, "a move must re-pin, renew, and advance the revision");
1593
- checkEqual((await store.listLeases()).map((lease) => lease.id), ["lease-m"], "a move must keep the one record, not create a second");
1594
- store.close();
1183
+ const begun = await store.beginTransaction({
1184
+ record: {
1185
+ id: "txn-b",
1186
+ ownerId: "txn-b/owner",
1187
+ expiresAt: LATER,
1188
+ pendingBlockIds: [],
1189
+ pendingSegmentIds: [],
1190
+ status: "active",
1191
+ revision: 0,
1192
+ startedAt: T0,
1193
+ updatedAt: T0,
1194
+ committedVersion: null
1595
1195
  },
1596
- },
1597
- {
1598
- name: "everything committed survives a reopen",
1599
- async run(target) {
1600
- if (target.reopen === undefined)
1601
- return;
1602
- let store = await target.create();
1603
- await store.addTable(table("t", true));
1604
- let { version, blockId } = await commitOne(store, "one", { keyTokens: ["number:1"] });
1605
- const reserved = await store.reserveRowIds("table-t", 10);
1606
- // The single-shot write and the lease move, when present, must be just as durable.
1607
- const write = store.writeTransaction?.bind(store);
1608
- if (write !== undefined) {
1609
- const { snapshotVersion: _pinned, ...record } = transaction("txn-single", null);
1610
- void _pinned;
1611
- const summary = await write({
1612
- transaction: {
1613
- record: {
1614
- ...record,
1615
- schemaEpochGuard: (await store.getCatalogProbe()).schemaEpoch,
1616
- },
1617
- },
1618
- blocks: [{ id: "block-single", bytes: Uint8Array.of(1, 2, 3, 4) }],
1619
- segments: [segment("segment-single", "table-t", "txn-single", "block-single")],
1620
- expectedManifestVersion: version,
1621
- levelZeroSegmentLimits: [{ tableId: "table-t", limit: MAX_LEVEL_ZERO_SEGMENTS }],
1622
- uniqueKeyChanges: [
1623
- { tableId: "table-t", keyTokens: ["number:1"], requireAbsent: false },
1624
- ],
1625
- committedAt: LATER,
1626
- });
1627
- version = summary.version;
1628
- blockId = "block-single";
1629
- }
1630
- const move = store.moveLease?.bind(store);
1631
- if (move !== undefined) {
1632
- await store.createLease({
1633
- id: "lease-durable",
1634
- kind: "reader",
1635
- manifestVersion: null,
1636
- ownerId: "owner",
1637
- createdAt: T0,
1638
- expiresAt: LATER,
1639
- revision: 0,
1640
- });
1641
- await move({
1642
- id: "lease-durable",
1643
- expectedRevision: 0,
1644
- manifestVersion: version,
1645
- expiresAtCutoff: T0,
1646
- expiresAt: LATER,
1647
- });
1648
- }
1649
- store = await target.reopen(store);
1650
- checkEqual(await store.getCurrentManifestVersion(), version, "the committed version must survive a reopen");
1651
- if (move !== undefined) {
1652
- checkEqual((await store.getLease("lease-durable"))?.manifestVersion, version, "a moved lease must survive a reopen at its new version");
1653
- }
1654
- checkEqual(await store.getBlock(blockId), Uint8Array.of(1, 2, 3, 4), "committed bytes must survive a reopen");
1655
- checkEqual(await store.getExistingUniqueKeys("table-t", ["number:1"]), ["number:1"], "unique-key membership must survive a reopen");
1656
- check((await store.getTableByName("t")) !== undefined, "the catalog must survive a reopen");
1657
- const next = await store.reserveRowIds("table-t", 1);
1658
- check(next.start >= reserved.endExclusive, "counters must continue past the reserved high-water mark after a reopen");
1659
- store.close();
1196
+ reserveRowIds: { tableId: "table-t", count: 4 }
1197
+ });
1198
+ check(begun.record.snapshotVersion === await store.getCurrentManifestVersion(), "beginTransaction must pin the current version");
1199
+ check(begun.rowIds !== void 0 && begun.rowIds.endExclusive - begun.rowIds.start === 4n, "beginTransaction must honor the reservation");
1200
+ }
1201
+ const stage = store.stageTransactionArtifacts.bind(store);
1202
+ await store.createTransaction(transaction("txn-s", await store.getCurrentManifestVersion()));
1203
+ const duplicate = segment("segment-duplicate", "table-t", "txn-s", "block-none");
1204
+ await checkThrows(() => stage({
1205
+ transactionId: "txn-s",
1206
+ expectedRevision: 0,
1207
+ blocks: [],
1208
+ segments: [duplicate, duplicate],
1209
+ updatedAt: LATER
1210
+ }), Error, "staging duplicate segment IDs");
1211
+ await checkThrows(() => stage({
1212
+ transactionId: "txn-s",
1213
+ expectedRevision: 0,
1214
+ blocks: [],
1215
+ segments: [segment("segment-foreign", "table-t", "another-txn", "block-none")],
1216
+ updatedAt: LATER
1217
+ }), Error, "staging a segment owned by another transaction");
1218
+ check(await store.getSegment("segment-duplicate") === void 0 && await store.getSegment("segment-foreign") === void 0 && (await store.getTransaction("txn-s"))?.revision === 0, "refused staging must leave the transaction and segment store unchanged");
1219
+ const first = await stage({
1220
+ transactionId: "txn-s",
1221
+ expectedRevision: 0,
1222
+ blocks: [{ id: "block-z", bytes: Uint8Array.of(7) }],
1223
+ segments: [segment("segment-z", "table-t", "txn-s", "block-z")],
1224
+ updatedAt: LATER
1225
+ });
1226
+ await checkThrows(() => stage({
1227
+ transactionId: "txn-s",
1228
+ expectedRevision: first.revision,
1229
+ blocks: [{ id: "block-gap", bytes: Uint8Array.of(9) }],
1230
+ segments: [segment("segment-gap", "table-t", "txn-s", "block-gap", 0)],
1231
+ updatedAt: LATER
1232
+ }), TypeError, "staging a segment whose commit ordinal does not continue the journal");
1233
+ check((await store.getTransaction("txn-s"))?.revision === first.revision && await store.getBlock("block-gap") === void 0 && await store.getSegment("segment-gap") === void 0, "an ordinal refusal must leave the record, block, and segment unchanged");
1234
+ const staged = await stage({
1235
+ transactionId: "txn-s",
1236
+ expectedRevision: first.revision,
1237
+ blocks: [{ id: "block-a", bytes: Uint8Array.of(8) }],
1238
+ segments: [segment("segment-a", "table-t", "txn-s", "block-a", 1)],
1239
+ updatedAt: LATER
1240
+ });
1241
+ checkEqual(staged.pendingBlockIds, ["block-z", "block-a"], "staging journals blocks");
1242
+ checkEqual(staged.pendingSegmentIds, ["segment-z", "segment-a"], "staging preserves the canonical segment journal order");
1243
+ check(await store.getBlock("block-a") !== void 0 && await store.getSegment("segment-a") !== void 0, "staging must write bytes and segments");
1244
+ const rollbackInput = {
1245
+ transactionId: "txn-s",
1246
+ expectedRevision: staged.revision,
1247
+ pendingBlockIds: ["block-z"],
1248
+ pendingSegmentIds: ["segment-z"],
1249
+ removeBlockIds: ["block-a"],
1250
+ removeSegmentIds: ["segment-a"],
1251
+ updatedAt: LATER
1252
+ };
1253
+ await checkThrows(() => store.rollbackTransactionArtifacts({
1254
+ ...rollbackInput,
1255
+ expectedRevision: staged.revision + 1
1256
+ }), TransactionRecordConflictError, "rollback with a stale transaction revision");
1257
+ await checkThrows(() => store.rollbackTransactionArtifacts({
1258
+ ...rollbackInput,
1259
+ removeBlockIds: []
1260
+ }), TypeError, "rollback whose lists do not exactly partition the journal");
1261
+ await checkThrows(() => store.rollbackTransactionArtifacts({
1262
+ ...rollbackInput,
1263
+ pendingBlockIds: ["block-a"],
1264
+ pendingSegmentIds: ["segment-a"],
1265
+ removeBlockIds: ["block-z"],
1266
+ removeSegmentIds: ["segment-z"]
1267
+ }), TypeError, "rollback that retains a non-prefix journal partition");
1268
+ check(await store.getBlock("block-a") !== void 0 && await store.getSegment("segment-a") !== void 0, "a refused rollback must leave every artifact intact");
1269
+ const rolledBack = await store.rollbackTransactionArtifacts(rollbackInput);
1270
+ checkEqual(rolledBack.pendingBlockIds, ["block-z"], "rollback must retain checkpoint block");
1271
+ check(await store.getBlock("block-a") === void 0 && await store.getSegment("segment-a") === void 0 && await store.getBlock("block-z") !== void 0 && await store.getSegment("segment-z") !== void 0, "rollback must delete only its exact removed partition");
1272
+ if (target.reopen !== void 0) {
1273
+ const reopened = await target.reopen(store);
1274
+ checkEqual((await reopened.getTransaction("txn-s"))?.pendingBlockIds, ["block-z"], "rollback journal must survive reopen");
1275
+ check(await reopened.getBlock("block-a") === void 0 && await reopened.getBlock("block-z") !== void 0, "rollback artifact removal must survive reopen");
1276
+ store = reopened;
1277
+ }
1278
+ const before = await store.getCatalogProbe();
1279
+ await store.addTable(table("probe-check"));
1280
+ const after = await store.getCatalogProbe();
1281
+ check(after.catalogEpoch > before.catalogEpoch, "a catalog mutation must advance the probe's epoch");
1282
+ store.close();
1283
+ }
1284
+ },
1285
+ {
1286
+ name: "the single-shot write, when present, commits atomically and refuses typed",
1287
+ async run(target) {
1288
+ const store = await target.create();
1289
+ const write = store.writeTransaction?.bind(store);
1290
+ if (write === void 0) {
1291
+ store.close();
1292
+ return;
1293
+ }
1294
+ await store.addTable(table("t", true));
1295
+ const schemaEpoch = (await store.getCatalogProbe()).schemaEpoch;
1296
+ const fresh = (id) => {
1297
+ const { snapshotVersion: _pinned, ...record } = transaction(id, null);
1298
+ void _pinned;
1299
+ return { ...record, schemaEpochGuard: schemaEpoch };
1300
+ };
1301
+ const first = await write({
1302
+ transaction: { record: fresh("txn-w1") },
1303
+ blocks: [{ id: "block-w1", bytes: Uint8Array.of(1) }],
1304
+ segments: [segment("segment-w1", "table-t", "txn-w1", "block-w1")],
1305
+ expectedManifestVersion: null,
1306
+ levelZeroSegmentLimits: [{ tableId: "table-t", limit: MAX_LEVEL_ZERO_SEGMENTS }],
1307
+ uniqueKeyChanges: [{ tableId: "table-t", keyTokens: ["number:1"], requireAbsent: true }],
1308
+ committedAt: LATER
1309
+ });
1310
+ checkEqual(first.version, 0, "the first single-shot write must publish version 0");
1311
+ const committed = await store.getTransaction("txn-w1");
1312
+ check(committed?.status === "committed" && committed.committedVersion === 0 && committed.snapshotVersion === null, "the fresh record must end committed, pinned at the expected version");
1313
+ checkEqual(committed.pendingBlockIds, ["block-w1"], "the single-shot write must journal its blocks on the record");
1314
+ check(await store.getBlock("block-w1") !== void 0 && (await store.getSegment("segment-w1"))?.logicalOrder === 0, "the single-shot write must land bytes and finalized segments");
1315
+ checkEqual(await store.getExistingUniqueKeys("table-t", ["number:1"]), ["number:1"], "the single-shot write must apply unique-key changes");
1316
+ await checkThrows(() => write({
1317
+ transaction: { record: fresh("txn-stale") },
1318
+ blocks: [{ id: "block-stale", bytes: Uint8Array.of(2) }],
1319
+ segments: [segment("segment-stale", "table-t", "txn-stale", "block-stale")],
1320
+ expectedManifestVersion: null,
1321
+ levelZeroSegmentLimits: [{ tableId: "table-t", limit: MAX_LEVEL_ZERO_SEGMENTS }],
1322
+ committedAt: LATER
1323
+ }), WriteConflictError, "a single-shot write against a moved manifest");
1324
+ check(await store.getTransaction("txn-stale") === void 0 && await store.getBlock("block-stale") === void 0 && await store.getSegment("segment-stale") === void 0, "a refused single-shot write must leave no record, block, or segment");
1325
+ await checkThrows(() => write({
1326
+ transaction: { record: fresh("txn-dup") },
1327
+ blocks: [{ id: "block-dup", bytes: Uint8Array.of(3) }],
1328
+ segments: [segment("segment-dup", "table-t", "txn-dup", "block-dup")],
1329
+ expectedManifestVersion: 0,
1330
+ levelZeroSegmentLimits: [{ tableId: "table-t", limit: MAX_LEVEL_ZERO_SEGMENTS }],
1331
+ uniqueKeyChanges: [
1332
+ { tableId: "table-t", keyTokens: ["number:1"], requireAbsent: true }
1333
+ ],
1334
+ committedAt: LATER
1335
+ }), UniqueKeyConflictError, "a single-shot write inserting an existing unique key");
1336
+ check(await store.getTransaction("txn-dup") === void 0 && await store.getBlock("block-dup") === void 0, "a key conflict must leave no record or block");
1337
+ checkEqual(await store.getCurrentManifestVersion(), 0, "refusals must not advance");
1338
+ await store.createTransaction(transaction("txn-w2", 0));
1339
+ await checkThrows(() => write({
1340
+ transaction: { id: "txn-w2", expectedRevision: 4 },
1341
+ blocks: [{ id: "block-w2", bytes: Uint8Array.of(4) }],
1342
+ segments: [segment("segment-w2", "table-t", "txn-w2", "block-w2")],
1343
+ expectedManifestVersion: 0,
1344
+ levelZeroSegmentLimits: [{ tableId: "table-t", limit: MAX_LEVEL_ZERO_SEGMENTS }],
1345
+ committedAt: LATER
1346
+ }), TransactionRecordConflictError, "a single-shot write with a stale transaction revision");
1347
+ check(await store.getBlock("block-w2") === void 0, "a revision conflict must leave no block");
1348
+ const second = await write({
1349
+ transaction: { id: "txn-w2", expectedRevision: 0 },
1350
+ blocks: [{ id: "block-w2", bytes: Uint8Array.of(4) }],
1351
+ segments: [segment("segment-w2", "table-t", "txn-w2", "block-w2")],
1352
+ expectedManifestVersion: 0,
1353
+ levelZeroSegmentLimits: [{ tableId: "table-t", limit: MAX_LEVEL_ZERO_SEGMENTS }],
1354
+ changedTableIds: ["table-t"],
1355
+ committedAt: LATER
1356
+ });
1357
+ checkEqual(second.version, 1, "the continued write must publish the next version");
1358
+ checkEqual(second.changedTableIds, ["table-t"], "the summary must carry the change set");
1359
+ checkEqual(await manifestBlockIds(store, 1), ["block-w1", "block-w2"], "the continued write must preserve visible blocks and add its own");
1360
+ check((await store.getTransaction("txn-w2"))?.status === "committed", "the begun record must end committed");
1361
+ store.close();
1362
+ }
1363
+ },
1364
+ {
1365
+ name: "compaction commit proves exact output provenance before publication",
1366
+ async run(target) {
1367
+ const store = await target.create();
1368
+ await store.addTable({
1369
+ id: "table-compact",
1370
+ name: "compact",
1371
+ columns: [{ id: "col-value", name: "value", type: "number", nullable: false }],
1372
+ managed: false,
1373
+ revision: 0,
1374
+ createdAt: T0
1375
+ });
1376
+ await store.createTransaction(transaction("txn-compact-source", null));
1377
+ const source = await store.stageTransactionArtifacts({
1378
+ transactionId: "txn-compact-source",
1379
+ expectedRevision: 0,
1380
+ blocks: [{ id: "compact-source-block", bytes: Uint8Array.of(1) }],
1381
+ segments: [
1382
+ {
1383
+ ...segment("compact-source-segment", "table-compact", "txn-compact-source", "compact-source-block"),
1384
+ columnBlockIds: { "col-value": ["compact-source-block"] }
1385
+ }
1386
+ ],
1387
+ updatedAt: T0
1388
+ });
1389
+ await store.commitTransaction({
1390
+ transactionId: source.id,
1391
+ expectedTransactionRevision: source.revision,
1392
+ expectedManifestVersion: null,
1393
+ levelZeroSegmentLimits: [{ tableId: "table-compact", limit: MAX_LEVEL_ZERO_SEGMENTS }],
1394
+ committedAt: T0
1395
+ });
1396
+ const jobId = "compact-proof-job";
1397
+ const outputBlockId = `${jobId}/output/segment/000000/column/000000/part/000000`;
1398
+ await store.createTransaction(transaction("txn-compact-output", 0));
1399
+ const output = await store.stageTransactionArtifacts({
1400
+ transactionId: "txn-compact-output",
1401
+ expectedRevision: 0,
1402
+ blocks: [{ id: outputBlockId, bytes: Uint8Array.of(2) }],
1403
+ segments: [
1404
+ {
1405
+ ...segment("compact-output-segment", "table-compact", "txn-compact-output", outputBlockId),
1406
+ columnBlockIds: { "col-value": [outputBlockId] },
1407
+ kind: "base",
1408
+ level: 1
1409
+ }
1410
+ ],
1411
+ updatedAt: LATER
1412
+ });
1413
+ const job = {
1414
+ id: jobId,
1415
+ tableId: "table-compact",
1416
+ sourceManifestVersion: 0,
1417
+ sourceSegmentIds: ["compact-source-segment"],
1418
+ sourceBlockIds: ["compact-source-block"],
1419
+ outputBlockIds: [outputBlockId],
1420
+ cursor: { sourceSegmentIndex: 1, sourceBlockIndex: 0 },
1421
+ processedRows: 2,
1422
+ sourceStoredBytes: 1,
1423
+ outputStoredBytes: 1,
1424
+ logicalBytes: 1,
1425
+ rewritePlan: { kind: "copy-v1" },
1426
+ outputCursor: null,
1427
+ memoryBudgetBytes: 0,
1428
+ minimumMemoryBytes: 0,
1429
+ level0SourceStoredBytes: 1,
1430
+ anchorSourceStoredBytes: 0,
1431
+ peakWorkingBytes: 0,
1432
+ outputLogicalBytes: 1,
1433
+ targetLevel: 1,
1434
+ state: "ready",
1435
+ transactionId: output.id,
1436
+ outputSegmentId: "compact-output-segment",
1437
+ publishedVersion: null,
1438
+ revision: 0,
1439
+ createdAt: T0,
1440
+ updatedAt: LATER
1441
+ };
1442
+ await store.createCompactionJob(job);
1443
+ await checkThrows(() => store.commitTransaction({
1444
+ transactionId: output.id,
1445
+ expectedTransactionRevision: output.revision,
1446
+ expectedManifestVersion: 0,
1447
+ compactionJobId: job.id,
1448
+ removedBlockIds: ["compact-source-block"],
1449
+ committedAt: LATER
1450
+ }), Error, "a structurally valid compaction segment that differs from the immutable plan");
1451
+ checkEqual(await store.getCurrentManifestVersion(), 0, "a provenance refusal must not publish a manifest");
1452
+ check((await store.getTransaction(output.id))?.status === "active" && (await store.getSegment("compact-output-segment"))?.kind === "base" && await store.getBlock(outputBlockId) !== void 0 && (await store.getCompactionJob(job.id))?.state === "ready", "a provenance refusal must leave the transaction, artifacts, and job unchanged");
1453
+ checkEqual(await manifestBlockIds(store, 0), ["compact-source-block"], "a provenance refusal must retain every source block");
1454
+ store.close();
1455
+ }
1456
+ },
1457
+ {
1458
+ name: "the lease move, when present, re-pins in place and refuses typed",
1459
+ async run(target) {
1460
+ const store = await target.create();
1461
+ const move = store.moveLease?.bind(store);
1462
+ if (move === void 0) {
1463
+ store.close();
1464
+ return;
1465
+ }
1466
+ await store.addTable(table("t"));
1467
+ const first = await commitOne(store, "one");
1468
+ const second = await commitOne(store, "two");
1469
+ await store.createLease({
1470
+ id: "lease-m",
1471
+ kind: "reader",
1472
+ manifestVersion: first.version,
1473
+ ownerId: "owner-m",
1474
+ createdAt: T0,
1475
+ expiresAt: LATER,
1476
+ revision: 0
1477
+ });
1478
+ await checkThrows(() => move({
1479
+ id: "lease-m",
1480
+ expectedRevision: 9,
1481
+ manifestVersion: second.version,
1482
+ expiresAtCutoff: T0,
1483
+ expiresAt: LATER
1484
+ }), LeaseConflictError, "moving with a stale revision");
1485
+ await checkThrows(() => move({
1486
+ id: "lease-m",
1487
+ expectedRevision: 0,
1488
+ manifestVersion: 999,
1489
+ expiresAtCutoff: T0,
1490
+ expiresAt: LATER
1491
+ }), SnapshotManifestMissingError, "moving to a version with no manifest");
1492
+ checkEqual(await store.getLease("lease-m"), {
1493
+ id: "lease-m",
1494
+ kind: "reader",
1495
+ manifestVersion: first.version,
1496
+ ownerId: "owner-m",
1497
+ createdAt: T0,
1498
+ expiresAt: LATER,
1499
+ revision: 0
1500
+ }, "a refused move must leave the lease exactly as it was");
1501
+ const moved = await move({
1502
+ id: "lease-m",
1503
+ expectedRevision: 0,
1504
+ manifestVersion: second.version,
1505
+ expiresAtCutoff: T0,
1506
+ expiresAt: LATER
1507
+ });
1508
+ checkEqual(moved, {
1509
+ id: "lease-m",
1510
+ kind: "reader",
1511
+ manifestVersion: second.version,
1512
+ ownerId: "owner-m",
1513
+ createdAt: T0,
1514
+ expiresAt: LATER,
1515
+ revision: 1
1516
+ }, "a move must re-pin, renew, and advance the revision");
1517
+ checkEqual((await store.listLeases()).map((lease) => lease.id), ["lease-m"], "a move must keep the one record, not create a second");
1518
+ store.close();
1519
+ }
1520
+ },
1521
+ {
1522
+ name: "everything committed survives a reopen",
1523
+ async run(target) {
1524
+ if (target.reopen === void 0)
1525
+ return;
1526
+ let store = await target.create();
1527
+ await store.addTable(table("t", true));
1528
+ let { version, blockId } = await commitOne(store, "one", { keyTokens: ["number:1"] });
1529
+ const reserved = await store.reserveRowIds("table-t", 10);
1530
+ const write = store.writeTransaction?.bind(store);
1531
+ if (write !== void 0) {
1532
+ const { snapshotVersion: _pinned, ...record } = transaction("txn-single", null);
1533
+ void _pinned;
1534
+ const summary = await write({
1535
+ transaction: {
1536
+ record: {
1537
+ ...record,
1538
+ schemaEpochGuard: (await store.getCatalogProbe()).schemaEpoch
1539
+ }
1660
1540
  },
1661
- },
1662
- ];
1541
+ blocks: [{ id: "block-single", bytes: Uint8Array.of(1, 2, 3, 4) }],
1542
+ segments: [segment("segment-single", "table-t", "txn-single", "block-single")],
1543
+ expectedManifestVersion: version,
1544
+ levelZeroSegmentLimits: [{ tableId: "table-t", limit: MAX_LEVEL_ZERO_SEGMENTS }],
1545
+ uniqueKeyChanges: [
1546
+ { tableId: "table-t", keyTokens: ["number:1"], requireAbsent: false }
1547
+ ],
1548
+ committedAt: LATER
1549
+ });
1550
+ version = summary.version;
1551
+ blockId = "block-single";
1552
+ }
1553
+ const move = store.moveLease?.bind(store);
1554
+ if (move !== void 0) {
1555
+ await store.createLease({
1556
+ id: "lease-durable",
1557
+ kind: "reader",
1558
+ manifestVersion: null,
1559
+ ownerId: "owner",
1560
+ createdAt: T0,
1561
+ expiresAt: LATER,
1562
+ revision: 0
1563
+ });
1564
+ await move({
1565
+ id: "lease-durable",
1566
+ expectedRevision: 0,
1567
+ manifestVersion: version,
1568
+ expiresAtCutoff: T0,
1569
+ expiresAt: LATER
1570
+ });
1571
+ }
1572
+ store = await target.reopen(store);
1573
+ checkEqual(await store.getCurrentManifestVersion(), version, "the committed version must survive a reopen");
1574
+ if (move !== void 0) {
1575
+ checkEqual((await store.getLease("lease-durable"))?.manifestVersion, version, "a moved lease must survive a reopen at its new version");
1576
+ }
1577
+ checkEqual(await store.getBlock(blockId), Uint8Array.of(1, 2, 3, 4), "committed bytes must survive a reopen");
1578
+ checkEqual(await store.getExistingUniqueKeys("table-t", ["number:1"]), ["number:1"], "unique-key membership must survive a reopen");
1579
+ check(await store.getTableByName("t") !== void 0, "the catalog must survive a reopen");
1580
+ const next = await store.reserveRowIds("table-t", 1);
1581
+ check(next.start >= reserved.endExclusive, "counters must continue past the reserved high-water mark after a reopen");
1582
+ store.close();
1583
+ }
1584
+ }
1585
+ ];
1663
1586
  }
1587
+ export {
1588
+ blockStoreConformanceCases,
1589
+ runBlockStoreConformance
1590
+ };