@cloudbitmaps/core 0.20.0 → 0.22.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 (77) hide show
  1. package/README.md +101 -5
  2. package/dist/{chunk-YSC7NJWV.js → chunk-OSU7W2Y3.js} +811 -164
  3. package/dist/chunk-OSU7W2Y3.js.map +7 -0
  4. package/dist/core/audit.d.ts +12 -2
  5. package/dist/core/bit-route.d.ts +9 -1
  6. package/dist/core/blob.d.ts +12 -1
  7. package/dist/core/codec.d.ts +1 -1
  8. package/dist/core/combine-expr.d.ts +10 -8
  9. package/dist/core/combine-feed.d.ts +12 -18
  10. package/dist/core/combine-many.d.ts +29 -22
  11. package/dist/core/concurrency.d.ts +8 -0
  12. package/dist/core/consistency.d.ts +69 -17
  13. package/dist/core/crbm/reader.d.ts +52 -6
  14. package/dist/core/crbm-storage-source.d.ts +65 -452
  15. package/dist/core/crypto.d.ts +6 -1
  16. package/dist/core/determinism.d.ts +2 -1
  17. package/dist/core/engine.d.ts +222 -33
  18. package/dist/core/erase-id.d.ts +60 -15
  19. package/dist/core/erasure.d.ts +5 -4
  20. package/dist/core/errors.d.ts +61 -11
  21. package/dist/core/leases.d.ts +4 -3
  22. package/dist/core/load.d.ts +58 -45
  23. package/dist/core/lru.d.ts +2 -15
  24. package/dist/core/metrics.d.ts +5 -3
  25. package/dist/core/pinned-storage-source.d.ts +4 -4
  26. package/dist/core/ports.d.ts +96 -22
  27. package/dist/core/registry-scan.d.ts +0 -7
  28. package/dist/core/retention-sweep.d.ts +70 -66
  29. package/dist/core/retention.d.ts +2 -2
  30. package/dist/core/retry.d.ts +15 -18
  31. package/dist/core/rollback.d.ts +61 -31
  32. package/dist/core/validate.d.ts +2 -0
  33. package/dist/driver-kit.d.ts +11 -2
  34. package/dist/driver-kit.js +84 -16
  35. package/dist/driver-kit.js.map +3 -3
  36. package/dist/drivers/_shared/bytes.d.ts +6 -0
  37. package/dist/drivers/_shared/faults.d.ts +15 -0
  38. package/dist/drivers/_shared/object-keys.d.ts +18 -0
  39. package/dist/drivers/_shared/object-registry-keys.d.ts +0 -2
  40. package/dist/drivers/_shared/object-registry.d.ts +2 -0
  41. package/dist/drivers/_shared/options.d.ts +19 -0
  42. package/dist/drivers/backends.d.ts +2 -0
  43. package/dist/drivers/crypto.d.ts +1 -1
  44. package/dist/export/index.d.ts +9 -8
  45. package/dist/index.d.ts +11 -10
  46. package/dist/index.js +4202 -2833
  47. package/dist/index.js.map +4 -4
  48. package/package.json +7 -2
  49. package/dist/chunk-YSC7NJWV.js.map +0 -7
  50. package/dist/core/chunk-checks.d.ts +0 -34
  51. package/dist/core/chunk-stream.d.ts +0 -22
  52. package/dist/core/chunk-window.d.ts +0 -30
  53. package/dist/core/combine-ledger.d.ts +0 -43
  54. package/dist/core/crbm/crc32c.d.ts +0 -10
  55. package/dist/core/crbm/fingerprint.d.ts +0 -18
  56. package/dist/core/crbm/format.d.ts +0 -151
  57. package/dist/core/crbm/frame.d.ts +0 -10
  58. package/dist/core/crbm/plan-reads.d.ts +0 -44
  59. package/dist/core/crbm/varint.d.ts +0 -13
  60. package/dist/core/crbm/writer.d.ts +0 -68
  61. package/dist/core/due-index.d.ts +0 -53
  62. package/dist/core/generation-gc.d.ts +0 -179
  63. package/dist/core/item-pull.d.ts +0 -29
  64. package/dist/core/kept-generations.d.ts +0 -40
  65. package/dist/core/metadata.d.ts +0 -42
  66. package/dist/core/reader-defaults.d.ts +0 -29
  67. package/dist/core/summary.d.ts +0 -78
  68. package/dist/core/token.d.ts +0 -41
  69. package/dist/drivers/_shared/entropy.d.ts +0 -5
  70. package/dist/drivers/_shared/registry.d.ts +0 -104
  71. package/dist/drivers/localfs/exact-case.d.ts +0 -37
  72. package/dist/drivers/localfs/fs-util.d.ts +0 -33
  73. package/dist/drivers/localfs/paths.d.ts +0 -40
  74. package/dist/drivers/localfs/registry.d.ts +0 -44
  75. package/dist/drivers/localfs/storage.d.ts +0 -21
  76. package/dist/drivers/memory.d.ts +0 -61
  77. package/dist/testing/fuzz-core.d.ts +0 -15
@@ -3,7 +3,7 @@ var ERROR_BRAND = /* @__PURE__ */ Symbol.for("cloudbitmaps.error");
3
3
  var TRANSIENT_BRAND = /* @__PURE__ */ Symbol.for("cloudbitmaps.error.transient");
4
4
  var NAMES = /* @__PURE__ */ new WeakMap();
5
5
  var CloudRoaringError = class extends Error {
6
- /** Cross-bundle brand — see the predicates ({@link isCloudRoaringError}, …). Non-enumerable-ish (symbol key ⇒ not in JSON). */
6
+ /** Cross-bundle brand — see {@link errorKind}. Non-enumerable-ish (symbol key ⇒ not in JSON). */
7
7
  [ERROR_BRAND] = true;
8
8
  constructor(message) {
9
9
  super(message);
@@ -12,6 +12,7 @@ var CloudRoaringError = class extends Error {
12
12
  };
13
13
  var ValidationError = class extends CloudRoaringError {
14
14
  };
15
+ var CONTENTION_NEXT_STEP = "; another writer changed the segment's row while this call ran. Re-run the call: it reads the row afresh";
15
16
  var WriteConflictError = class extends CloudRoaringError {
16
17
  };
17
18
  var IntegrityError = class extends CloudRoaringError {
@@ -48,6 +49,21 @@ var UnsupportedError = class extends CloudRoaringError {
48
49
  var CapabilityError = class extends CloudRoaringError {
49
50
  };
50
51
  var BudgetExceededError = class extends CloudRoaringError {
52
+ /** The option to raise to let the call through. */
53
+ option;
54
+ /** The bound in force: requests, bytes or rows, by `option`. */
55
+ limit;
56
+ /**
57
+ * What the call had reached when refused. By `option`: the requests the call needs (`'budget.maxRequests'`), a
58
+ * lower bound on the resident bytes it needs (`'maxBufferedBytes'`), or `limit + 1` (`'maxScanSegments'`).
59
+ */
60
+ spent;
61
+ constructor(message, details) {
62
+ super(message);
63
+ this.option = details.option;
64
+ this.limit = details.limit;
65
+ this.spent = details.spent;
66
+ }
51
67
  };
52
68
  var KeyUnavailableError = class extends CloudRoaringError {
53
69
  };
@@ -84,15 +100,6 @@ function isWriteConflictError(err) {
84
100
  function isTransientError(err) {
85
101
  return hasBrand(err, TRANSIENT_BRAND);
86
102
  }
87
- function isLeaseExpiredError(err) {
88
- return isCloudRoaringError(err) && err.name === "LeaseExpiredError";
89
- }
90
- function isStaleOperandError(err) {
91
- return isCloudRoaringError(err) && err.name === "StaleOperandError";
92
- }
93
- function isLeaseLimitError(err) {
94
- return isCloudRoaringError(err) && err.name === "LeaseLimitError";
95
- }
96
103
  function isNotFoundError(err) {
97
104
  return isCloudRoaringError(err) && err.name === "NotFoundError";
98
105
  }
@@ -102,23 +109,169 @@ function isIntegrityError(err) {
102
109
  function isValidationError(err) {
103
110
  return isCloudRoaringError(err) && err.name === "ValidationError";
104
111
  }
105
- function isUnsupportedError(err) {
106
- return isCloudRoaringError(err) && err.name === "UnsupportedError";
112
+ function isKeyUnavailableError(err) {
113
+ return isCloudRoaringError(err) && err.name === "KeyUnavailableError";
107
114
  }
108
- function isCapabilityError(err) {
109
- return isCloudRoaringError(err) && err.name === "CapabilityError";
115
+ var KIND_NAMES = {
116
+ CloudRoaringError: true,
117
+ ValidationError: true,
118
+ WriteConflictError: true,
119
+ IntegrityError: true,
120
+ NotFoundError: true,
121
+ UnsupportedError: true,
122
+ CapabilityError: true,
123
+ TransientError: true,
124
+ KeyUnavailableError: true,
125
+ BudgetExceededError: true,
126
+ LeaseExpiredError: true,
127
+ LeaseLimitError: true,
128
+ StaleOperandError: true
129
+ };
130
+ function errorKind(err) {
131
+ if (hasBrand(err, TRANSIENT_BRAND)) return "TransientError";
132
+ if (!hasBrand(err, ERROR_BRAND)) return void 0;
133
+ const name = err.name;
134
+ return typeof name === "string" && Object.hasOwn(KIND_NAMES, name) ? name : "CloudRoaringError";
110
135
  }
111
- function isBudgetExceededError(err) {
112
- return isCloudRoaringError(err) && err.name === "BudgetExceededError";
136
+
137
+ // src/core/bit-route.ts
138
+ var U32_MAX = 4294967295;
139
+ var CHUNK_COUNT = 65536;
140
+ var LOW_16 = 65535;
141
+ var idErrors = /* @__PURE__ */ new WeakSet();
142
+ function isBadIdError(err) {
143
+ return err instanceof ValidationError && idErrors.has(err);
144
+ }
145
+ function badId(id) {
146
+ let got;
147
+ if (id === null) got = "null";
148
+ else if (id === void 0) got = "undefined";
149
+ else if (typeof id === "object") got = "an object";
150
+ else if (typeof id !== "number") got = `${/^[aeiou]/.test(typeof id) ? "an" : "a"} ${typeof id}`;
151
+ else if (!Number.isFinite(id)) got = "NaN or an infinity";
152
+ else if (id < 0) got = "a negative number";
153
+ else if (!Number.isInteger(id)) got = "a number with a fraction";
154
+ else got = `a number above ${U32_MAX} (ids are 32-bit)`;
155
+ const err = new ValidationError(`an id must be an integer from 0 to ${U32_MAX}; got ${got}`);
156
+ idErrors.add(err);
157
+ return err;
158
+ }
159
+ function splitId(id) {
160
+ if (!Number.isInteger(id) || id < 0 || id > U32_MAX) {
161
+ throw badId(id);
162
+ }
163
+ return { chunkKey: id >>> 16, remainder: id & LOW_16 };
164
+ }
165
+ var MAX_REMAINDER = CHUNK_COUNT - 1;
166
+ function joinId(chunkKey, remainder) {
167
+ return ((chunkKey & LOW_16) << 16 >>> 0) + (remainder & LOW_16);
113
168
  }
114
- function isKeyUnavailableError(err) {
115
- return isCloudRoaringError(err) && err.name === "KeyUnavailableError";
169
+
170
+ // src/core/check.ts
171
+ function shown(value) {
172
+ if (typeof value === "string") return JSON.stringify(value);
173
+ if (typeof value === "bigint") return `${value}n`;
174
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
175
+ return value === null ? "null" : typeof value;
176
+ }
177
+ function rangeOf(min, max, note) {
178
+ let range = "";
179
+ if (min !== void 0 && max !== void 0) range = ` from ${min} to ${max}`;
180
+ else if (min !== void 0) range = ` of at least ${min}`;
181
+ else if (max !== void 0) range = ` of at most ${max}`;
182
+ return note === void 0 ? range : `${range} (${note})`;
183
+ }
184
+ function requireInteger(value, bounds) {
185
+ const { min, max, what, note } = bounds;
186
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < min || max !== void 0 && value > max) {
187
+ throw new ValidationError(
188
+ `${what} must be an integer${rangeOf(min, max, note)}; got ${shown(value)}`
189
+ );
190
+ }
191
+ return value;
192
+ }
193
+ function requirePositiveInteger(value, what) {
194
+ return requireInteger(value, { min: 1, what });
195
+ }
196
+ function requireFiniteNumber(value, bounds) {
197
+ const { min, max, what, note } = bounds;
198
+ if (typeof value !== "number" || !Number.isFinite(value) || min !== void 0 && value < min || max !== void 0 && value > max) {
199
+ throw new ValidationError(
200
+ `${what} must be a finite number${rangeOf(min, max, note)}; got ${shown(value)}`
201
+ );
202
+ }
203
+ return value;
204
+ }
205
+
206
+ // src/core/concurrency.ts
207
+ async function mapWithConcurrency(items, limit, fn) {
208
+ requirePositiveInteger(limit, "concurrency limit");
209
+ const results = new Array(items.length);
210
+ let next = 0;
211
+ let failed = false;
212
+ let firstError;
213
+ const worker = async () => {
214
+ while (!failed) {
215
+ const i = next++;
216
+ if (i >= items.length) return;
217
+ try {
218
+ results[i] = await fn(items[i], i);
219
+ } catch (err) {
220
+ if (!failed) {
221
+ failed = true;
222
+ firstError = err;
223
+ }
224
+ return;
225
+ }
226
+ }
227
+ };
228
+ const workers = Array.from({ length: Math.min(limit, items.length) }, () => worker());
229
+ await Promise.all(workers);
230
+ if (failed) throw firstError;
231
+ return results;
232
+ }
233
+ function boundedAll(items, limit, fn) {
234
+ try {
235
+ requirePositiveInteger(limit, "concurrency limit");
236
+ } catch (err) {
237
+ return Promise.reject(err);
238
+ }
239
+ if (items.length > limit) return inWaves(items, limit, fn);
240
+ const calls = [];
241
+ for (const item of items) {
242
+ try {
243
+ calls.push(fn(item));
244
+ } catch (err) {
245
+ void Promise.allSettled(calls);
246
+ return Promise.reject(err);
247
+ }
248
+ }
249
+ return Promise.all(calls);
250
+ }
251
+ async function inWaves(items, limit, fn) {
252
+ const results = new Array(items.length);
253
+ let next = 0;
254
+ let failed = false;
255
+ const worker = async () => {
256
+ while (!failed && next < items.length) {
257
+ const i = next++;
258
+ try {
259
+ results[i] = await fn(items[i]);
260
+ } catch (err) {
261
+ failed = true;
262
+ throw err;
263
+ }
264
+ }
265
+ };
266
+ await Promise.all(Array.from({ length: limit }, () => worker()));
267
+ return results;
116
268
  }
117
269
 
118
270
  // src/core/name-codec.ts
119
271
  var KEY_SAFE = /[A-Za-z0-9._:-]/;
120
272
  var PATH_SAFE = /[A-Za-z0-9._-]/;
121
273
  var UTF8 = new TextEncoder();
274
+ var UTF8_LENIENT = new TextDecoder("utf-8", { fatal: false, ignoreBOM: true });
122
275
  function escapeChar(ch) {
123
276
  let out = "";
124
277
  for (const byte of UTF8.encode(ch)) {
@@ -136,17 +289,17 @@ function encodeWith(name, safe) {
136
289
  function decodePercent(encoded) {
137
290
  const bytes = [];
138
291
  for (let i = 0; i < encoded.length; ) {
139
- if (encoded[i] === "%" && i + 2 < encoded.length + 1) {
292
+ if (encoded[i] === "%" && i + 2 <= encoded.length) {
140
293
  const hex = encoded.slice(i + 1, i + 3);
141
294
  if (!/^[0-9A-Fa-f]{2}$/.test(hex)) return encoded;
142
295
  bytes.push(parseInt(hex, 16));
143
296
  i += 3;
144
297
  } else {
145
- for (const b of new TextEncoder().encode(encoded[i])) bytes.push(b);
298
+ for (const b of UTF8.encode(encoded[i])) bytes.push(b);
146
299
  i += 1;
147
300
  }
148
301
  }
149
- return new TextDecoder("utf-8", { fatal: false, ignoreBOM: true }).decode(new Uint8Array(bytes));
302
+ return UTF8_LENIENT.decode(new Uint8Array(bytes));
150
303
  }
151
304
  function encodeNameForKey(name) {
152
305
  return defuseDotComponent(encodeWith(name, KEY_SAFE));
@@ -206,7 +359,13 @@ function validatePart(value, field) {
206
359
  );
207
360
  }
208
361
  }
362
+ function requireRefObject(ref, hint = "") {
363
+ if (typeof ref !== "object" || ref === null) {
364
+ throw new ValidationError(`a segment ref is an object such as { segment: 'name' }${hint}`);
365
+ }
366
+ }
209
367
  function validateSegmentRef(ref) {
368
+ requireRefObject(ref);
210
369
  validatePart(ref.segment, "segment");
211
370
  if (ref.namespace !== void 0) validatePart(ref.namespace, "namespace");
212
371
  }
@@ -223,6 +382,7 @@ function validateUserNamespace(namespace) {
223
382
  }
224
383
  }
225
384
  function validateUserRef(ref) {
385
+ requireRefObject(ref, "; store.segment() takes the bare name");
226
386
  validatePart(ref.segment, "segment");
227
387
  if (ref.namespace !== void 0) validateUserNamespace(ref.namespace);
228
388
  }
@@ -234,39 +394,12 @@ function validateChunkKeyOrder(keys) {
234
394
  }
235
395
  function validateChunkRef(ref) {
236
396
  validateSegmentRef(ref);
237
- if (!Number.isInteger(ref.chunkKey) || ref.chunkKey < 0 || ref.chunkKey > CHUNK_KEY_MAX) {
238
- throw new ValidationError(`chunkKey must be an integer in [0, ${CHUNK_KEY_MAX}]`);
239
- }
397
+ validateChunkKey(ref.chunkKey);
240
398
  }
241
-
242
- // src/core/concurrency.ts
243
- async function mapWithConcurrency(items, limit, fn) {
244
- if (!Number.isInteger(limit) || limit < 1) {
245
- throw new ValidationError(`concurrency limit must be a positive integer; got ${limit}`);
399
+ function validateChunkKey(chunkKey) {
400
+ if (!(typeof chunkKey === "number" && chunkKey >= 0 && chunkKey <= CHUNK_KEY_MAX && (chunkKey | 0) === chunkKey)) {
401
+ throw new ValidationError(`chunkKey must be an integer in [0, ${CHUNK_KEY_MAX}]`);
246
402
  }
247
- const results = new Array(items.length);
248
- let next = 0;
249
- let failed = false;
250
- let firstError;
251
- const worker = async () => {
252
- while (!failed) {
253
- const i = next++;
254
- if (i >= items.length) return;
255
- try {
256
- results[i] = await fn(items[i], i);
257
- } catch (err) {
258
- if (!failed) {
259
- failed = true;
260
- firstError = err;
261
- }
262
- return;
263
- }
264
- }
265
- };
266
- const workers = Array.from({ length: Math.min(limit, items.length) }, () => worker());
267
- await Promise.all(workers);
268
- if (failed) throw firstError;
269
- return results;
270
403
  }
271
404
 
272
405
  // src/core/ports.ts
@@ -336,6 +469,97 @@ function pointerIdOf(row) {
336
469
  return pointerId;
337
470
  }
338
471
 
472
+ // src/core/crypto.ts
473
+ var UTF82 = new TextEncoder();
474
+ var AAD_VERSION = 1;
475
+ var SCOPE_CHUNK = 0;
476
+ var SCOPE_INDEX = 1;
477
+ var SCOPE_METADATA = 2;
478
+ var SCOPE_SUMMARY = 3;
479
+ function aadFor(ref, generation, scope) {
480
+ const ns = UTF82.encode(ref.namespace ?? "");
481
+ const seg = UTF82.encode(ref.segment);
482
+ const out = new Uint8Array(1 + 2 + ns.length + 2 + seg.length + 8 + 1 + 4);
483
+ const view = new DataView(out.buffer);
484
+ let o = 0;
485
+ out[o] = AAD_VERSION;
486
+ o += 1;
487
+ view.setUint16(o, ns.length, true);
488
+ o += 2;
489
+ out.set(ns, o);
490
+ o += ns.length;
491
+ view.setUint16(o, seg.length, true);
492
+ o += 2;
493
+ out.set(seg, o);
494
+ o += seg.length;
495
+ view.setBigUint64(o, BigInt(generation), true);
496
+ o += 8;
497
+ out[o] = scope === "index" ? SCOPE_INDEX : scope === "metadata" ? SCOPE_METADATA : scope === "summary" ? SCOPE_SUMMARY : SCOPE_CHUNK;
498
+ o += 1;
499
+ view.setUint32(o, typeof scope === "number" ? scope : 0, true);
500
+ return out;
501
+ }
502
+ var sameKeys = (a, b) => a !== void 0 && JSON.stringify(a) === JSON.stringify(b);
503
+
504
+ // src/core/retry.ts
505
+ var DEFAULT_RETRY_POLICY = {
506
+ maxAttempts: 4,
507
+ baseDelayMs: 50,
508
+ maxDelayMs: 2e3,
509
+ backoffFactor: 2,
510
+ jitter: "full"
511
+ };
512
+ function backoffDelayMs(policy, attempt) {
513
+ const raw = policy.baseDelayMs * policy.backoffFactor ** (attempt - 1);
514
+ return Math.min(policy.maxDelayMs, raw);
515
+ }
516
+ function applyJitter(policy, delayMs, rng) {
517
+ if (policy.jitter === "none") return delayMs;
518
+ return rng.next() * delayMs;
519
+ }
520
+ function jitteredWait(clock, rng, boundMs) {
521
+ const sleep = clock?.sleep;
522
+ return sleep === void 0 ? Promise.resolve() : sleep.call(clock, Math.floor((rng?.next() ?? 1) * boundMs));
523
+ }
524
+ function retryRead(op, retry) {
525
+ return retry === void 0 ? op() : withRetry(op, retry.policy ?? DEFAULT_RETRY_POLICY, retry);
526
+ }
527
+ function checkedRetryPolicy(policy) {
528
+ const { maxAttempts, baseDelayMs, maxDelayMs, backoffFactor, jitter } = policy;
529
+ requireInteger(maxAttempts, { min: 1, what: "retry.maxAttempts" });
530
+ requireFiniteNumber(baseDelayMs, { min: 0, what: "retry.baseDelayMs" });
531
+ requireFiniteNumber(maxDelayMs, {
532
+ min: baseDelayMs,
533
+ what: "retry.maxDelayMs",
534
+ note: "retry.baseDelayMs"
535
+ });
536
+ requireFiniteNumber(backoffFactor, { min: 1, what: "retry.backoffFactor" });
537
+ if (jitter !== "full" && jitter !== "none") {
538
+ throw new ValidationError(`retry.jitter must be 'full' or 'none'`);
539
+ }
540
+ return policy;
541
+ }
542
+ async function withRetry(op, policy, deps) {
543
+ const retryable = deps.isRetryable ?? isTransientError;
544
+ const attempts = requireInteger(policy.maxAttempts, { min: 1, what: "retry.maxAttempts" });
545
+ let lastErr;
546
+ for (let attempt = 1; attempt <= attempts; attempt++) {
547
+ try {
548
+ return await op();
549
+ } catch (err) {
550
+ lastErr = err;
551
+ if (attempt >= attempts || !retryable(err)) throw err;
552
+ const delayMs = applyJitter(policy, backoffDelayMs(policy, attempt), deps.rng);
553
+ try {
554
+ deps.onRetry?.({ attempt, delayMs, err });
555
+ } catch {
556
+ }
557
+ await deps.clock.sleep(delayMs);
558
+ }
559
+ }
560
+ throw lastErr;
561
+ }
562
+
339
563
  // src/core/token.ts
340
564
  var INCARNATION_TOKEN = /^([0-9a-f]{32})\.(0|[1-9]\d*)\.([0-9a-f]{16})$/;
341
565
  function incarnationOf(token) {
@@ -403,10 +627,7 @@ function leaseChurn(deps) {
403
627
  if (used >= LEASE_ONLY_RETRIES || !onlyLeasesDiffer(held, now, alsoIgnore)) return void 0;
404
628
  const bound = Math.min(CONFLICT_BASE_MS * 2 ** Math.min(used, 4), CONFLICT_CAP_MS);
405
629
  used += 1;
406
- const sleep = deps.clock?.sleep;
407
- if (sleep !== void 0) {
408
- await sleep.call(deps.clock, Math.floor((deps.rng?.next() ?? 1) * bound));
409
- }
630
+ await jitteredWait(deps.clock, deps.rng, bound);
410
631
  return { row: await reread() };
411
632
  }
412
633
  };
@@ -417,7 +638,7 @@ function listOf(entries) {
417
638
  (a, b) => a.generation !== b.generation ? a.generation - b.generation : a.holder < b.holder ? -1 : a.holder > b.holder ? 1 : 0
418
639
  );
419
640
  }
420
- var wait = (deps, bound) => deps.clock.sleep(Math.floor((deps.rng?.next() ?? 1) * bound));
641
+ var wait = (deps, bound) => jitteredWait(deps.clock, deps.rng, bound);
421
642
  var heldBy = (row, take) => (row?.leases ?? []).some(
422
643
  (e) => e.holder === take.holder && e.generation === take.generation && e.until === take.until
423
644
  );
@@ -480,7 +701,9 @@ async function takeLease(ref, deps, take) {
480
701
  }
481
702
  }
482
703
  if (unanswered !== void 0) throw unanswered;
483
- throw new WriteConflictError(`lease: contention taking a lease on segment "${ref.segment}"`);
704
+ throw new WriteConflictError(
705
+ `lease: contention taking a lease on segment "${ref.segment}"${CONTENTION_NEXT_STEP}`
706
+ );
484
707
  }
485
708
  async function releaseLease(ref, deps, holder) {
486
709
  let resends = 0;
@@ -514,7 +737,9 @@ async function releaseLease(ref, deps, holder) {
514
737
  }
515
738
  }
516
739
  if (unanswered !== void 0) throw unanswered;
517
- throw new WriteConflictError(`lease: contention releasing a lease on segment "${ref.segment}"`);
740
+ throw new WriteConflictError(
741
+ `lease: contention releasing a lease on segment "${ref.segment}"${CONTENTION_NEXT_STEP}`
742
+ );
518
743
  }
519
744
 
520
745
  // src/drivers/_shared/keys.ts
@@ -526,6 +751,44 @@ function namespacePathPart(namespace) {
526
751
  return namespace === void 0 ? DEFAULT_NAMESPACE : encodeNameForPath(namespace);
527
752
  }
528
753
 
754
+ // src/drivers/_shared/options.ts
755
+ function refuseUnknownOptions(name, options, keys, hint) {
756
+ if (options === null || typeof options !== "object") {
757
+ throw new ValidationError(
758
+ `${name} needs an options object \u2014 got ${options === null ? "null" : typeof options}`
759
+ );
760
+ }
761
+ const unknown = Object.keys(options).filter((k) => !keys.includes(k));
762
+ if (unknown.length > 0) {
763
+ const list = (ks) => ks.map((k) => `\`${k}\``).join(", ");
764
+ throw new ValidationError(
765
+ `${name} does not take ${list(unknown)}. It takes ${list(keys)}${hint === void 0 ? "" : `; ${hint}`}.`
766
+ );
767
+ }
768
+ }
769
+ function checkNow(name, now) {
770
+ if (now !== void 0 && typeof now !== "function") {
771
+ throw new ValidationError(`${name}: \`now\` must be a function returning epoch milliseconds`);
772
+ }
773
+ }
774
+ function requirePositiveInt(name, value) {
775
+ return value === void 0 ? void 0 : requirePositiveInteger(value, name);
776
+ }
777
+ function requireBool(name, value) {
778
+ if (value === void 0) return void 0;
779
+ if (typeof value !== "boolean") {
780
+ throw new ValidationError(`${name} must be a boolean; got ${shown2(value)}`);
781
+ }
782
+ return value;
783
+ }
784
+ function shown2(value) {
785
+ if (typeof value === "string") return JSON.stringify(value);
786
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
787
+ return String(value);
788
+ }
789
+ return value === null ? "null" : typeof value;
790
+ }
791
+
529
792
  // src/core/crbm/frame.ts
530
793
  var PREAMBLE_BYTES = 8;
531
794
  var FOOTER_BYTES = 104;
@@ -533,7 +796,9 @@ var FOOTER_BYTES = 104;
533
796
  // src/core/metadata.ts
534
797
  var MAX_METADATA_BYTES = 1024;
535
798
  var MAX_METADATA_KEY_BYTES = 128;
536
- var utf8Length = (s) => new TextEncoder().encode(s).length;
799
+ var UTF83 = new TextEncoder();
800
+ var UTF8_STRICT = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
801
+ var utf8Length = (s) => UTF83.encode(s).length;
537
802
  function canonicalMetadataJson(value, fail) {
538
803
  if (value === null || typeof value !== "object") {
539
804
  fail(`metadata must be a plain object (got ${value === null ? "null" : typeof value})`);
@@ -580,7 +845,7 @@ function canonicalMetadataJson(value, fail) {
580
845
  function metadataBytes(value, fail) {
581
846
  if (value === void 0) return void 0;
582
847
  const json = canonicalMetadataJson(value, fail);
583
- return json === "{}" ? void 0 : new TextEncoder().encode(json);
848
+ return json === "{}" ? void 0 : UTF83.encode(json);
584
849
  }
585
850
  function copiedMetadata(value, fail) {
586
851
  const bytes = metadataBytes(value, fail);
@@ -592,7 +857,7 @@ function metadataFromBytes(bytes, fail) {
592
857
  }
593
858
  let text;
594
859
  try {
595
- text = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
860
+ text = UTF8_STRICT.decode(bytes);
596
861
  } catch {
597
862
  fail("metadata is not valid UTF-8");
598
863
  }
@@ -621,6 +886,13 @@ function fingerprintParts(fingerprint) {
621
886
  if (footerCrc > 4294967295) return void 0;
622
887
  return { size, footerCrc };
623
888
  }
889
+ function footerCrcOf(fingerprint) {
890
+ const parts = fingerprintParts(fingerprint);
891
+ if (parts === void 0) {
892
+ throw new IntegrityError(`not an object's fingerprint: ${String(fingerprint).slice(0, 64)}`);
893
+ }
894
+ return parts.footerCrc;
895
+ }
624
896
  function compactFingerprint(fingerprint) {
625
897
  const colon = fingerprint.indexOf(":");
626
898
  const size = Number(fingerprint.slice(0, colon));
@@ -628,25 +900,352 @@ function compactFingerprint(fingerprint) {
628
900
  return `${size.toString(36)}.${crc.toString(36)}`;
629
901
  }
630
902
 
631
- // src/core/kept-generations.ts
903
+ // src/core/summary.ts
904
+ var NONCE_BYTES = 12;
905
+ var TAG_BYTES = 16;
906
+ var COUNT_BYTES = 8;
907
+ var SIZE_BYTES = 8;
908
+ var CRC_BYTES = 4;
909
+ var TAIL_BYTES = 4;
910
+ var FIXED_BYTES = COUNT_BYTES + SIZE_BYTES + CRC_BYTES + TAIL_BYTES;
911
+ var MAX_SUMMARY_CARDINALITY = U32_MAX + 1;
912
+ var MAX_TAIL_BYTES = 8392820;
913
+ function tailBytesFit(tailBytes, size) {
914
+ return typeof tailBytes === "number" && Number.isInteger(tailBytes) && tailBytes >= FOOTER_BYTES && tailBytes <= size - PREAMBLE_BYTES && tailBytes <= MAX_TAIL_BYTES;
915
+ }
916
+ function sizeOfFingerprint(fingerprint) {
917
+ const parts = fingerprintParts(fingerprint);
918
+ if (parts === void 0)
919
+ throw new IntegrityError("registry record summary: a malformed fingerprint");
920
+ return parts.size;
921
+ }
922
+ var BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
923
+ function toBase64(bytes) {
924
+ let out = "";
925
+ for (let i = 0; i < bytes.length; i += 3) {
926
+ const a = bytes[i];
927
+ const b = bytes[i + 1];
928
+ const c = bytes[i + 2];
929
+ out += BASE64[a >> 2] + BASE64[(a & 3) << 4 | (b ?? 0) >> 4];
930
+ out += b === void 0 ? "=" : BASE64[(b & 15) << 2 | (c ?? 0) >> 6];
931
+ out += c === void 0 ? "=" : BASE64[c & 63];
932
+ }
933
+ return out;
934
+ }
935
+ function fromBase64(text) {
936
+ if (text.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(text)) return void 0;
937
+ const pad = text.endsWith("==") ? 2 : text.endsWith("=") ? 1 : 0;
938
+ const out = new Uint8Array(text.length / 4 * 3 - pad);
939
+ let o = 0;
940
+ for (let i = 0; i < text.length; i += 4) {
941
+ const n = BASE64.indexOf(text[i]) << 18 | BASE64.indexOf(text[i + 1]) << 12 | Math.max(BASE64.indexOf(text[i + 2]), 0) << 6 | Math.max(BASE64.indexOf(text[i + 3]), 0);
942
+ if (o < out.length) out[o++] = n >> 16 & 255;
943
+ if (o < out.length) out[o++] = n >> 8 & 255;
944
+ if (o < out.length) out[o++] = n & 255;
945
+ }
946
+ return out;
947
+ }
948
+ function clearSummary(generation, cardinality, fingerprint, tailBytes, metadata) {
949
+ return metadata === void 0 || Object.keys(metadata).length === 0 ? { generation, cardinality, fingerprint, tailBytes } : { generation, cardinality, fingerprint, tailBytes, metadata };
950
+ }
951
+ function sealSummary(aead, ref, generation, cardinality, fingerprint, tailBytes, metadata) {
952
+ const meta = metadataBytes(metadata, (message) => {
953
+ throw new IntegrityError(`summary metadata: ${message}`);
954
+ });
955
+ const parts = fingerprintParts(fingerprint);
956
+ if (parts === void 0) throw new IntegrityError("summary: a malformed fingerprint");
957
+ const plain = new Uint8Array(FIXED_BYTES + (meta?.length ?? 0));
958
+ const view = new DataView(plain.buffer);
959
+ view.setBigUint64(0, BigInt(cardinality), true);
960
+ view.setBigUint64(COUNT_BYTES, BigInt(parts.size), true);
961
+ view.setUint32(COUNT_BYTES + SIZE_BYTES, parts.footerCrc, true);
962
+ view.setUint32(COUNT_BYTES + SIZE_BYTES + CRC_BYTES, tailBytes, true);
963
+ if (meta !== void 0) plain.set(meta, FIXED_BYTES);
964
+ const { nonce, ciphertext, tag } = aead.seal(plain, aadFor(ref, generation, "summary"));
965
+ const framed = new Uint8Array(nonce.length + ciphertext.length + tag.length);
966
+ framed.set(nonce, 0);
967
+ framed.set(ciphertext, nonce.length);
968
+ framed.set(tag, nonce.length + ciphertext.length);
969
+ return { generation, sealed: toBase64(framed) };
970
+ }
971
+ function openSummary(aead, ref, summary) {
972
+ const fail = (message) => {
973
+ throw new IntegrityError(`registry record summary: ${message}`);
974
+ };
975
+ const framed = fromBase64(summary.sealed);
976
+ const framing = NONCE_BYTES + TAG_BYTES;
977
+ if (framed === void 0 || framed.length < framing + FIXED_BYTES || framed.length > framing + FIXED_BYTES + MAX_METADATA_BYTES) {
978
+ fail(
979
+ "the sealed summary is not a nonce, a count, a fingerprint, a tail size, up to 1 KiB of metadata and a tag"
980
+ );
981
+ }
982
+ const bytes = framed;
983
+ let plain;
984
+ try {
985
+ plain = aead.open(
986
+ {
987
+ nonce: bytes.subarray(0, NONCE_BYTES),
988
+ ciphertext: bytes.subarray(NONCE_BYTES, bytes.length - TAG_BYTES),
989
+ tag: bytes.subarray(bytes.length - TAG_BYTES)
990
+ },
991
+ aadFor(ref, summary.generation, "summary")
992
+ );
993
+ } catch (err) {
994
+ if (!isIntegrityError(err)) throw err;
995
+ return fail(
996
+ "the sealed summary does not open: tampered bytes, or a summary moved from another generation"
997
+ );
998
+ }
999
+ if (plain.length < FIXED_BYTES)
1000
+ fail("the sealed summary is shorter than its count, fingerprint and tail size");
1001
+ const view = new DataView(plain.buffer, plain.byteOffset, plain.byteLength);
1002
+ const count = view.getBigUint64(0, true);
1003
+ if (count > BigInt(MAX_SUMMARY_CARDINALITY)) fail(`cardinality ${count} is outside 0 to 2^32`);
1004
+ const size = view.getBigUint64(COUNT_BYTES, true);
1005
+ const fingerprint = fingerprintFor(Number(size), view.getUint32(COUNT_BYTES + SIZE_BYTES, true));
1006
+ if (size > BigInt(Number.MAX_SAFE_INTEGER) || fingerprintParts(fingerprint) === void 0) {
1007
+ fail(`the object size ${size} is not one a .crbm object can have`);
1008
+ }
1009
+ const tailBytes = view.getUint32(COUNT_BYTES + SIZE_BYTES + CRC_BYTES, true);
1010
+ if (!tailBytesFit(tailBytes, Number(size))) {
1011
+ fail(`the tail size ${tailBytes} is not one a .crbm object of ${size} bytes can have`);
1012
+ }
1013
+ const metadata = plain.length === FIXED_BYTES ? void 0 : metadataFromBytes(plain.subarray(FIXED_BYTES), fail);
1014
+ return { cardinality: Number(count), metadata, fingerprint, tailBytes };
1015
+ }
1016
+ function usableSummary(ref, row, aead) {
1017
+ const summary = row.summary;
1018
+ if (summary === void 0 || row.status !== "active" || summary.generation !== row.currentGen) {
1019
+ return void 0;
1020
+ }
1021
+ const keyed = row.wrappedDeks !== void 0 && row.wrappedDeks.length > 0;
1022
+ if ("sealed" in summary) {
1023
+ if (!keyed || aead === void 0) return void 0;
1024
+ try {
1025
+ return openSummary(aead, ref, summary);
1026
+ } catch (err) {
1027
+ if (isIntegrityError(err)) return void 0;
1028
+ throw err;
1029
+ }
1030
+ }
1031
+ if (keyed) return void 0;
1032
+ const parts = fingerprintParts(summary.fingerprint);
1033
+ if (parts === void 0 || !tailBytesFit(summary.tailBytes, parts.size)) return void 0;
1034
+ return {
1035
+ cardinality: summary.cardinality,
1036
+ metadata: summary.metadata,
1037
+ fingerprint: summary.fingerprint,
1038
+ tailBytes: summary.tailBytes
1039
+ };
1040
+ }
1041
+ function canonicalOrNone(metadata) {
1042
+ if (metadata === void 0) return void 0;
1043
+ const json = canonicalMetadataJson(metadata, (message) => {
1044
+ throw new IntegrityError(`metadata: ${message}`);
1045
+ });
1046
+ return json === "{}" ? void 0 : json;
1047
+ }
1048
+ function summaryAgrees(summary, object) {
1049
+ return summary.cardinality === object.cardinality && canonicalOrNone(summary.metadata) === canonicalOrNone(object.metadata) && (summary.tailBytes === void 0 || object.tailBytes === void 0 || summary.tailBytes === object.tailBytes);
1050
+ }
1051
+ function metadataToCarry(object, authenticated) {
1052
+ const empty = (metadata) => metadata === void 0 || Object.keys(metadata).length === 0;
1053
+ return empty(object) && authenticated !== void 0 && !empty(authenticated.metadata) ? authenticated.metadata : object;
1054
+ }
1055
+ function summaryOf(ref, generation, description, aead) {
1056
+ const { cardinality, fingerprint, tailBytes, metadata } = description;
1057
+ if (tailBytes > MAX_TAIL_BYTES) return void 0;
1058
+ return aead === void 0 ? clearSummary(generation, cardinality, fingerprint, tailBytes, metadata) : sealSummary(aead, ref, generation, cardinality, fingerprint, tailBytes, metadata);
1059
+ }
1060
+ function ownSummary(ref, generation, description, aead) {
1061
+ const summary = summaryOf(ref, generation, description, aead);
1062
+ if (summary === void 0) {
1063
+ throw new IntegrityError(
1064
+ `the object written for generation ${generation} of "${ref.segment}" has a tail of ${description.tailBytes} bytes, above the ${MAX_TAIL_BYTES} a summary records`
1065
+ );
1066
+ }
1067
+ return summary;
1068
+ }
1069
+
1070
+ // src/core/known-generations-shape.ts
1071
+ var ENTRY_KEYS = ["generation", "footerCrc"];
1072
+ function knownGensShapeFault(value, max, show = String) {
1073
+ if (value === void 0) return void 0;
1074
+ if (!Array.isArray(value)) return "must be an array";
1075
+ const list = value;
1076
+ if (list.length > max) return `has ${list.length} entries, cap ${max}`;
1077
+ const seen = /* @__PURE__ */ new Set();
1078
+ for (const e of list) {
1079
+ if (typeof e !== "object" || e === null || Array.isArray(e))
1080
+ return "an entry must be an object";
1081
+ const entry = e;
1082
+ const extra = Object.keys(entry).filter((k) => !ENTRY_KEYS.includes(k));
1083
+ if (extra.length > 0) {
1084
+ return `an entry carries an undeclared field (${show(extra.join(", "))})`;
1085
+ }
1086
+ const { generation, footerCrc } = entry;
1087
+ if (!Number.isSafeInteger(generation) || generation < 0) {
1088
+ return `a generation must be a non-negative safe integer (got ${show(generation)})`;
1089
+ }
1090
+ if (!Number.isInteger(footerCrc) || footerCrc < 0 || footerCrc > 4294967295) {
1091
+ return `a footerCrc must be an integer from 0 to 2^32 - 1 (got ${show(footerCrc)})`;
1092
+ }
1093
+ if (seen.has(generation)) return `generation ${String(generation)} is repeated`;
1094
+ seen.add(generation);
1095
+ }
1096
+ return void 0;
1097
+ }
1098
+
1099
+ // src/core/order.ts
1100
+ var ascending = (a, b) => a - b;
1101
+
1102
+ // src/core/known-generations.ts
632
1103
  var MAX_KEPT_GENERATIONS = 64;
633
- var MAX_STORED_KEPT_GENERATIONS = 256;
634
- function usableKeptGens(record) {
635
- const list = record?.keptGens;
636
- if (record === null || list === void 0 || record.currentGen === null) return void 0;
637
- for (const g of list) if (g >= record.currentGen) return void 0;
638
- return list;
639
- }
640
- function nextKept(prev, generation, keep) {
641
- const none = { list: void 0, evict: [] };
642
- if (keep === void 0 || keep > MAX_KEPT_GENERATIONS) return none;
643
- if (generation === 0) return { list: [], evict: [] };
644
- const previous = prev?.currentGen ?? null;
645
- const known = usableKeptGens(prev);
646
- if (previous === null || known === void 0) return none;
647
- const all = [...known, previous];
648
- const cut = Math.max(0, all.length - keep);
649
- return { list: all.slice(cut), evict: all.slice(0, cut) };
1104
+ var KNOWN_FILL = 128;
1105
+ var MAX_KNOWN_GENERATIONS = 192;
1106
+ var MAX_STORED_KNOWN_GENERATIONS = 384;
1107
+ function usableKnownGens(record) {
1108
+ const known = record?.knownGens;
1109
+ if (record === null || known === void 0 || known.length === 0 || record.status !== "active" || record.currentGen === null) {
1110
+ return void 0;
1111
+ }
1112
+ const last = known[known.length - 1];
1113
+ if (last.generation !== record.currentGen) return void 0;
1114
+ const summary = record.summary;
1115
+ if (summary !== void 0 && summary.generation === record.currentGen && "fingerprint" in summary && fingerprintParts(summary.fingerprint)?.footerCrc !== last.footerCrc) {
1116
+ return void 0;
1117
+ }
1118
+ return known;
1119
+ }
1120
+ function windowOf(record) {
1121
+ const known = usableKnownGens(record);
1122
+ if (known === void 0) return void 0;
1123
+ const pointer = record.currentGen;
1124
+ return known.filter((e) => e.generation < pointer).map((e) => e.generation);
1125
+ }
1126
+ function carriesWindow(record, leased) {
1127
+ const below = windowOf(record);
1128
+ return below !== void 0 && below.filter((g) => !leased.has(g)).length <= MAX_KEPT_GENERATIONS;
1129
+ }
1130
+ var KNOWN_SPAN = 127;
1131
+ function keptWindow(below, superseded, keep) {
1132
+ const oldestFirst = [...below].sort(ascending);
1133
+ const first = keep > 0 && superseded !== void 0 && oldestFirst.includes(superseded) ? superseded : void 0;
1134
+ const held = new Set(first === void 0 ? [] : [first]);
1135
+ for (let i = oldestFirst.length - 1, room = keep - held.size; i >= 0 && room > 0; i--) {
1136
+ if (oldestFirst[i] === first) continue;
1137
+ held.add(oldestFirst[i]);
1138
+ room -= 1;
1139
+ }
1140
+ return {
1141
+ kept: oldestFirst.filter((g) => held.has(g)),
1142
+ evict: oldestFirst.filter((g) => !held.has(g))
1143
+ };
1144
+ }
1145
+ function capKnown(known, protect, leased = /* @__PURE__ */ new Set()) {
1146
+ const keep = /* @__PURE__ */ new Set();
1147
+ for (const e of known) if (protect.has(e.generation)) keep.add(e.generation);
1148
+ for (let i = known.length - 1; i >= 0 && keep.size < MAX_KNOWN_GENERATIONS; i--) {
1149
+ if (leased.has(known[i].generation)) keep.add(known[i].generation);
1150
+ }
1151
+ for (let i = known.length - 1; i >= 0 && keep.size < KNOWN_FILL; i--)
1152
+ keep.add(known[i].generation);
1153
+ return known.filter((e) => keep.has(e.generation));
1154
+ }
1155
+ function capInsert(known, protect, leased) {
1156
+ const keep = /* @__PURE__ */ new Set();
1157
+ let unleased = 0;
1158
+ for (const e of known) {
1159
+ if (!protect.has(e.generation)) continue;
1160
+ keep.add(e.generation);
1161
+ if (!leased.has(e.generation)) unleased += 1;
1162
+ }
1163
+ for (let i = known.length - 1; i >= 0 && unleased < KNOWN_FILL; i--) {
1164
+ const g = known[i].generation;
1165
+ if (keep.has(g) || leased.has(g)) continue;
1166
+ keep.add(g);
1167
+ unleased += 1;
1168
+ }
1169
+ for (let i = known.length - 1; i >= 0 && keep.size < MAX_KNOWN_GENERATIONS; i--) {
1170
+ if (leased.has(known[i].generation)) keep.add(known[i].generation);
1171
+ }
1172
+ return known.filter((e) => keep.has(e.generation));
1173
+ }
1174
+ function makeCurrent(known, entry) {
1175
+ return [...known.filter((e) => e.generation !== entry.generation), entry];
1176
+ }
1177
+ function nextKnown(prev, generation, footerCrc, keep, options = {}) {
1178
+ const self = { generation, footerCrc };
1179
+ const clears = options.clearsLeases === true;
1180
+ const byName = keep !== void 0 && keep <= MAX_KEPT_GENERATIONS;
1181
+ if (options.seed !== void 0 && !clears) {
1182
+ const seed = options.seed.filter((e) => e.generation !== generation);
1183
+ const protect = /* @__PURE__ */ new Set([...seed.map((e) => e.generation), generation]);
1184
+ return {
1185
+ known: capKnown([...seed, self], protect, options.leased ?? /* @__PURE__ */ new Set()),
1186
+ list: void 0,
1187
+ evict: []
1188
+ };
1189
+ }
1190
+ const found = usableKnownGens(prev);
1191
+ if (found === void 0 && prev !== null && prev.currentGen !== null) {
1192
+ return { known: [self], list: void 0, evict: [] };
1193
+ }
1194
+ const all = found ?? [];
1195
+ const below = all.filter((e) => e.generation < generation);
1196
+ const above = clears ? [] : all.filter((e) => e.generation > generation);
1197
+ const span = clears ? 0 : Math.min(keep ?? Number.MAX_SAFE_INTEGER, KNOWN_SPAN);
1198
+ const vouch = new Set((span === 0 ? [] : below.slice(-span)).map((e) => e.generation));
1199
+ const leased = clears ? /* @__PURE__ */ new Set() : options.leased ?? /* @__PURE__ */ new Set();
1200
+ const held = below.filter((e) => !vouch.has(e.generation) && leased.has(e.generation));
1201
+ const chosen = /* @__PURE__ */ new Set([
1202
+ ...vouch,
1203
+ ...held.map((e) => e.generation),
1204
+ ...above.map((e) => e.generation)
1205
+ ]);
1206
+ const known = capKnown(
1207
+ [...all.filter((e) => chosen.has(e.generation)), self],
1208
+ /* @__PURE__ */ new Set([...vouch, generation]),
1209
+ leased
1210
+ );
1211
+ const wide = !clears && found !== void 0 && !carriesWindow(prev, leased);
1212
+ if (!byName || wide) return { known, list: void 0, evict: [] };
1213
+ const named = new Set(known.map((e) => e.generation));
1214
+ return {
1215
+ known,
1216
+ list: known.filter((e) => e.generation < generation).map((e) => e.generation).sort(ascending),
1217
+ evict: below.filter((e) => !named.has(e.generation)).map((e) => e.generation).sort(ascending)
1218
+ };
1219
+ }
1220
+ function rollbackKnown(prev, target) {
1221
+ const found = usableKnownGens(prev);
1222
+ if (found === void 0) return void 0;
1223
+ const moved = makeCurrent(found, target);
1224
+ const inserted = !found.some((e) => e.generation === target.generation);
1225
+ if (!inserted && moved.length <= MAX_KNOWN_GENERATIONS) return moved;
1226
+ return capInsert(moved, /* @__PURE__ */ new Set([target.generation, prev.currentGen]), leasedByRow(prev));
1227
+ }
1228
+ function pruneKnown(record, doomed) {
1229
+ const found = usableKnownGens(record);
1230
+ if (found === void 0) return void 0;
1231
+ const rest = found.filter((e) => e.generation === record.currentGen || !doomed.has(e.generation));
1232
+ if (rest.length <= MAX_KNOWN_GENERATIONS) return rest;
1233
+ return capKnown(rest, /* @__PURE__ */ new Set([record.currentGen]), leasedByRow(record));
1234
+ }
1235
+ function describingKnown(record, movedTo) {
1236
+ const found = usableKnownGens(record);
1237
+ if (found === void 0 || found.length <= MAX_KNOWN_GENERATIONS) return found;
1238
+ return capKnown(found, /* @__PURE__ */ new Set([record.currentGen, movedTo]), leasedByRow(record));
1239
+ }
1240
+ function isFullLedger(known) {
1241
+ return known.length >= KNOWN_FILL;
1242
+ }
1243
+ function fallbackBound(record, known) {
1244
+ const leased = leasedByRow(record);
1245
+ return (known.find((e) => !leased.has(e.generation)) ?? known[0]).generation;
1246
+ }
1247
+ function leasedByRow(record) {
1248
+ return new Set((record.leases ?? []).map((e) => e.generation));
650
1249
  }
651
1250
 
652
1251
  // src/drivers/_shared/registry.ts
@@ -664,7 +1263,7 @@ var RECORD_FIELDS = [
664
1263
  "createdAt",
665
1264
  "updatedAt",
666
1265
  "token",
667
- "keptGens",
1266
+ "knownGens",
668
1267
  "leases",
669
1268
  "pointerId"
670
1269
  ];
@@ -673,10 +1272,9 @@ var ENVELOPE_FIELDS = ["schemaVersion", "deleted", "record"];
673
1272
  var MAX_GOVERNANCE_BYTES = 64 * 1024;
674
1273
  var MAX_WRAPPED_DEKS = 8;
675
1274
  var MAX_WRAPPED_DEK_BYTES = 4 * 1024;
676
- var MAX_SUMMARY_CARDINALITY = 2 ** 32;
677
- var SEALED_SUMMARY_MIN_BYTES = 12 + 8 + 8 + 4 + 16;
1275
+ var SEALED_SUMMARY_MIN_BYTES = 12 + 8 + 8 + 4 + 4 + 16;
678
1276
  var SEALED_SUMMARY_MAX_BYTES = SEALED_SUMMARY_MIN_BYTES + MAX_METADATA_BYTES;
679
- var BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
1277
+ var BASE642 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
680
1278
  function validateGeneration(gen) {
681
1279
  if (gen === null) return;
682
1280
  if (!Number.isSafeInteger(gen) || gen < 0) {
@@ -716,10 +1314,10 @@ function validateGovernance(meta, field) {
716
1314
  throw new ValidationError(`${field} is ${json.length}B, exceeds cap ${MAX_GOVERNANCE_BYTES}B`);
717
1315
  }
718
1316
  }
719
- function validateWrappedDeks(value, isStored) {
1317
+ function validateWrappedDeks(value, origin) {
720
1318
  if (value === void 0) return;
721
1319
  const fail = (msg) => {
722
- throw isStored ? new IntegrityError(msg) : new ValidationError(msg);
1320
+ throw origin === "stored" ? new IntegrityError(msg) : new ValidationError(msg);
723
1321
  };
724
1322
  if (!Array.isArray(value)) fail("wrappedDeks must be an array");
725
1323
  const list = value;
@@ -744,11 +1342,11 @@ function base64PadBitsAreZero(text) {
744
1342
  const last = BASE64_ALPHABET.indexOf(text[text.length - pad - 1]);
745
1343
  return pad === 2 ? (last & 15) === 0 : (last & 3) === 0;
746
1344
  }
747
- function validateSummary(value, isStored, ctx) {
1345
+ function validateSummary(value, origin, ctx) {
748
1346
  if (value === void 0) return void 0;
749
1347
  const fail = (msg) => {
750
1348
  const where = ctx === void 0 ? "" : `: ${ctx}`;
751
- throw isStored ? new IntegrityError(`registry record summary: ${msg}${where}`) : new ValidationError(`summary: ${msg}${where}`);
1349
+ throw origin === "stored" ? new IntegrityError(`registry record summary: ${msg}${where}`) : new ValidationError(`summary: ${msg}${where}`);
752
1350
  };
753
1351
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
754
1352
  fail(
@@ -758,7 +1356,7 @@ function validateSummary(value, isStored, ctx) {
758
1356
  const s = value;
759
1357
  const keys = Object.keys(s);
760
1358
  const isSealed = keys.includes("sealed");
761
- const allowed = isSealed ? ["generation", "sealed"] : ["generation", "cardinality", "fingerprint", "metadata"];
1359
+ const allowed = isSealed ? ["generation", "sealed"] : ["generation", "cardinality", "fingerprint", "tailBytes", "metadata"];
762
1360
  const extra = keys.filter((k) => !allowed.includes(k));
763
1361
  if (extra.length > 0) {
764
1362
  fail(
@@ -771,14 +1369,14 @@ function validateSummary(value, isStored, ctx) {
771
1369
  }
772
1370
  if (isSealed) {
773
1371
  const sealed = s.sealed;
774
- if (typeof sealed !== "string" || !BASE64.test(sealed) || !base64PadBitsAreZero(sealed)) {
1372
+ if (typeof sealed !== "string" || !BASE642.test(sealed) || !base64PadBitsAreZero(sealed)) {
775
1373
  fail("sealed must be canonical, padded standard base64");
776
1374
  }
777
1375
  const text = sealed;
778
1376
  const bytes = text.length / 4 * 3 - (text.endsWith("==") ? 2 : text.endsWith("=") ? 1 : 0);
779
1377
  if (bytes < SEALED_SUMMARY_MIN_BYTES || bytes > SEALED_SUMMARY_MAX_BYTES) {
780
1378
  fail(
781
- `sealed is ${bytes}B, outside ${SEALED_SUMMARY_MIN_BYTES}..${SEALED_SUMMARY_MAX_BYTES}B (a nonce, a fixed-width count and fingerprint, a tag, and metadata up to its cap)`
1379
+ `sealed is ${bytes}B, outside ${SEALED_SUMMARY_MIN_BYTES}..${SEALED_SUMMARY_MAX_BYTES}B (a nonce, a fixed-width count, fingerprint and tail size, a tag, and metadata up to its cap)`
782
1380
  );
783
1381
  }
784
1382
  return Object.freeze({ generation, sealed: text });
@@ -788,26 +1386,35 @@ function validateSummary(value, isStored, ctx) {
788
1386
  fail(`cardinality must be an integer from 0 to 2^32 (got ${String(n)})`);
789
1387
  }
790
1388
  const fingerprint = s.fingerprint;
791
- if (fingerprintParts(fingerprint) === void 0) {
1389
+ const parts = fingerprintParts(fingerprint);
1390
+ if (parts === void 0) {
1391
+ fail(
1392
+ `fingerprint must be an object's, <size>:<footer checksum> in decimal (got ${shown3(JSON.stringify(fingerprint))})`
1393
+ );
1394
+ }
1395
+ const tailBytes = s.tailBytes;
1396
+ if (!tailBytesFit(tailBytes, parts.size)) {
792
1397
  fail(
793
- `fingerprint must be an object's, <size>:<footer checksum> in decimal (got ${shown(JSON.stringify(fingerprint))})`
1398
+ `tailBytes must be an integer from the footer's size to the object's size less its preamble, and at most the cap of one open's tail read (got ${shown3(JSON.stringify(tailBytes))} for an object of ${parts.size} bytes)`
794
1399
  );
795
1400
  }
796
1401
  if (s.metadata === void 0) {
797
1402
  return Object.freeze({
798
1403
  generation,
799
1404
  cardinality: n,
800
- fingerprint
1405
+ fingerprint,
1406
+ tailBytes
801
1407
  });
802
1408
  }
803
1409
  const canonical = canonicalMetadataJson(s.metadata, fail);
804
1410
  if (canonical === "{}") fail("metadata is empty; a generation without metadata carries none");
805
- if (isStored) return s;
1411
+ if (origin === "stored") return s;
806
1412
  const metadata = Object.freeze(JSON.parse(canonical));
807
1413
  return Object.freeze({
808
1414
  generation,
809
1415
  cardinality: n,
810
1416
  fingerprint,
1417
+ tailBytes,
811
1418
  metadata
812
1419
  });
813
1420
  }
@@ -828,33 +1435,30 @@ function validateSummaryKeys(summary, wrappedDeks) {
828
1435
  );
829
1436
  }
830
1437
  }
831
- function validateKeptGens(value, isStored, ctx) {
1438
+ function validateKnownGens(value, origin, ctx) {
832
1439
  if (value === void 0) return void 0;
833
1440
  const fail = (msg) => {
834
1441
  const where = ctx === void 0 ? "" : `: ${ctx}`;
835
- throw isStored ? new IntegrityError(`registry record keptGens: ${msg}${where}`) : new ValidationError(`keptGens: ${msg}`);
1442
+ throw origin === "stored" ? new IntegrityError(`registry record knownGens: ${msg}${where}`) : new ValidationError(`knownGens: ${msg}`);
836
1443
  };
837
- if (!Array.isArray(value)) fail("must be an array");
838
- const list = value;
839
- const max = isStored ? MAX_STORED_KEPT_GENERATIONS : MAX_KEPT_GENERATIONS;
840
- if (list.length > max) fail(`has ${list.length} entries, cap ${max}`);
841
- let last = -1;
842
- for (const g of list) {
843
- if (!Number.isSafeInteger(g) || g < 0) {
844
- fail(`entries must be non-negative safe integers (got ${String(g)})`);
845
- }
846
- if (g <= last) fail("entries must be strictly ascending");
847
- last = g;
1444
+ const max = origin === "stored" ? MAX_STORED_KNOWN_GENERATIONS : MAX_KNOWN_GENERATIONS;
1445
+ const fault = knownGensShapeFault(value, max, shown3);
1446
+ if (fault !== void 0) fail(fault);
1447
+ const out = [];
1448
+ for (const entry of value) {
1449
+ out.push(
1450
+ origin === "stored" ? entry : Object.freeze({ generation: entry.generation, footerCrc: entry.footerCrc })
1451
+ );
848
1452
  }
849
- return isStored ? list : Object.freeze([...list]);
1453
+ return origin === "stored" ? out : Object.freeze(out);
850
1454
  }
851
1455
  var HOLDER = /^[0-9a-f]{16}$/;
852
1456
  var LEASE_ENTRY_KEYS = ["holder", "generation", "until"];
853
- function validateLeases(value, isStored, ctx) {
1457
+ function validateLeases(value, origin, ctx) {
854
1458
  if (value === void 0) return void 0;
855
1459
  const fail = (msg) => {
856
1460
  const where = ctx === void 0 ? "" : `: ${ctx}`;
857
- throw isStored ? new IntegrityError(`registry record leases: ${msg}${where}`) : new ValidationError(`leases: ${msg}`);
1461
+ throw origin === "stored" ? new IntegrityError(`registry record leases: ${msg}${where}`) : new ValidationError(`leases: ${msg}`);
858
1462
  };
859
1463
  if (!Array.isArray(value)) fail("must be an array");
860
1464
  const list = value;
@@ -882,14 +1486,14 @@ function validateLeases(value, isStored, ctx) {
882
1486
  }
883
1487
  }
884
1488
  out.push(
885
- isStored ? entry : Object.freeze({
1489
+ origin === "stored" ? entry : Object.freeze({
886
1490
  holder,
887
1491
  generation,
888
1492
  until
889
1493
  })
890
1494
  );
891
1495
  }
892
- if (isStored) return out;
1496
+ if (origin === "stored") return out;
893
1497
  return out.length === 0 ? void 0 : Object.freeze(out);
894
1498
  }
895
1499
  function validateLeasesPointer(leases, currentGen) {
@@ -897,17 +1501,20 @@ function validateLeasesPointer(leases, currentGen) {
897
1501
  throw new ValidationError("leases: a row with no pointer has no generation to lease");
898
1502
  }
899
1503
  }
900
- function validateKeptGensBelow(keptGens, currentGen) {
901
- if (keptGens === void 0) return;
1504
+ function validateKnownGensFit(knownGens, currentGen, summary) {
1505
+ if (knownGens === void 0) return;
902
1506
  if (currentGen === null) {
1507
+ throw new ValidationError("knownGens: a row with no pointer has no generation to know");
1508
+ }
1509
+ const last = knownGens[knownGens.length - 1];
1510
+ if (last === void 0 || last.generation !== currentGen) {
903
1511
  throw new ValidationError(
904
- "keptGens: a row with no pointer has no generations below it to keep"
1512
+ `knownGens must end with the row's pointer ${currentGen}, in the order the generations were made current (its last entry is ${last === void 0 ? "missing" : last.generation})`
905
1513
  );
906
1514
  }
907
- const top = keptGens[keptGens.length - 1];
908
- if (top !== void 0 && top >= currentGen) {
1515
+ if (summary !== void 0 && summary.generation === currentGen && "fingerprint" in summary && fingerprintParts(summary.fingerprint)?.footerCrc !== last.footerCrc) {
909
1516
  throw new ValidationError(
910
- `keptGens names generation ${top}, which is not below the row's pointer ${currentGen}`
1517
+ `knownGens records footerCrc ${last.footerCrc} for generation ${currentGen}, but the summary's fingerprint ${summary.fingerprint} carries another: both describe the one current object`
911
1518
  );
912
1519
  }
913
1520
  }
@@ -919,45 +1526,52 @@ function refuseDriverManaged(write, what) {
919
1526
  );
920
1527
  }
921
1528
  }
1529
+ function validateKeyId(keyId) {
1530
+ if (keyId !== void 0 && (typeof keyId !== "string" || keyId.length === 0)) {
1531
+ throw new ValidationError("keyId must be a non-empty string, or absent");
1532
+ }
1533
+ }
922
1534
  function validateNewRegistryRecord(rec) {
923
1535
  refuseDriverManaged(rec, "a new record");
1536
+ validateKeyId(rec.keyId);
924
1537
  validateGeneration(rec.currentGen);
925
1538
  if (rec.status !== void 0) validateStatus(rec.status);
926
- validateWrappedDeks(rec.wrappedDeks, false);
1539
+ validateWrappedDeks(rec.wrappedDeks, "caller");
927
1540
  validateGovernance(rec.retention, "retention");
928
1541
  validateGovernance(rec.residency, "residency");
929
- const keptGens = validateKeptGens(rec.keptGens, false);
930
- validateKeptGensBelow(keptGens, rec.currentGen);
931
- const withKept = keptGens === void 0 ? rec : { ...rec, keptGens };
932
- if (!("summary" in rec)) return withKept;
933
- const summary = validateSummary(rec.summary, false);
1542
+ const knownGens = validateKnownGens(rec.knownGens, "caller");
1543
+ const withKnown = knownGens === void 0 ? rec : { ...rec, knownGens };
1544
+ const summary = "summary" in rec ? validateSummary(rec.summary, "caller") : void 0;
1545
+ validateKnownGensFit(knownGens, rec.currentGen, summary);
1546
+ if (!("summary" in rec)) return withKnown;
934
1547
  validateSummaryNames(summary, rec.currentGen);
935
1548
  validateSummaryKeys(summary, rec.wrappedDeks);
936
- return { ...withKept, summary };
1549
+ return { ...withKnown, summary };
937
1550
  }
938
1551
  function validateRegistryPatch(patch) {
939
1552
  refuseDriverManaged(patch, "a patch");
1553
+ if ("keyId" in patch) validateKeyId(patch.keyId);
940
1554
  validatePatchGeneration(patch);
941
1555
  if (patch.status !== void 0) validateStatus(patch.status);
942
- if ("wrappedDeks" in patch) validateWrappedDeks(patch.wrappedDeks, false);
1556
+ if ("wrappedDeks" in patch) validateWrappedDeks(patch.wrappedDeks, "caller");
943
1557
  if ("retention" in patch) validateGovernance(patch.retention, "retention");
944
1558
  if ("residency" in patch) validateGovernance(patch.residency, "residency");
945
- const withKept = patch.keptGens === void 0 ? patch : { ...patch, keptGens: validateKeptGens(patch.keptGens, false) };
946
- const withLeases = "leases" in withKept ? { ...withKept, leases: validateLeases(withKept.leases, false) } : withKept;
1559
+ const withKnown = patch.knownGens === void 0 ? patch : { ...patch, knownGens: validateKnownGens(patch.knownGens, "caller") };
1560
+ const withLeases = "leases" in withKnown ? { ...withKnown, leases: validateLeases(withKnown.leases, "caller") } : withKnown;
947
1561
  if (!("summary" in patch)) return withLeases;
948
- return { ...withLeases, summary: validateSummary(patch.summary, false) };
1562
+ return { ...withLeases, summary: validateSummary(patch.summary, "caller") };
949
1563
  }
950
- var REGISTRY_SCHEMA_VERSION = 4;
1564
+ var REGISTRY_SCHEMA_VERSION = 5;
951
1565
  function assertRegistrySchemaVersion(raw, ctx) {
952
1566
  if (raw === void 0) {
953
1567
  throw new IntegrityError(`registry row has no schemaVersion: ${ctx}`);
954
1568
  }
955
1569
  if (typeof raw !== "number" || !Number.isInteger(raw) || raw < 1) {
956
- throw new IntegrityError(`registry row has a malformed schemaVersion (${shown(raw)}): ${ctx}`);
1570
+ throw new IntegrityError(`registry row has a malformed schemaVersion (${shown3(raw)}): ${ctx}`);
957
1571
  }
958
1572
  if (raw < REGISTRY_SCHEMA_VERSION) {
959
1573
  throw new UnsupportedError(
960
- `registry row schemaVersion ${raw} is one this build does not read: it reads schema ${REGISTRY_SCHEMA_VERSION} only. Export the segments with the release that wrote the row and load them with this one into a new prefix (the CHANGELOG gives the steps): ${ctx}`
1574
+ `registry row schemaVersion ${raw} is one this build does not read: it reads schema ${REGISTRY_SCHEMA_VERSION} only. Export the segments with the release that wrote the row and load them with this one into a new prefix (steps: https://github.com/cloudbitmaps/cloudbitmaps/blob/main/CHANGELOG.md, the Breaking entry on registry schema 5): ${ctx}`
961
1575
  );
962
1576
  }
963
1577
  if (raw > REGISTRY_SCHEMA_VERSION) {
@@ -980,7 +1594,7 @@ function tokenParts(token, ctx, what = "token") {
980
1594
  const match = typeof token === "string" ? INCARNATION_TOKEN.exec(token) : null;
981
1595
  if (match === null) {
982
1596
  throw new IntegrityError(
983
- `registry row ${what} is not one a shipped registry writes (${shown(JSON.stringify(token))}): ${ctx}`
1597
+ `registry row ${what} is not one a shipped registry writes (${shown3(JSON.stringify(token))}): ${ctx}`
984
1598
  );
985
1599
  }
986
1600
  const counter = Number(match[2]);
@@ -1025,7 +1639,7 @@ function newIncarnationToken(entropy, tombstone) {
1025
1639
  const counter = tombstone === void 0 ? 0 : tokenParts(tombstone.token, tombstone.segment).counter + 1;
1026
1640
  return `${drawIncarnation(entropy)}.${counter}.${drawWrite(entropy)}`;
1027
1641
  }
1028
- function shown(v) {
1642
+ function shown3(v) {
1029
1643
  const text = String(v);
1030
1644
  return text.length > 64 ? `${text.slice(0, 64)}\u2026` : text;
1031
1645
  }
@@ -1044,7 +1658,7 @@ function parseRegistryEnvelope(text, ctx, expected) {
1044
1658
  const extra = Object.keys(env).filter((k) => !ENVELOPE_FIELDS.includes(k));
1045
1659
  if (extra.length > 0) {
1046
1660
  throw new IntegrityError(
1047
- `registry row has fields its envelope does not declare (${shown(extra.join(", "))}): ${ctx}`
1661
+ `registry row has fields its envelope does not declare (${shown3(extra.join(", "))}): ${ctx}`
1048
1662
  );
1049
1663
  }
1050
1664
  if (typeof env.deleted !== "boolean" || env.record === null || typeof env.record !== "object") {
@@ -1074,7 +1688,7 @@ function recordFromNew(ref, rec, now, token) {
1074
1688
  retention: rec.retention,
1075
1689
  residency: rec.residency,
1076
1690
  summary: rec.summary,
1077
- keptGens: rec.keptGens,
1691
+ knownGens: rec.knownGens,
1078
1692
  // A new row has no holder: a lease is written only against a row that already exists.
1079
1693
  leases: void 0,
1080
1694
  createdAt: now,
@@ -1089,32 +1703,32 @@ function assertStoredRecordShape(r, ctx) {
1089
1703
  }
1090
1704
  if (r.currentGen !== null && (!Number.isSafeInteger(r.currentGen) || r.currentGen < 0)) {
1091
1705
  throw new IntegrityError(
1092
- `registry record has an invalid currentGen (${shown(r.currentGen)}): ${ctx}`
1706
+ `registry record has an invalid currentGen (${shown3(r.currentGen)}): ${ctx}`
1093
1707
  );
1094
1708
  }
1095
1709
  if (!STATUSES.includes(r.status)) {
1096
- throw new IntegrityError(`registry record has an unknown status (${shown(r.status)}): ${ctx}`);
1710
+ throw new IntegrityError(`registry record has an unknown status (${shown3(r.status)}): ${ctx}`);
1097
1711
  }
1098
1712
  const declared = RECORD_FIELDS;
1099
1713
  const extra = Object.keys(r).filter((k) => !declared.includes(k));
1100
1714
  if (extra.length > 0) {
1101
1715
  throw new IntegrityError(
1102
- `registry record has fields a schema-${REGISTRY_SCHEMA_VERSION} row does not declare (${shown(extra.join(", "))}): ${ctx}`
1716
+ `registry record has fields a schema-${REGISTRY_SCHEMA_VERSION} row does not declare (${shown3(extra.join(", "))}): ${ctx}`
1103
1717
  );
1104
1718
  }
1105
1719
  if (r.keyId !== void 0 && typeof r.keyId !== "string") {
1106
1720
  throw new IntegrityError(`registry record has an invalid keyId: ${ctx}`);
1107
1721
  }
1108
- validateWrappedDeks(r.wrappedDeks, true);
1722
+ validateWrappedDeks(r.wrappedDeks, "stored");
1109
1723
  for (const field of ["retention", "residency"]) {
1110
1724
  const meta = r[field];
1111
1725
  if (meta !== void 0 && (meta === null || typeof meta !== "object" || Array.isArray(meta))) {
1112
1726
  throw new IntegrityError(`registry record has a non-object ${field}: ${ctx}`);
1113
1727
  }
1114
1728
  }
1115
- validateSummary(r.summary, true, ctx);
1116
- validateKeptGens(r.keptGens, true, ctx);
1117
- validateLeases(r.leases, true, ctx);
1729
+ validateSummary(r.summary, "stored", ctx);
1730
+ validateKnownGens(r.knownGens, "stored", ctx);
1731
+ validateLeases(r.leases, "stored", ctx);
1118
1732
  }
1119
1733
  function applyRegistryPatch(prev, patch, now, token) {
1120
1734
  const currentGen = "currentGen" in patch ? patch.currentGen ?? null : prev.currentGen;
@@ -1128,12 +1742,12 @@ function applyRegistryPatch(prev, patch, now, token) {
1128
1742
  const kept = currentGen === prev.currentGen ? prev.summary : void 0;
1129
1743
  summary = kept !== void 0 && summaryAgreesWithKeys(kept, wrappedDeks) ? kept : void 0;
1130
1744
  }
1131
- let keptGens;
1132
- if ("keptGens" in patch) {
1133
- validateKeptGensBelow(patch.keptGens, currentGen);
1134
- keptGens = patch.keptGens;
1745
+ let knownGens;
1746
+ if ("knownGens" in patch) {
1747
+ validateKnownGensFit(patch.knownGens, currentGen, summary);
1748
+ knownGens = patch.knownGens;
1135
1749
  } else {
1136
- keptGens = currentGen === prev.currentGen ? prev.keptGens : void 0;
1750
+ knownGens = currentGen === prev.currentGen ? prev.knownGens : void 0;
1137
1751
  }
1138
1752
  let leases;
1139
1753
  if ("leases" in patch) {
@@ -1156,7 +1770,7 @@ function applyRegistryPatch(prev, patch, now, token) {
1156
1770
  retention: "retention" in patch ? patch.retention : prev.retention,
1157
1771
  residency: "residency" in patch ? patch.residency : prev.residency,
1158
1772
  summary,
1159
- keptGens,
1773
+ knownGens,
1160
1774
  leases,
1161
1775
  createdAt: prev.createdAt,
1162
1776
  updatedAt: now,
@@ -1186,6 +1800,7 @@ function entropyIsAvailable(entropy) {
1186
1800
  export {
1187
1801
  CloudRoaringError,
1188
1802
  ValidationError,
1803
+ CONTENTION_NEXT_STEP,
1189
1804
  WriteConflictError,
1190
1805
  IntegrityError,
1191
1806
  NotFoundError,
@@ -1197,19 +1812,24 @@ export {
1197
1812
  BudgetExceededError,
1198
1813
  KeyUnavailableError,
1199
1814
  TransientError,
1200
- isCloudRoaringError,
1201
1815
  isWriteConflictError,
1202
1816
  isTransientError,
1203
- isLeaseExpiredError,
1204
- isStaleOperandError,
1205
- isLeaseLimitError,
1206
1817
  isNotFoundError,
1207
1818
  isIntegrityError,
1208
1819
  isValidationError,
1209
- isUnsupportedError,
1210
- isCapabilityError,
1211
- isBudgetExceededError,
1212
1820
  isKeyUnavailableError,
1821
+ errorKind,
1822
+ requireInteger,
1823
+ requirePositiveInteger,
1824
+ requireFiniteNumber,
1825
+ U32_MAX,
1826
+ CHUNK_COUNT,
1827
+ isBadIdError,
1828
+ splitId,
1829
+ MAX_REMAINDER,
1830
+ joinId,
1831
+ mapWithConcurrency,
1832
+ boundedAll,
1213
1833
  PREAMBLE_BYTES,
1214
1834
  FOOTER_BYTES,
1215
1835
  encodeNameForKey,
@@ -1225,19 +1845,19 @@ export {
1225
1845
  validateUserRef,
1226
1846
  validateChunkKeyOrder,
1227
1847
  validateChunkRef,
1228
- mapWithConcurrency,
1848
+ validateChunkKey,
1229
1849
  assertRegistryCanWrite,
1230
1850
  STORAGE_BACKEND,
1231
1851
  brandAsBackend,
1232
1852
  isStorageBackend,
1233
1853
  MAX_METADATA_BYTES,
1234
- canonicalMetadataJson,
1235
1854
  metadataBytes,
1236
1855
  copiedMetadata,
1237
1856
  metadataFromBytes,
1238
1857
  sizePart,
1239
1858
  fingerprintFor,
1240
1859
  fingerprintParts,
1860
+ footerCrcOf,
1241
1861
  compactFingerprint,
1242
1862
  incarnationOf,
1243
1863
  incarnationField,
@@ -1246,6 +1866,19 @@ export {
1246
1866
  renewsPointer,
1247
1867
  renewPointer,
1248
1868
  pointerIdOf,
1869
+ aadFor,
1870
+ sameKeys,
1871
+ sizeOfFingerprint,
1872
+ usableSummary,
1873
+ summaryAgrees,
1874
+ metadataToCarry,
1875
+ summaryOf,
1876
+ ownSummary,
1877
+ DEFAULT_RETRY_POLICY,
1878
+ jitteredWait,
1879
+ retryRead,
1880
+ checkedRetryPolicy,
1881
+ withRetry,
1249
1882
  MAX_LEASE_MS,
1250
1883
  LEASE_SKEW_MS,
1251
1884
  MAX_LEASES_PER_SEGMENT,
@@ -1257,9 +1890,19 @@ export {
1257
1890
  leaseChurn,
1258
1891
  takeLease,
1259
1892
  releaseLease,
1260
- MAX_KEPT_GENERATIONS,
1261
- usableKeptGens,
1262
- nextKept,
1893
+ knownGensShapeFault,
1894
+ ascending,
1895
+ MAX_STORED_KNOWN_GENERATIONS,
1896
+ usableKnownGens,
1897
+ windowOf,
1898
+ KNOWN_SPAN,
1899
+ keptWindow,
1900
+ nextKnown,
1901
+ rollbackKnown,
1902
+ pruneKnown,
1903
+ describingKnown,
1904
+ isFullLedger,
1905
+ fallbackBound,
1263
1906
  validateNewRegistryRecord,
1264
1907
  validateRegistryPatch,
1265
1908
  serializeRegistryEnvelope,
@@ -1274,6 +1917,10 @@ export {
1274
1917
  entropyIsAvailable,
1275
1918
  DEFAULT_NAMESPACE,
1276
1919
  namespaceKeyPart,
1277
- namespacePathPart
1920
+ namespacePathPart,
1921
+ refuseUnknownOptions,
1922
+ checkNow,
1923
+ requirePositiveInt,
1924
+ requireBool
1278
1925
  };
1279
- //# sourceMappingURL=chunk-YSC7NJWV.js.map
1926
+ //# sourceMappingURL=chunk-OSU7W2Y3.js.map