@optimystic/db-p2p 0.24.2 → 0.25.0

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 (249) hide show
  1. package/{readme.md → README.md} +688 -668
  2. package/dist/src/cluster/block-transfer-service.d.ts +133 -10
  3. package/dist/src/cluster/block-transfer-service.d.ts.map +1 -1
  4. package/dist/src/cluster/block-transfer-service.js +155 -9
  5. package/dist/src/cluster/block-transfer-service.js.map +1 -1
  6. package/dist/src/cluster/block-transfer.d.ts +67 -7
  7. package/dist/src/cluster/block-transfer.d.ts.map +1 -1
  8. package/dist/src/cluster/block-transfer.js +127 -24
  9. package/dist/src/cluster/block-transfer.js.map +1 -1
  10. package/dist/src/cluster/certified-claims.d.ts +170 -0
  11. package/dist/src/cluster/certified-claims.d.ts.map +1 -0
  12. package/dist/src/cluster/certified-claims.js +200 -0
  13. package/dist/src/cluster/certified-claims.js.map +1 -0
  14. package/dist/src/cluster/client-signature-verifier.d.ts +23 -0
  15. package/dist/src/cluster/client-signature-verifier.d.ts.map +1 -0
  16. package/dist/src/cluster/client-signature-verifier.js +33 -0
  17. package/dist/src/cluster/client-signature-verifier.js.map +1 -0
  18. package/dist/src/cluster/cluster-policy.d.ts +9 -3
  19. package/dist/src/cluster/cluster-policy.d.ts.map +1 -1
  20. package/dist/src/cluster/cluster-policy.js +111 -27
  21. package/dist/src/cluster/cluster-policy.js.map +1 -1
  22. package/dist/src/cluster/cluster-repo.d.ts +140 -11
  23. package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
  24. package/dist/src/cluster/cluster-repo.js +485 -29
  25. package/dist/src/cluster/cluster-repo.js.map +1 -1
  26. package/dist/src/cluster/commit-proof.d.ts +134 -0
  27. package/dist/src/cluster/commit-proof.d.ts.map +1 -0
  28. package/dist/src/cluster/commit-proof.js +263 -0
  29. package/dist/src/cluster/commit-proof.js.map +1 -0
  30. package/dist/src/cluster/quorum-restore.d.ts +95 -8
  31. package/dist/src/cluster/quorum-restore.d.ts.map +1 -1
  32. package/dist/src/cluster/quorum-restore.js +113 -14
  33. package/dist/src/cluster/quorum-restore.js.map +1 -1
  34. package/dist/src/cluster/rebalance-monitor.d.ts +84 -0
  35. package/dist/src/cluster/rebalance-monitor.d.ts.map +1 -1
  36. package/dist/src/cluster/rebalance-monitor.js +207 -5
  37. package/dist/src/cluster/rebalance-monitor.js.map +1 -1
  38. package/dist/src/cluster/reconcile-block.d.ts +27 -7
  39. package/dist/src/cluster/reconcile-block.d.ts.map +1 -1
  40. package/dist/src/cluster/reconcile-block.js +179 -38
  41. package/dist/src/cluster/reconcile-block.js.map +1 -1
  42. package/dist/src/cluster/service.d.ts +13 -4
  43. package/dist/src/cluster/service.d.ts.map +1 -1
  44. package/dist/src/cluster/service.js +17 -18
  45. package/dist/src/cluster/service.js.map +1 -1
  46. package/dist/src/cluster/spread-on-churn.d.ts +8 -2
  47. package/dist/src/cluster/spread-on-churn.d.ts.map +1 -1
  48. package/dist/src/cluster/spread-on-churn.js +7 -7
  49. package/dist/src/cluster/spread-on-churn.js.map +1 -1
  50. package/dist/src/cohort-topic/host.d.ts.map +1 -1
  51. package/dist/src/cohort-topic/host.js +8 -7
  52. package/dist/src/cohort-topic/host.js.map +1 -1
  53. package/dist/src/cohort-topic/stream-util.d.ts +7 -6
  54. package/dist/src/cohort-topic/stream-util.d.ts.map +1 -1
  55. package/dist/src/cohort-topic/stream-util.js +14 -41
  56. package/dist/src/cohort-topic/stream-util.js.map +1 -1
  57. package/dist/src/dispute/cascade.d.ts +7 -8
  58. package/dist/src/dispute/cascade.d.ts.map +1 -1
  59. package/dist/src/dispute/cascade.js +24 -7
  60. package/dist/src/dispute/cascade.js.map +1 -1
  61. package/dist/src/dispute/dispute-service.d.ts +5 -0
  62. package/dist/src/dispute/dispute-service.d.ts.map +1 -1
  63. package/dist/src/dispute/dispute-service.js +13 -3
  64. package/dist/src/dispute/dispute-service.js.map +1 -1
  65. package/dist/src/dispute/invalidation.d.ts +33 -15
  66. package/dist/src/dispute/invalidation.d.ts.map +1 -1
  67. package/dist/src/dispute/invalidation.js +127 -34
  68. package/dist/src/dispute/invalidation.js.map +1 -1
  69. package/dist/src/dispute/service.d.ts.map +1 -1
  70. package/dist/src/dispute/service.js +2 -1
  71. package/dist/src/dispute/service.js.map +1 -1
  72. package/dist/src/index.d.ts +7 -0
  73. package/dist/src/index.d.ts.map +1 -1
  74. package/dist/src/index.js +7 -0
  75. package/dist/src/index.js.map +1 -1
  76. package/dist/src/libp2p-key-network.d.ts +14 -10
  77. package/dist/src/libp2p-key-network.d.ts.map +1 -1
  78. package/dist/src/libp2p-key-network.js +28 -58
  79. package/dist/src/libp2p-key-network.js.map +1 -1
  80. package/dist/src/libp2p-node-base.d.ts +38 -7
  81. package/dist/src/libp2p-node-base.d.ts.map +1 -1
  82. package/dist/src/libp2p-node-base.js +148 -63
  83. package/dist/src/libp2p-node-base.js.map +1 -1
  84. package/dist/src/network/open-protocol-stream.d.ts +56 -0
  85. package/dist/src/network/open-protocol-stream.d.ts.map +1 -0
  86. package/dist/src/network/open-protocol-stream.js +60 -0
  87. package/dist/src/network/open-protocol-stream.js.map +1 -0
  88. package/dist/src/network/register-protocol-handler.d.ts +66 -0
  89. package/dist/src/network/register-protocol-handler.d.ts.map +1 -0
  90. package/dist/src/network/register-protocol-handler.js +51 -0
  91. package/dist/src/network/register-protocol-handler.js.map +1 -0
  92. package/dist/src/peer-address-book.d.ts +60 -2
  93. package/dist/src/peer-address-book.d.ts.map +1 -1
  94. package/dist/src/peer-address-book.js +89 -1
  95. package/dist/src/peer-address-book.js.map +1 -1
  96. package/dist/src/pend-validation.d.ts +65 -0
  97. package/dist/src/pend-validation.d.ts.map +1 -0
  98. package/dist/src/pend-validation.js +69 -0
  99. package/dist/src/pend-validation.js.map +1 -0
  100. package/dist/src/reactivity/notify-transport.d.ts.map +1 -1
  101. package/dist/src/reactivity/notify-transport.js +2 -1
  102. package/dist/src/reactivity/notify-transport.js.map +1 -1
  103. package/dist/src/reactivity/push-state-gossip.d.ts.map +1 -1
  104. package/dist/src/reactivity/push-state-gossip.js +2 -1
  105. package/dist/src/reactivity/push-state-gossip.js.map +1 -1
  106. package/dist/src/repo/cluster-coordinator.d.ts +32 -6
  107. package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
  108. package/dist/src/repo/cluster-coordinator.js +44 -10
  109. package/dist/src/repo/cluster-coordinator.js.map +1 -1
  110. package/dist/src/repo/coordinator-repo.d.ts +181 -25
  111. package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
  112. package/dist/src/repo/coordinator-repo.js +650 -62
  113. package/dist/src/repo/coordinator-repo.js.map +1 -1
  114. package/dist/src/repo/served-repo-proxy.d.ts +30 -0
  115. package/dist/src/repo/served-repo-proxy.d.ts.map +1 -0
  116. package/dist/src/repo/served-repo-proxy.js +50 -0
  117. package/dist/src/repo/served-repo-proxy.js.map +1 -0
  118. package/dist/src/repo/service.d.ts +16 -1
  119. package/dist/src/repo/service.d.ts.map +1 -1
  120. package/dist/src/repo/service.js +18 -16
  121. package/dist/src/repo/service.js.map +1 -1
  122. package/dist/src/reputation/types.d.ts +3 -1
  123. package/dist/src/reputation/types.d.ts.map +1 -1
  124. package/dist/src/reputation/types.js +3 -1
  125. package/dist/src/reputation/types.js.map +1 -1
  126. package/dist/src/rn.d.ts +24 -2
  127. package/dist/src/rn.d.ts.map +1 -1
  128. package/dist/src/rn.js +33 -4
  129. package/dist/src/rn.js.map +1 -1
  130. package/dist/src/storage/block-archive.d.ts +153 -0
  131. package/dist/src/storage/block-archive.d.ts.map +1 -0
  132. package/dist/src/storage/block-archive.js +194 -0
  133. package/dist/src/storage/block-archive.js.map +1 -0
  134. package/dist/src/storage/block-latch.d.ts +92 -0
  135. package/dist/src/storage/block-latch.d.ts.map +1 -0
  136. package/dist/src/storage/block-latch.js +132 -0
  137. package/dist/src/storage/block-latch.js.map +1 -0
  138. package/dist/src/storage/block-storage.d.ts +169 -12
  139. package/dist/src/storage/block-storage.d.ts.map +1 -1
  140. package/dist/src/storage/block-storage.js +415 -152
  141. package/dist/src/storage/block-storage.js.map +1 -1
  142. package/dist/src/storage/cached-raw-storage.d.ts +5 -1
  143. package/dist/src/storage/cached-raw-storage.d.ts.map +1 -1
  144. package/dist/src/storage/cached-raw-storage.js +16 -1
  145. package/dist/src/storage/cached-raw-storage.js.map +1 -1
  146. package/dist/src/storage/cached-store-driver.d.ts +14 -4
  147. package/dist/src/storage/cached-store-driver.d.ts.map +1 -1
  148. package/dist/src/storage/cached-store-driver.js +39 -5
  149. package/dist/src/storage/cached-store-driver.js.map +1 -1
  150. package/dist/src/storage/i-block-storage.d.ts +70 -19
  151. package/dist/src/storage/i-block-storage.d.ts.map +1 -1
  152. package/dist/src/storage/i-block-storage.js +21 -1
  153. package/dist/src/storage/i-block-storage.js.map +1 -1
  154. package/dist/src/storage/i-raw-storage.d.ts +38 -0
  155. package/dist/src/storage/i-raw-storage.d.ts.map +1 -1
  156. package/dist/src/storage/kv-raw-storage.d.ts +14 -8
  157. package/dist/src/storage/kv-raw-storage.d.ts.map +1 -1
  158. package/dist/src/storage/kv-raw-storage.js +29 -9
  159. package/dist/src/storage/kv-raw-storage.js.map +1 -1
  160. package/dist/src/storage/memory-store-driver.d.ts +4 -1
  161. package/dist/src/storage/memory-store-driver.d.ts.map +1 -1
  162. package/dist/src/storage/memory-store-driver.js +22 -2
  163. package/dist/src/storage/memory-store-driver.js.map +1 -1
  164. package/dist/src/storage/raw-store-codec.js +2 -2
  165. package/dist/src/storage/raw-store-driver.d.ts +44 -3
  166. package/dist/src/storage/raw-store-driver.d.ts.map +1 -1
  167. package/dist/src/storage/shared-cache-pool.d.ts +60 -3
  168. package/dist/src/storage/shared-cache-pool.d.ts.map +1 -1
  169. package/dist/src/storage/shared-cache-pool.js +68 -4
  170. package/dist/src/storage/shared-cache-pool.js.map +1 -1
  171. package/dist/src/storage/storage-repo.d.ts +204 -26
  172. package/dist/src/storage/storage-repo.d.ts.map +1 -1
  173. package/dist/src/storage/storage-repo.js +422 -119
  174. package/dist/src/storage/storage-repo.js.map +1 -1
  175. package/dist/src/storage/store-identity.d.ts +21 -0
  176. package/dist/src/storage/store-identity.d.ts.map +1 -0
  177. package/dist/src/storage/store-identity.js +33 -0
  178. package/dist/src/storage/store-identity.js.map +1 -0
  179. package/dist/src/storage/struct.d.ts +15 -0
  180. package/dist/src/storage/struct.d.ts.map +1 -1
  181. package/dist/src/storage/with-read-cache.d.ts +92 -0
  182. package/dist/src/storage/with-read-cache.d.ts.map +1 -0
  183. package/dist/src/storage/with-read-cache.js +132 -0
  184. package/dist/src/storage/with-read-cache.js.map +1 -0
  185. package/dist/src/sync/service.d.ts +13 -3
  186. package/dist/src/sync/service.d.ts.map +1 -1
  187. package/dist/src/sync/service.js +7 -30
  188. package/dist/src/sync/service.js.map +1 -1
  189. package/dist/src/testing/mesh-harness.d.ts +75 -2
  190. package/dist/src/testing/mesh-harness.d.ts.map +1 -1
  191. package/dist/src/testing/mesh-harness.js +193 -81
  192. package/dist/src/testing/mesh-harness.js.map +1 -1
  193. package/dist/src/testing/raw-storage-conformance.d.ts +8 -0
  194. package/dist/src/testing/raw-storage-conformance.d.ts.map +1 -1
  195. package/dist/src/testing/raw-storage-conformance.js +97 -2
  196. package/dist/src/testing/raw-storage-conformance.js.map +1 -1
  197. package/package.json +5 -6
  198. package/src/cluster/block-transfer-service.ts +528 -312
  199. package/src/cluster/block-transfer.ts +547 -424
  200. package/src/cluster/certified-claims.ts +319 -0
  201. package/src/cluster/client-signature-verifier.ts +34 -0
  202. package/src/cluster/cluster-policy.ts +294 -203
  203. package/src/cluster/cluster-repo.ts +2501 -2034
  204. package/src/cluster/commit-proof.ts +344 -0
  205. package/src/cluster/quorum-restore.ts +348 -223
  206. package/src/cluster/rebalance-monitor.ts +539 -254
  207. package/src/cluster/reconcile-block.ts +404 -203
  208. package/src/cluster/service.ts +23 -20
  209. package/src/cluster/spread-on-churn.ts +352 -346
  210. package/src/cohort-topic/host.ts +8 -7
  211. package/src/cohort-topic/stream-util.ts +14 -43
  212. package/src/dispute/cascade.ts +31 -15
  213. package/src/dispute/dispute-service.ts +706 -696
  214. package/src/dispute/invalidation.ts +162 -52
  215. package/src/dispute/service.ts +126 -125
  216. package/src/index.ts +7 -0
  217. package/src/libp2p-key-network.ts +1204 -1235
  218. package/src/libp2p-node-base.ts +185 -70
  219. package/src/network/open-protocol-stream.ts +87 -0
  220. package/src/network/register-protocol-handler.ts +85 -0
  221. package/src/peer-address-book.ts +96 -2
  222. package/src/pend-validation.ts +89 -0
  223. package/src/reactivity/notify-transport.ts +2 -1
  224. package/src/reactivity/push-state-gossip.ts +2 -1
  225. package/src/repo/cluster-coordinator.ts +65 -11
  226. package/src/repo/coordinator-repo.ts +729 -71
  227. package/src/repo/served-repo-proxy.ts +55 -0
  228. package/src/repo/service.ts +25 -16
  229. package/src/reputation/types.ts +3 -1
  230. package/src/rn.ts +34 -4
  231. package/src/storage/block-archive.ts +242 -0
  232. package/src/storage/block-latch.ts +144 -0
  233. package/src/storage/block-storage.ts +787 -498
  234. package/src/storage/cached-raw-storage.ts +21 -1
  235. package/src/storage/cached-store-driver.ts +898 -859
  236. package/src/storage/i-block-storage.ts +172 -116
  237. package/src/storage/i-raw-storage.ts +40 -0
  238. package/src/storage/kv-raw-storage.ts +34 -9
  239. package/src/storage/memory-store-driver.ts +26 -2
  240. package/src/storage/raw-store-codec.ts +42 -42
  241. package/src/storage/raw-store-driver.ts +45 -3
  242. package/src/storage/shared-cache-pool.ts +516 -452
  243. package/src/storage/storage-repo.ts +1421 -1014
  244. package/src/storage/store-identity.ts +41 -0
  245. package/src/storage/struct.ts +52 -34
  246. package/src/storage/with-read-cache.ts +182 -0
  247. package/src/sync/service.ts +178 -198
  248. package/src/testing/mesh-harness.ts +524 -344
  249. package/src/testing/raw-storage-conformance.ts +114 -2
@@ -1,9 +1,11 @@
1
1
  import { expect } from 'chai';
2
2
  import type { BlockId, ActionId, IBlock, BlockHeader, Transform, Transforms } from '@optimystic/db-core';
3
+ import type { BlockCommitProof } from '../cluster/commit-proof.js';
3
4
  import type { IRawStorage } from '../storage/i-raw-storage.js';
4
5
  import type { BlockMetadata } from '../storage/struct.js';
5
6
  import { StorageRepo } from '../storage/storage-repo.js';
6
7
  import { BlockStorage } from '../storage/block-storage.js';
8
+ import { withBlockWriteLatch } from '../storage/block-latch.js';
7
9
 
8
10
  /**
9
11
  * What `makeStorage` hands each conformance case: a fresh `IRawStorage` and a
@@ -32,6 +34,23 @@ const makeInsertTransforms = (blockId: BlockId, block: IBlock): Transforms => ({
32
34
  deletes: []
33
35
  });
34
36
 
37
+ /**
38
+ * A structurally-valid {@link BlockCommitProof} carrying a recognizable marker. Storage-level
39
+ * suites never VERIFY a proof (that is `commit-proof.spec.ts`); they only assert the proofs
40
+ * store round-trips a proof-shaped object and keeps it in its own keyspace, so the votes are
41
+ * deliberately empty. Exported so every proofs-store suite shares one shape.
42
+ */
43
+ export const makeProof = (marker: string): BlockCommitProof => ({
44
+ v: 1,
45
+ messageHash: `hash-${marker}`,
46
+ message: { operations: [] } as unknown as BlockCommitProof['message'],
47
+ promises: {},
48
+ commits: {},
49
+ membershipVersion: 2,
50
+ membershipDigest: `digest-${marker}`,
51
+ peerIds: ['peer-a', 'peer-b']
52
+ });
53
+
35
54
  async function collect<T>(iter: AsyncIterable<T>): Promise<T[]> {
36
55
  const out: T[] = [];
37
56
  for await (const item of iter) out.push(item);
@@ -241,6 +260,84 @@ export function runRawStorageConformance(
241
260
  expect(await storage.getTransaction(blockId, 'a1' as ActionId)).to.deep.equal(transform);
242
261
  });
243
262
 
263
+ // --- Commit proofs (their own (blockId, rev)-keyed store) ---
264
+
265
+ it('getBlockProof returns undefined on a miss', async () => {
266
+ expect(await storage.getBlockProof('block' as BlockId, 1)).to.equal(undefined);
267
+ });
268
+
269
+ it('commit proof round-trips', async () => {
270
+ const blockId = 'proof-block' as BlockId;
271
+ const proof = makeProof('rt');
272
+ await storage.saveBlockProof(blockId, 5, proof);
273
+ expect(await storage.getBlockProof(blockId, 5)).to.deep.equal(proof);
274
+ });
275
+
276
+ it('proofs are per-block and per-revision', async () => {
277
+ const a = 'proof-a' as BlockId;
278
+ const b = 'proof-b' as BlockId;
279
+ await storage.saveBlockProof(a, 1, makeProof('a1'));
280
+ await storage.saveBlockProof(a, 2, makeProof('a2'));
281
+ await storage.saveBlockProof(b, 1, makeProof('b1'));
282
+
283
+ expect((await storage.getBlockProof(a, 1))!.messageHash).to.equal('hash-a1');
284
+ expect((await storage.getBlockProof(a, 2))!.messageHash).to.equal('hash-a2');
285
+ expect((await storage.getBlockProof(b, 1))!.messageHash).to.equal('hash-b1');
286
+ expect(await storage.getBlockProof(a, 3), 'an unwritten rev on a block that has proofs')
287
+ .to.equal(undefined);
288
+ });
289
+
290
+ it('a later save replaces the proof for that revision only', async () => {
291
+ const blockId = 'proof-replace' as BlockId;
292
+ await storage.saveBlockProof(blockId, 1, makeProof('first'));
293
+ await storage.saveBlockProof(blockId, 2, makeProof('other-rev'));
294
+ await storage.saveBlockProof(blockId, 1, makeProof('second'));
295
+
296
+ expect((await storage.getBlockProof(blockId, 1))!.messageHash).to.equal('hash-second');
297
+ expect((await storage.getBlockProof(blockId, 2))!.messageHash).to.equal('hash-other-rev');
298
+ });
299
+
300
+ // The whole point of giving proofs their own store: an action id is chosen by whoever
301
+ // originates a write (a client's `pend`, a peer's restore archive) and is never
302
+ // re-derived or format-checked by the storing node, so a transaction may legitimately
303
+ // be named `~proof:<N>` — the id proofs USED to be filed under in the transactions
304
+ // store. Neither write may disturb the other, in either arrival order.
305
+ it('a transaction named `~proof:<N>` and the proof for revision N do not collide', async () => {
306
+ const blockId = 'proof-collide' as BlockId;
307
+ const collidingId = '~proof:5' as ActionId;
308
+ const transform: Transform = { insert: makeBlock('proof-collide', { items: ['committed'] }) };
309
+
310
+ // Order A: committed transform first, then the proof.
311
+ await storage.saveTransaction(blockId, collidingId, transform);
312
+ await storage.saveBlockProof(blockId, 5, makeProof('A'));
313
+ expect(await storage.getTransaction(blockId, collidingId), 'the committed transform survives')
314
+ .to.deep.equal(transform);
315
+ expect((await storage.getBlockProof(blockId, 5))!.messageHash, 'the proof is stored')
316
+ .to.equal('hash-A');
317
+
318
+ // Order B: proof first, then a transaction under the colliding id.
319
+ const other = 'proof-collide-b' as BlockId;
320
+ await storage.saveBlockProof(other, 5, makeProof('B'));
321
+ await storage.saveTransaction(other, collidingId, transform);
322
+ expect((await storage.getBlockProof(other, 5))!.messageHash, 'the proof survives')
323
+ .to.equal('hash-B');
324
+ expect(await storage.getTransaction(other, collidingId), 'the transform is stored')
325
+ .to.deep.equal(transform);
326
+ });
327
+
328
+ it('saveBlockProof snapshots — a later caller mutation does not corrupt stored state', async () => {
329
+ const blockId = 'proof-clone' as BlockId;
330
+ const proof = makeProof('clone');
331
+ await storage.saveBlockProof(blockId, 1, proof);
332
+
333
+ proof.peerIds.push('peer-c');
334
+ proof.messageHash = 'hash-mutated';
335
+
336
+ const stored = await storage.getBlockProof(blockId, 1);
337
+ expect(stored!.peerIds).to.deep.equal(['peer-a', 'peer-b']);
338
+ expect(stored!.messageHash).to.equal('hash-clone');
339
+ });
340
+
244
341
  // --- Materialized blocks ---
245
342
 
246
343
  it('getMaterializedBlock returns undefined on a miss', async () => {
@@ -348,6 +445,20 @@ export function runRawStorageConformance(
348
445
  expect(reread.items, 'fresh read unaffected by mutation of a prior read').to.deep.equal(['original']);
349
446
  });
350
447
 
448
+ // --- getStoreIdentity (optional; only when the backend implements it) ---
449
+
450
+ it('getStoreIdentity is a stable, scheme-prefixed, non-empty string', async function () {
451
+ const getStoreIdentity = storage.getStoreIdentity;
452
+ if (typeof getStoreIdentity !== 'function') {
453
+ this.skip();
454
+ } else {
455
+ const first = getStoreIdentity.call(storage);
456
+ expect(first, 'identity is a non-empty string').to.be.a('string').and.not.equal('');
457
+ expect(first, 'identity is scheme-prefixed').to.include(':');
458
+ expect(getStoreIdentity.call(storage), 'identity is stable across calls').to.equal(first);
459
+ }
460
+ });
461
+
351
462
  // --- listBlockIds (optional; only when the driver implements it) ---
352
463
 
353
464
  it('listBlockIds yields exactly the blocks with metadata', async function () {
@@ -436,12 +547,13 @@ export function runRawStorageConformance(
436
547
  const blockId = 'parity-tombstone' as BlockId;
437
548
  const bs = new BlockStorage(blockId, storage);
438
549
 
439
- await bs.saveReplica(makeBlock('parity-tombstone', { items: ['live'] }), { rev: 1, actionId: 'r1' as ActionId });
550
+ await withBlockWriteLatch(blockId, l =>
551
+ bs.saveReplica(makeBlock('parity-tombstone', { items: ['live'] }), { rev: 1, actionId: 'r1' as ActionId }, undefined, l));
440
552
  const meta = await storage.getMetadata(blockId);
441
553
  // saveReplica seeds open-ended coverage anchored at the replica rev, never [[0]].
442
554
  expect(meta!.ranges, 'replica seeds open-ended [[1]]').to.deep.equal([[1]]);
443
555
 
444
- const latest = await bs.saveDeletion({ rev: 2, actionId: 'd2' as ActionId });
556
+ const latest = await withBlockWriteLatch(blockId, l => bs.saveDeletion({ rev: 2, actionId: 'd2' as ActionId }, l));
445
557
  expect(latest.rev).to.equal(2);
446
558
 
447
559
  // getBlock at the tombstone rev reverse-applies { delete: true } → absent block.