@cloudbitmaps/core 0.20.0 → 0.21.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 (75) hide show
  1. package/README.md +85 -0
  2. package/dist/{chunk-YSC7NJWV.js → chunk-IGLIZBR7.js} +740 -137
  3. package/dist/chunk-IGLIZBR7.js.map +7 -0
  4. package/dist/core/absent-operands.d.ts +8 -0
  5. package/dist/core/audit.d.ts +12 -2
  6. package/dist/core/bit-route.d.ts +2 -1
  7. package/dist/core/blob.d.ts +12 -1
  8. package/dist/core/bulk-load.d.ts +148 -0
  9. package/dist/core/cas-retry.d.ts +27 -0
  10. package/dist/core/check.d.ts +22 -0
  11. package/dist/core/chunk-checks.d.ts +6 -1
  12. package/dist/core/chunk-window.d.ts +11 -4
  13. package/dist/core/combine-expr.d.ts +1 -3
  14. package/dist/core/combine-feed.d.ts +2 -16
  15. package/dist/core/combine-many.d.ts +12 -9
  16. package/dist/core/concurrency.d.ts +8 -0
  17. package/dist/core/consistency.d.ts +69 -17
  18. package/dist/core/crbm/fingerprint.d.ts +5 -0
  19. package/dist/core/crbm/format.d.ts +0 -1
  20. package/dist/core/crbm/plan-reads.d.ts +9 -3
  21. package/dist/core/crbm/reader.d.ts +52 -6
  22. package/dist/core/crbm/varint.d.ts +2 -1
  23. package/dist/core/crbm/writer.d.ts +5 -1
  24. package/dist/core/crbm-open.d.ts +154 -0
  25. package/dist/core/crbm-storage-source.d.ts +65 -452
  26. package/dist/core/crbm-write.d.ts +75 -0
  27. package/dist/core/crypto.d.ts +6 -1
  28. package/dist/core/engine.d.ts +222 -33
  29. package/dist/core/erase-id.d.ts +42 -7
  30. package/dist/core/erasure.d.ts +4 -3
  31. package/dist/core/errors.d.ts +8 -2
  32. package/dist/core/generation-gc.d.ts +129 -15
  33. package/dist/core/generation-list.d.ts +11 -0
  34. package/dist/core/id-window.d.ts +46 -0
  35. package/dist/core/invalidation-scope.d.ts +77 -0
  36. package/dist/core/known-generations-shape.d.ts +13 -0
  37. package/dist/core/known-generations.d.ts +147 -0
  38. package/dist/core/leases.d.ts +4 -3
  39. package/dist/core/ledger-seed.d.ts +17 -0
  40. package/dist/core/load.d.ts +17 -38
  41. package/dist/core/lru.d.ts +2 -15
  42. package/dist/core/metrics.d.ts +4 -2
  43. package/dist/core/order.d.ts +2 -0
  44. package/dist/core/pinned-storage-source.d.ts +4 -4
  45. package/dist/core/ports.d.ts +89 -16
  46. package/dist/core/publish.d.ts +207 -0
  47. package/dist/core/registry-scan.d.ts +0 -2
  48. package/dist/core/retention-sweep.d.ts +69 -65
  49. package/dist/core/retention.d.ts +1 -1
  50. package/dist/core/retry.d.ts +12 -19
  51. package/dist/core/rollback.d.ts +60 -31
  52. package/dist/core/summary.d.ts +50 -18
  53. package/dist/core/token.d.ts +2 -2
  54. package/dist/core/validate.d.ts +2 -0
  55. package/dist/driver-kit.d.ts +4 -0
  56. package/dist/driver-kit.js +80 -7
  57. package/dist/driver-kit.js.map +3 -3
  58. package/dist/drivers/_shared/bytes.d.ts +6 -0
  59. package/dist/drivers/_shared/faults.d.ts +15 -0
  60. package/dist/drivers/_shared/object-keys.d.ts +18 -0
  61. package/dist/drivers/_shared/object-registry-keys.d.ts +0 -2
  62. package/dist/drivers/_shared/object-registry.d.ts +2 -0
  63. package/dist/drivers/_shared/options.d.ts +19 -0
  64. package/dist/drivers/_shared/registry.d.ts +13 -10
  65. package/dist/drivers/localfs/exact-case.d.ts +0 -3
  66. package/dist/drivers/localfs/fs-util.d.ts +22 -0
  67. package/dist/drivers/localfs/registry.d.ts +10 -4
  68. package/dist/drivers/localfs/storage.d.ts +9 -2
  69. package/dist/drivers/memory.d.ts +7 -4
  70. package/dist/index.d.ts +1 -1
  71. package/dist/index.js +3647 -2410
  72. package/dist/index.js.map +4 -4
  73. package/package.json +1 -1
  74. package/dist/chunk-YSC7NJWV.js.map +0 -7
  75. package/dist/core/kept-generations.d.ts +0 -40
@@ -115,10 +115,126 @@ function isKeyUnavailableError(err) {
115
115
  return isCloudRoaringError(err) && err.name === "KeyUnavailableError";
116
116
  }
117
117
 
118
+ // src/core/bit-route.ts
119
+ var U32_MAX = 4294967295;
120
+ var CHUNK_COUNT = 65536;
121
+ var LOW_16 = 65535;
122
+ function splitId(id) {
123
+ if (!Number.isInteger(id) || id < 0 || id > U32_MAX) {
124
+ throw new ValidationError(`an id must be an integer from 0 to ${U32_MAX}`);
125
+ }
126
+ return { chunkKey: id >>> 16, remainder: id & LOW_16 };
127
+ }
128
+ var MAX_REMAINDER = CHUNK_COUNT - 1;
129
+ function joinId(chunkKey, remainder) {
130
+ return ((chunkKey & LOW_16) << 16 >>> 0) + (remainder & LOW_16);
131
+ }
132
+
133
+ // src/core/check.ts
134
+ function shown(value) {
135
+ if (typeof value === "string") return JSON.stringify(value);
136
+ if (typeof value === "bigint") return `${value}n`;
137
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
138
+ return value === null ? "null" : typeof value;
139
+ }
140
+ function rangeOf(min, max, note) {
141
+ let range = "";
142
+ if (min !== void 0 && max !== void 0) range = ` from ${min} to ${max}`;
143
+ else if (min !== void 0) range = ` of at least ${min}`;
144
+ else if (max !== void 0) range = ` of at most ${max}`;
145
+ return note === void 0 ? range : `${range} (${note})`;
146
+ }
147
+ function requireInteger(value, bounds) {
148
+ const { min, max, what, note } = bounds;
149
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < min || max !== void 0 && value > max) {
150
+ throw new ValidationError(
151
+ `${what} must be an integer${rangeOf(min, max, note)}; got ${shown(value)}`
152
+ );
153
+ }
154
+ return value;
155
+ }
156
+ function requirePositiveInteger(value, what) {
157
+ return requireInteger(value, { min: 1, what });
158
+ }
159
+ function requireFiniteNumber(value, bounds) {
160
+ const { min, max, what, note } = bounds;
161
+ if (typeof value !== "number" || !Number.isFinite(value) || min !== void 0 && value < min || max !== void 0 && value > max) {
162
+ throw new ValidationError(
163
+ `${what} must be a finite number${rangeOf(min, max, note)}; got ${shown(value)}`
164
+ );
165
+ }
166
+ return value;
167
+ }
168
+
169
+ // src/core/concurrency.ts
170
+ async function mapWithConcurrency(items, limit, fn) {
171
+ requirePositiveInteger(limit, "concurrency limit");
172
+ const results = new Array(items.length);
173
+ let next = 0;
174
+ let failed = false;
175
+ let firstError;
176
+ const worker = async () => {
177
+ while (!failed) {
178
+ const i = next++;
179
+ if (i >= items.length) return;
180
+ try {
181
+ results[i] = await fn(items[i], i);
182
+ } catch (err) {
183
+ if (!failed) {
184
+ failed = true;
185
+ firstError = err;
186
+ }
187
+ return;
188
+ }
189
+ }
190
+ };
191
+ const workers = Array.from({ length: Math.min(limit, items.length) }, () => worker());
192
+ await Promise.all(workers);
193
+ if (failed) throw firstError;
194
+ return results;
195
+ }
196
+ function boundedAll(items, limit, fn) {
197
+ try {
198
+ requirePositiveInteger(limit, "concurrency limit");
199
+ } catch (err) {
200
+ return Promise.reject(err);
201
+ }
202
+ if (items.length > limit) return inWaves(items, limit, fn);
203
+ const calls = [];
204
+ for (const item of items) {
205
+ try {
206
+ calls.push(fn(item));
207
+ } catch (err) {
208
+ void Promise.allSettled(calls);
209
+ return Promise.reject(err);
210
+ }
211
+ }
212
+ return Promise.all(calls);
213
+ }
214
+ async function inWaves(items, limit, fn) {
215
+ const results = new Array(items.length);
216
+ let next = 0;
217
+ let failed = false;
218
+ const worker = async () => {
219
+ while (!failed && next < items.length) {
220
+ const i = next++;
221
+ try {
222
+ results[i] = await fn(items[i]);
223
+ } catch (err) {
224
+ failed = true;
225
+ throw err;
226
+ }
227
+ }
228
+ };
229
+ await Promise.all(Array.from({ length: limit }, () => worker()));
230
+ return results;
231
+ }
232
+
118
233
  // src/core/name-codec.ts
119
234
  var KEY_SAFE = /[A-Za-z0-9._:-]/;
120
235
  var PATH_SAFE = /[A-Za-z0-9._-]/;
121
236
  var UTF8 = new TextEncoder();
237
+ var UTF8_LENIENT = new TextDecoder("utf-8", { fatal: false, ignoreBOM: true });
122
238
  function escapeChar(ch) {
123
239
  let out = "";
124
240
  for (const byte of UTF8.encode(ch)) {
@@ -136,17 +252,17 @@ function encodeWith(name, safe) {
136
252
  function decodePercent(encoded) {
137
253
  const bytes = [];
138
254
  for (let i = 0; i < encoded.length; ) {
139
- if (encoded[i] === "%" && i + 2 < encoded.length + 1) {
255
+ if (encoded[i] === "%" && i + 2 <= encoded.length) {
140
256
  const hex = encoded.slice(i + 1, i + 3);
141
257
  if (!/^[0-9A-Fa-f]{2}$/.test(hex)) return encoded;
142
258
  bytes.push(parseInt(hex, 16));
143
259
  i += 3;
144
260
  } else {
145
- for (const b of new TextEncoder().encode(encoded[i])) bytes.push(b);
261
+ for (const b of UTF8.encode(encoded[i])) bytes.push(b);
146
262
  i += 1;
147
263
  }
148
264
  }
149
- return new TextDecoder("utf-8", { fatal: false, ignoreBOM: true }).decode(new Uint8Array(bytes));
265
+ return UTF8_LENIENT.decode(new Uint8Array(bytes));
150
266
  }
151
267
  function encodeNameForKey(name) {
152
268
  return defuseDotComponent(encodeWith(name, KEY_SAFE));
@@ -234,41 +350,14 @@ function validateChunkKeyOrder(keys) {
234
350
  }
235
351
  function validateChunkRef(ref) {
236
352
  validateSegmentRef(ref);
237
- if (!Number.isInteger(ref.chunkKey) || ref.chunkKey < 0 || ref.chunkKey > CHUNK_KEY_MAX) {
353
+ validateChunkKey(ref.chunkKey);
354
+ }
355
+ function validateChunkKey(chunkKey) {
356
+ if (!(typeof chunkKey === "number" && chunkKey >= 0 && chunkKey <= CHUNK_KEY_MAX && (chunkKey | 0) === chunkKey)) {
238
357
  throw new ValidationError(`chunkKey must be an integer in [0, ${CHUNK_KEY_MAX}]`);
239
358
  }
240
359
  }
241
360
 
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}`);
246
- }
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
- }
271
-
272
361
  // src/core/ports.ts
273
362
  function assertRegistryCanWrite(registry, what) {
274
363
  if (registry.capabilities().canWrite === false) {
@@ -336,6 +425,97 @@ function pointerIdOf(row) {
336
425
  return pointerId;
337
426
  }
338
427
 
428
+ // src/core/crypto.ts
429
+ var UTF82 = new TextEncoder();
430
+ var AAD_VERSION = 1;
431
+ var SCOPE_CHUNK = 0;
432
+ var SCOPE_INDEX = 1;
433
+ var SCOPE_METADATA = 2;
434
+ var SCOPE_SUMMARY = 3;
435
+ function aadFor(ref, generation, scope) {
436
+ const ns = UTF82.encode(ref.namespace ?? "");
437
+ const seg = UTF82.encode(ref.segment);
438
+ const out = new Uint8Array(1 + 2 + ns.length + 2 + seg.length + 8 + 1 + 4);
439
+ const view = new DataView(out.buffer);
440
+ let o = 0;
441
+ out[o] = AAD_VERSION;
442
+ o += 1;
443
+ view.setUint16(o, ns.length, true);
444
+ o += 2;
445
+ out.set(ns, o);
446
+ o += ns.length;
447
+ view.setUint16(o, seg.length, true);
448
+ o += 2;
449
+ out.set(seg, o);
450
+ o += seg.length;
451
+ view.setBigUint64(o, BigInt(generation), true);
452
+ o += 8;
453
+ out[o] = scope === "index" ? SCOPE_INDEX : scope === "metadata" ? SCOPE_METADATA : scope === "summary" ? SCOPE_SUMMARY : SCOPE_CHUNK;
454
+ o += 1;
455
+ view.setUint32(o, typeof scope === "number" ? scope : 0, true);
456
+ return out;
457
+ }
458
+ var sameKeys = (a, b) => a !== void 0 && JSON.stringify(a) === JSON.stringify(b);
459
+
460
+ // src/core/retry.ts
461
+ var DEFAULT_RETRY_POLICY = {
462
+ maxAttempts: 4,
463
+ baseDelayMs: 50,
464
+ maxDelayMs: 2e3,
465
+ backoffFactor: 2,
466
+ jitter: "full"
467
+ };
468
+ function backoffDelayMs(policy, attempt) {
469
+ const raw = policy.baseDelayMs * policy.backoffFactor ** (attempt - 1);
470
+ return Math.min(policy.maxDelayMs, raw);
471
+ }
472
+ function applyJitter(policy, delayMs, rng) {
473
+ if (policy.jitter === "none") return delayMs;
474
+ return rng.next() * delayMs;
475
+ }
476
+ function jitteredWait(clock, rng, boundMs) {
477
+ const sleep = clock?.sleep;
478
+ return sleep === void 0 ? Promise.resolve() : sleep.call(clock, Math.floor((rng?.next() ?? 1) * boundMs));
479
+ }
480
+ function retryRead(op, retry) {
481
+ return retry === void 0 ? op() : withRetry(op, retry.policy ?? DEFAULT_RETRY_POLICY, retry);
482
+ }
483
+ function checkedRetryPolicy(policy) {
484
+ const { maxAttempts, baseDelayMs, maxDelayMs, backoffFactor, jitter } = policy;
485
+ requireInteger(maxAttempts, { min: 1, what: "retry.maxAttempts" });
486
+ requireFiniteNumber(baseDelayMs, { min: 0, what: "retry.baseDelayMs" });
487
+ requireFiniteNumber(maxDelayMs, {
488
+ min: baseDelayMs,
489
+ what: "retry.maxDelayMs",
490
+ note: "retry.baseDelayMs"
491
+ });
492
+ requireFiniteNumber(backoffFactor, { min: 1, what: "retry.backoffFactor" });
493
+ if (jitter !== "full" && jitter !== "none") {
494
+ throw new ValidationError(`retry.jitter must be 'full' or 'none'`);
495
+ }
496
+ return policy;
497
+ }
498
+ async function withRetry(op, policy, deps) {
499
+ const retryable = deps.isRetryable ?? isTransientError;
500
+ const attempts = requireInteger(policy.maxAttempts, { min: 1, what: "retry.maxAttempts" });
501
+ let lastErr;
502
+ for (let attempt = 1; attempt <= attempts; attempt++) {
503
+ try {
504
+ return await op();
505
+ } catch (err) {
506
+ lastErr = err;
507
+ if (attempt >= attempts || !retryable(err)) throw err;
508
+ const delayMs = applyJitter(policy, backoffDelayMs(policy, attempt), deps.rng);
509
+ try {
510
+ deps.onRetry?.({ attempt, delayMs, err });
511
+ } catch {
512
+ }
513
+ await deps.clock.sleep(delayMs);
514
+ }
515
+ }
516
+ throw lastErr;
517
+ }
518
+
339
519
  // src/core/token.ts
340
520
  var INCARNATION_TOKEN = /^([0-9a-f]{32})\.(0|[1-9]\d*)\.([0-9a-f]{16})$/;
341
521
  function incarnationOf(token) {
@@ -403,10 +583,7 @@ function leaseChurn(deps) {
403
583
  if (used >= LEASE_ONLY_RETRIES || !onlyLeasesDiffer(held, now, alsoIgnore)) return void 0;
404
584
  const bound = Math.min(CONFLICT_BASE_MS * 2 ** Math.min(used, 4), CONFLICT_CAP_MS);
405
585
  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
- }
586
+ await jitteredWait(deps.clock, deps.rng, bound);
410
587
  return { row: await reread() };
411
588
  }
412
589
  };
@@ -417,7 +594,7 @@ function listOf(entries) {
417
594
  (a, b) => a.generation !== b.generation ? a.generation - b.generation : a.holder < b.holder ? -1 : a.holder > b.holder ? 1 : 0
418
595
  );
419
596
  }
420
- var wait = (deps, bound) => deps.clock.sleep(Math.floor((deps.rng?.next() ?? 1) * bound));
597
+ var wait = (deps, bound) => jitteredWait(deps.clock, deps.rng, bound);
421
598
  var heldBy = (row, take) => (row?.leases ?? []).some(
422
599
  (e) => e.holder === take.holder && e.generation === take.generation && e.until === take.until
423
600
  );
@@ -526,6 +703,44 @@ function namespacePathPart(namespace) {
526
703
  return namespace === void 0 ? DEFAULT_NAMESPACE : encodeNameForPath(namespace);
527
704
  }
528
705
 
706
+ // src/drivers/_shared/options.ts
707
+ function refuseUnknownOptions(name, options, keys, hint) {
708
+ if (options === null || typeof options !== "object") {
709
+ throw new ValidationError(
710
+ `${name} needs an options object \u2014 got ${options === null ? "null" : typeof options}`
711
+ );
712
+ }
713
+ const unknown = Object.keys(options).filter((k) => !keys.includes(k));
714
+ if (unknown.length > 0) {
715
+ const list = (ks) => ks.map((k) => `\`${k}\``).join(", ");
716
+ throw new ValidationError(
717
+ `${name} does not take ${list(unknown)}. It takes ${list(keys)}${hint === void 0 ? "" : `; ${hint}`}.`
718
+ );
719
+ }
720
+ }
721
+ function checkNow(name, now) {
722
+ if (now !== void 0 && typeof now !== "function") {
723
+ throw new ValidationError(`${name}: \`now\` must be a function returning epoch milliseconds`);
724
+ }
725
+ }
726
+ function requirePositiveInt(name, value) {
727
+ return value === void 0 ? void 0 : requirePositiveInteger(value, name);
728
+ }
729
+ function requireBool(name, value) {
730
+ if (value === void 0) return void 0;
731
+ if (typeof value !== "boolean") {
732
+ throw new ValidationError(`${name} must be a boolean; got ${shown2(value)}`);
733
+ }
734
+ return value;
735
+ }
736
+ function shown2(value) {
737
+ if (typeof value === "string") return JSON.stringify(value);
738
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
739
+ return String(value);
740
+ }
741
+ return value === null ? "null" : typeof value;
742
+ }
743
+
529
744
  // src/core/crbm/frame.ts
530
745
  var PREAMBLE_BYTES = 8;
531
746
  var FOOTER_BYTES = 104;
@@ -533,7 +748,9 @@ var FOOTER_BYTES = 104;
533
748
  // src/core/metadata.ts
534
749
  var MAX_METADATA_BYTES = 1024;
535
750
  var MAX_METADATA_KEY_BYTES = 128;
536
- var utf8Length = (s) => new TextEncoder().encode(s).length;
751
+ var UTF83 = new TextEncoder();
752
+ var UTF8_STRICT = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
753
+ var utf8Length = (s) => UTF83.encode(s).length;
537
754
  function canonicalMetadataJson(value, fail) {
538
755
  if (value === null || typeof value !== "object") {
539
756
  fail(`metadata must be a plain object (got ${value === null ? "null" : typeof value})`);
@@ -580,7 +797,7 @@ function canonicalMetadataJson(value, fail) {
580
797
  function metadataBytes(value, fail) {
581
798
  if (value === void 0) return void 0;
582
799
  const json = canonicalMetadataJson(value, fail);
583
- return json === "{}" ? void 0 : new TextEncoder().encode(json);
800
+ return json === "{}" ? void 0 : UTF83.encode(json);
584
801
  }
585
802
  function copiedMetadata(value, fail) {
586
803
  const bytes = metadataBytes(value, fail);
@@ -592,7 +809,7 @@ function metadataFromBytes(bytes, fail) {
592
809
  }
593
810
  let text;
594
811
  try {
595
- text = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
812
+ text = UTF8_STRICT.decode(bytes);
596
813
  } catch {
597
814
  fail("metadata is not valid UTF-8");
598
815
  }
@@ -621,6 +838,13 @@ function fingerprintParts(fingerprint) {
621
838
  if (footerCrc > 4294967295) return void 0;
622
839
  return { size, footerCrc };
623
840
  }
841
+ function footerCrcOf(fingerprint) {
842
+ const parts = fingerprintParts(fingerprint);
843
+ if (parts === void 0) {
844
+ throw new IntegrityError(`not an object's fingerprint: ${String(fingerprint).slice(0, 64)}`);
845
+ }
846
+ return parts.footerCrc;
847
+ }
624
848
  function compactFingerprint(fingerprint) {
625
849
  const colon = fingerprint.indexOf(":");
626
850
  const size = Number(fingerprint.slice(0, colon));
@@ -628,25 +852,352 @@ function compactFingerprint(fingerprint) {
628
852
  return `${size.toString(36)}.${crc.toString(36)}`;
629
853
  }
630
854
 
631
- // src/core/kept-generations.ts
855
+ // src/core/summary.ts
856
+ var NONCE_BYTES = 12;
857
+ var TAG_BYTES = 16;
858
+ var COUNT_BYTES = 8;
859
+ var SIZE_BYTES = 8;
860
+ var CRC_BYTES = 4;
861
+ var TAIL_BYTES = 4;
862
+ var FIXED_BYTES = COUNT_BYTES + SIZE_BYTES + CRC_BYTES + TAIL_BYTES;
863
+ var MAX_SUMMARY_CARDINALITY = U32_MAX + 1;
864
+ var MAX_TAIL_BYTES = 8392820;
865
+ function tailBytesFit(tailBytes, size) {
866
+ return typeof tailBytes === "number" && Number.isInteger(tailBytes) && tailBytes >= FOOTER_BYTES && tailBytes <= size - PREAMBLE_BYTES && tailBytes <= MAX_TAIL_BYTES;
867
+ }
868
+ function sizeOfFingerprint(fingerprint) {
869
+ const parts = fingerprintParts(fingerprint);
870
+ if (parts === void 0)
871
+ throw new IntegrityError("registry record summary: a malformed fingerprint");
872
+ return parts.size;
873
+ }
874
+ var BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
875
+ function toBase64(bytes) {
876
+ let out = "";
877
+ for (let i = 0; i < bytes.length; i += 3) {
878
+ const a = bytes[i];
879
+ const b = bytes[i + 1];
880
+ const c = bytes[i + 2];
881
+ out += BASE64[a >> 2] + BASE64[(a & 3) << 4 | (b ?? 0) >> 4];
882
+ out += b === void 0 ? "=" : BASE64[(b & 15) << 2 | (c ?? 0) >> 6];
883
+ out += c === void 0 ? "=" : BASE64[c & 63];
884
+ }
885
+ return out;
886
+ }
887
+ function fromBase64(text) {
888
+ if (text.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(text)) return void 0;
889
+ const pad = text.endsWith("==") ? 2 : text.endsWith("=") ? 1 : 0;
890
+ const out = new Uint8Array(text.length / 4 * 3 - pad);
891
+ let o = 0;
892
+ for (let i = 0; i < text.length; i += 4) {
893
+ 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);
894
+ if (o < out.length) out[o++] = n >> 16 & 255;
895
+ if (o < out.length) out[o++] = n >> 8 & 255;
896
+ if (o < out.length) out[o++] = n & 255;
897
+ }
898
+ return out;
899
+ }
900
+ function clearSummary(generation, cardinality, fingerprint, tailBytes, metadata) {
901
+ return metadata === void 0 || Object.keys(metadata).length === 0 ? { generation, cardinality, fingerprint, tailBytes } : { generation, cardinality, fingerprint, tailBytes, metadata };
902
+ }
903
+ function sealSummary(aead, ref, generation, cardinality, fingerprint, tailBytes, metadata) {
904
+ const meta = metadataBytes(metadata, (message) => {
905
+ throw new IntegrityError(`summary metadata: ${message}`);
906
+ });
907
+ const parts = fingerprintParts(fingerprint);
908
+ if (parts === void 0) throw new IntegrityError("summary: a malformed fingerprint");
909
+ const plain = new Uint8Array(FIXED_BYTES + (meta?.length ?? 0));
910
+ const view = new DataView(plain.buffer);
911
+ view.setBigUint64(0, BigInt(cardinality), true);
912
+ view.setBigUint64(COUNT_BYTES, BigInt(parts.size), true);
913
+ view.setUint32(COUNT_BYTES + SIZE_BYTES, parts.footerCrc, true);
914
+ view.setUint32(COUNT_BYTES + SIZE_BYTES + CRC_BYTES, tailBytes, true);
915
+ if (meta !== void 0) plain.set(meta, FIXED_BYTES);
916
+ const { nonce, ciphertext, tag } = aead.seal(plain, aadFor(ref, generation, "summary"));
917
+ const framed = new Uint8Array(nonce.length + ciphertext.length + tag.length);
918
+ framed.set(nonce, 0);
919
+ framed.set(ciphertext, nonce.length);
920
+ framed.set(tag, nonce.length + ciphertext.length);
921
+ return { generation, sealed: toBase64(framed) };
922
+ }
923
+ function openSummary(aead, ref, summary) {
924
+ const fail = (message) => {
925
+ throw new IntegrityError(`registry record summary: ${message}`);
926
+ };
927
+ const framed = fromBase64(summary.sealed);
928
+ const framing = NONCE_BYTES + TAG_BYTES;
929
+ if (framed === void 0 || framed.length < framing + FIXED_BYTES || framed.length > framing + FIXED_BYTES + MAX_METADATA_BYTES) {
930
+ fail(
931
+ "the sealed summary is not a nonce, a count, a fingerprint, a tail size, up to 1 KiB of metadata and a tag"
932
+ );
933
+ }
934
+ const bytes = framed;
935
+ let plain;
936
+ try {
937
+ plain = aead.open(
938
+ {
939
+ nonce: bytes.subarray(0, NONCE_BYTES),
940
+ ciphertext: bytes.subarray(NONCE_BYTES, bytes.length - TAG_BYTES),
941
+ tag: bytes.subarray(bytes.length - TAG_BYTES)
942
+ },
943
+ aadFor(ref, summary.generation, "summary")
944
+ );
945
+ } catch (err) {
946
+ if (!isIntegrityError(err)) throw err;
947
+ return fail(
948
+ "the sealed summary does not open: tampered bytes, or a summary moved from another generation"
949
+ );
950
+ }
951
+ if (plain.length < FIXED_BYTES)
952
+ fail("the sealed summary is shorter than its count, fingerprint and tail size");
953
+ const view = new DataView(plain.buffer, plain.byteOffset, plain.byteLength);
954
+ const count = view.getBigUint64(0, true);
955
+ if (count > BigInt(MAX_SUMMARY_CARDINALITY)) fail(`cardinality ${count} is outside 0 to 2^32`);
956
+ const size = view.getBigUint64(COUNT_BYTES, true);
957
+ const fingerprint = fingerprintFor(Number(size), view.getUint32(COUNT_BYTES + SIZE_BYTES, true));
958
+ if (size > BigInt(Number.MAX_SAFE_INTEGER) || fingerprintParts(fingerprint) === void 0) {
959
+ fail(`the object size ${size} is not one a .crbm object can have`);
960
+ }
961
+ const tailBytes = view.getUint32(COUNT_BYTES + SIZE_BYTES + CRC_BYTES, true);
962
+ if (!tailBytesFit(tailBytes, Number(size))) {
963
+ fail(`the tail size ${tailBytes} is not one a .crbm object of ${size} bytes can have`);
964
+ }
965
+ const metadata = plain.length === FIXED_BYTES ? void 0 : metadataFromBytes(plain.subarray(FIXED_BYTES), fail);
966
+ return { cardinality: Number(count), metadata, fingerprint, tailBytes };
967
+ }
968
+ function usableSummary(ref, row, aead) {
969
+ const summary = row.summary;
970
+ if (summary === void 0 || row.status !== "active" || summary.generation !== row.currentGen) {
971
+ return void 0;
972
+ }
973
+ const keyed = row.wrappedDeks !== void 0 && row.wrappedDeks.length > 0;
974
+ if ("sealed" in summary) {
975
+ if (!keyed || aead === void 0) return void 0;
976
+ try {
977
+ return openSummary(aead, ref, summary);
978
+ } catch (err) {
979
+ if (isIntegrityError(err)) return void 0;
980
+ throw err;
981
+ }
982
+ }
983
+ if (keyed) return void 0;
984
+ const parts = fingerprintParts(summary.fingerprint);
985
+ if (parts === void 0 || !tailBytesFit(summary.tailBytes, parts.size)) return void 0;
986
+ return {
987
+ cardinality: summary.cardinality,
988
+ metadata: summary.metadata,
989
+ fingerprint: summary.fingerprint,
990
+ tailBytes: summary.tailBytes
991
+ };
992
+ }
993
+ function canonicalOrNone(metadata) {
994
+ if (metadata === void 0) return void 0;
995
+ const json = canonicalMetadataJson(metadata, (message) => {
996
+ throw new IntegrityError(`metadata: ${message}`);
997
+ });
998
+ return json === "{}" ? void 0 : json;
999
+ }
1000
+ function summaryAgrees(summary, object) {
1001
+ return summary.cardinality === object.cardinality && canonicalOrNone(summary.metadata) === canonicalOrNone(object.metadata) && (summary.tailBytes === void 0 || object.tailBytes === void 0 || summary.tailBytes === object.tailBytes);
1002
+ }
1003
+ function metadataToCarry(object, authenticated) {
1004
+ const empty = (metadata) => metadata === void 0 || Object.keys(metadata).length === 0;
1005
+ return empty(object) && authenticated !== void 0 && !empty(authenticated.metadata) ? authenticated.metadata : object;
1006
+ }
1007
+ function summaryOf(ref, generation, description, aead) {
1008
+ const { cardinality, fingerprint, tailBytes, metadata } = description;
1009
+ if (tailBytes > MAX_TAIL_BYTES) return void 0;
1010
+ return aead === void 0 ? clearSummary(generation, cardinality, fingerprint, tailBytes, metadata) : sealSummary(aead, ref, generation, cardinality, fingerprint, tailBytes, metadata);
1011
+ }
1012
+ function ownSummary(ref, generation, description, aead) {
1013
+ const summary = summaryOf(ref, generation, description, aead);
1014
+ if (summary === void 0) {
1015
+ throw new IntegrityError(
1016
+ `the object written for generation ${generation} of "${ref.segment}" has a tail of ${description.tailBytes} bytes, above the ${MAX_TAIL_BYTES} a summary records`
1017
+ );
1018
+ }
1019
+ return summary;
1020
+ }
1021
+
1022
+ // src/core/known-generations-shape.ts
1023
+ var ENTRY_KEYS = ["generation", "footerCrc"];
1024
+ function knownGensShapeFault(value, max, show = String) {
1025
+ if (value === void 0) return void 0;
1026
+ if (!Array.isArray(value)) return "must be an array";
1027
+ const list = value;
1028
+ if (list.length > max) return `has ${list.length} entries, cap ${max}`;
1029
+ const seen = /* @__PURE__ */ new Set();
1030
+ for (const e of list) {
1031
+ if (typeof e !== "object" || e === null || Array.isArray(e))
1032
+ return "an entry must be an object";
1033
+ const entry = e;
1034
+ const extra = Object.keys(entry).filter((k) => !ENTRY_KEYS.includes(k));
1035
+ if (extra.length > 0) {
1036
+ return `an entry carries an undeclared field (${show(extra.join(", "))})`;
1037
+ }
1038
+ const { generation, footerCrc } = entry;
1039
+ if (!Number.isSafeInteger(generation) || generation < 0) {
1040
+ return `a generation must be a non-negative safe integer (got ${show(generation)})`;
1041
+ }
1042
+ if (!Number.isInteger(footerCrc) || footerCrc < 0 || footerCrc > 4294967295) {
1043
+ return `a footerCrc must be an integer from 0 to 2^32 - 1 (got ${show(footerCrc)})`;
1044
+ }
1045
+ if (seen.has(generation)) return `generation ${String(generation)} is repeated`;
1046
+ seen.add(generation);
1047
+ }
1048
+ return void 0;
1049
+ }
1050
+
1051
+ // src/core/order.ts
1052
+ var ascending = (a, b) => a - b;
1053
+
1054
+ // src/core/known-generations.ts
632
1055
  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) };
1056
+ var KNOWN_FILL = 128;
1057
+ var MAX_KNOWN_GENERATIONS = 192;
1058
+ var MAX_STORED_KNOWN_GENERATIONS = 384;
1059
+ function usableKnownGens(record) {
1060
+ const known = record?.knownGens;
1061
+ if (record === null || known === void 0 || known.length === 0 || record.status !== "active" || record.currentGen === null) {
1062
+ return void 0;
1063
+ }
1064
+ const last = known[known.length - 1];
1065
+ if (last.generation !== record.currentGen) return void 0;
1066
+ const summary = record.summary;
1067
+ if (summary !== void 0 && summary.generation === record.currentGen && "fingerprint" in summary && fingerprintParts(summary.fingerprint)?.footerCrc !== last.footerCrc) {
1068
+ return void 0;
1069
+ }
1070
+ return known;
1071
+ }
1072
+ function windowOf(record) {
1073
+ const known = usableKnownGens(record);
1074
+ if (known === void 0) return void 0;
1075
+ const pointer = record.currentGen;
1076
+ return known.filter((e) => e.generation < pointer).map((e) => e.generation);
1077
+ }
1078
+ function carriesWindow(record, leased) {
1079
+ const below = windowOf(record);
1080
+ return below !== void 0 && below.filter((g) => !leased.has(g)).length <= MAX_KEPT_GENERATIONS;
1081
+ }
1082
+ var KNOWN_SPAN = 127;
1083
+ function keptWindow(below, superseded, keep) {
1084
+ const oldestFirst = [...below].sort(ascending);
1085
+ const first = keep > 0 && superseded !== void 0 && oldestFirst.includes(superseded) ? superseded : void 0;
1086
+ const held = new Set(first === void 0 ? [] : [first]);
1087
+ for (let i = oldestFirst.length - 1, room = keep - held.size; i >= 0 && room > 0; i--) {
1088
+ if (oldestFirst[i] === first) continue;
1089
+ held.add(oldestFirst[i]);
1090
+ room -= 1;
1091
+ }
1092
+ return {
1093
+ kept: oldestFirst.filter((g) => held.has(g)),
1094
+ evict: oldestFirst.filter((g) => !held.has(g))
1095
+ };
1096
+ }
1097
+ function capKnown(known, protect, leased = /* @__PURE__ */ new Set()) {
1098
+ const keep = /* @__PURE__ */ new Set();
1099
+ for (const e of known) if (protect.has(e.generation)) keep.add(e.generation);
1100
+ for (let i = known.length - 1; i >= 0 && keep.size < MAX_KNOWN_GENERATIONS; i--) {
1101
+ if (leased.has(known[i].generation)) keep.add(known[i].generation);
1102
+ }
1103
+ for (let i = known.length - 1; i >= 0 && keep.size < KNOWN_FILL; i--)
1104
+ keep.add(known[i].generation);
1105
+ return known.filter((e) => keep.has(e.generation));
1106
+ }
1107
+ function capInsert(known, protect, leased) {
1108
+ const keep = /* @__PURE__ */ new Set();
1109
+ let unleased = 0;
1110
+ for (const e of known) {
1111
+ if (!protect.has(e.generation)) continue;
1112
+ keep.add(e.generation);
1113
+ if (!leased.has(e.generation)) unleased += 1;
1114
+ }
1115
+ for (let i = known.length - 1; i >= 0 && unleased < KNOWN_FILL; i--) {
1116
+ const g = known[i].generation;
1117
+ if (keep.has(g) || leased.has(g)) continue;
1118
+ keep.add(g);
1119
+ unleased += 1;
1120
+ }
1121
+ for (let i = known.length - 1; i >= 0 && keep.size < MAX_KNOWN_GENERATIONS; i--) {
1122
+ if (leased.has(known[i].generation)) keep.add(known[i].generation);
1123
+ }
1124
+ return known.filter((e) => keep.has(e.generation));
1125
+ }
1126
+ function makeCurrent(known, entry) {
1127
+ return [...known.filter((e) => e.generation !== entry.generation), entry];
1128
+ }
1129
+ function nextKnown(prev, generation, footerCrc, keep, options = {}) {
1130
+ const self = { generation, footerCrc };
1131
+ const clears = options.clearsLeases === true;
1132
+ const byName = keep !== void 0 && keep <= MAX_KEPT_GENERATIONS;
1133
+ if (options.seed !== void 0 && !clears) {
1134
+ const seed = options.seed.filter((e) => e.generation !== generation);
1135
+ const protect = /* @__PURE__ */ new Set([...seed.map((e) => e.generation), generation]);
1136
+ return {
1137
+ known: capKnown([...seed, self], protect, options.leased ?? /* @__PURE__ */ new Set()),
1138
+ list: void 0,
1139
+ evict: []
1140
+ };
1141
+ }
1142
+ const found = usableKnownGens(prev);
1143
+ if (found === void 0 && prev !== null && prev.currentGen !== null) {
1144
+ return { known: [self], list: void 0, evict: [] };
1145
+ }
1146
+ const all = found ?? [];
1147
+ const below = all.filter((e) => e.generation < generation);
1148
+ const above = clears ? [] : all.filter((e) => e.generation > generation);
1149
+ const span = clears ? 0 : Math.min(keep ?? Number.MAX_SAFE_INTEGER, KNOWN_SPAN);
1150
+ const vouch = new Set((span === 0 ? [] : below.slice(-span)).map((e) => e.generation));
1151
+ const leased = clears ? /* @__PURE__ */ new Set() : options.leased ?? /* @__PURE__ */ new Set();
1152
+ const held = below.filter((e) => !vouch.has(e.generation) && leased.has(e.generation));
1153
+ const chosen = /* @__PURE__ */ new Set([
1154
+ ...vouch,
1155
+ ...held.map((e) => e.generation),
1156
+ ...above.map((e) => e.generation)
1157
+ ]);
1158
+ const known = capKnown(
1159
+ [...all.filter((e) => chosen.has(e.generation)), self],
1160
+ /* @__PURE__ */ new Set([...vouch, generation]),
1161
+ leased
1162
+ );
1163
+ const wide = !clears && found !== void 0 && !carriesWindow(prev, leased);
1164
+ if (!byName || wide) return { known, list: void 0, evict: [] };
1165
+ const named = new Set(known.map((e) => e.generation));
1166
+ return {
1167
+ known,
1168
+ list: known.filter((e) => e.generation < generation).map((e) => e.generation).sort(ascending),
1169
+ evict: below.filter((e) => !named.has(e.generation)).map((e) => e.generation).sort(ascending)
1170
+ };
1171
+ }
1172
+ function rollbackKnown(prev, target) {
1173
+ const found = usableKnownGens(prev);
1174
+ if (found === void 0) return void 0;
1175
+ const moved = makeCurrent(found, target);
1176
+ const inserted = !found.some((e) => e.generation === target.generation);
1177
+ if (!inserted && moved.length <= MAX_KNOWN_GENERATIONS) return moved;
1178
+ return capInsert(moved, /* @__PURE__ */ new Set([target.generation, prev.currentGen]), leasedByRow(prev));
1179
+ }
1180
+ function pruneKnown(record, doomed) {
1181
+ const found = usableKnownGens(record);
1182
+ if (found === void 0) return void 0;
1183
+ const rest = found.filter((e) => e.generation === record.currentGen || !doomed.has(e.generation));
1184
+ if (rest.length <= MAX_KNOWN_GENERATIONS) return rest;
1185
+ return capKnown(rest, /* @__PURE__ */ new Set([record.currentGen]), leasedByRow(record));
1186
+ }
1187
+ function describingKnown(record, movedTo) {
1188
+ const found = usableKnownGens(record);
1189
+ if (found === void 0 || found.length <= MAX_KNOWN_GENERATIONS) return found;
1190
+ return capKnown(found, /* @__PURE__ */ new Set([record.currentGen, movedTo]), leasedByRow(record));
1191
+ }
1192
+ function isFullLedger(known) {
1193
+ return known.length >= KNOWN_FILL;
1194
+ }
1195
+ function fallbackBound(record, known) {
1196
+ const leased = leasedByRow(record);
1197
+ return (known.find((e) => !leased.has(e.generation)) ?? known[0]).generation;
1198
+ }
1199
+ function leasedByRow(record) {
1200
+ return new Set((record.leases ?? []).map((e) => e.generation));
650
1201
  }
651
1202
 
652
1203
  // src/drivers/_shared/registry.ts
@@ -664,7 +1215,7 @@ var RECORD_FIELDS = [
664
1215
  "createdAt",
665
1216
  "updatedAt",
666
1217
  "token",
667
- "keptGens",
1218
+ "knownGens",
668
1219
  "leases",
669
1220
  "pointerId"
670
1221
  ];
@@ -673,10 +1224,9 @@ var ENVELOPE_FIELDS = ["schemaVersion", "deleted", "record"];
673
1224
  var MAX_GOVERNANCE_BYTES = 64 * 1024;
674
1225
  var MAX_WRAPPED_DEKS = 8;
675
1226
  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;
1227
+ var SEALED_SUMMARY_MIN_BYTES = 12 + 8 + 8 + 4 + 4 + 16;
678
1228
  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}=)?$/;
1229
+ var BASE642 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
680
1230
  function validateGeneration(gen) {
681
1231
  if (gen === null) return;
682
1232
  if (!Number.isSafeInteger(gen) || gen < 0) {
@@ -716,10 +1266,10 @@ function validateGovernance(meta, field) {
716
1266
  throw new ValidationError(`${field} is ${json.length}B, exceeds cap ${MAX_GOVERNANCE_BYTES}B`);
717
1267
  }
718
1268
  }
719
- function validateWrappedDeks(value, isStored) {
1269
+ function validateWrappedDeks(value, origin) {
720
1270
  if (value === void 0) return;
721
1271
  const fail = (msg) => {
722
- throw isStored ? new IntegrityError(msg) : new ValidationError(msg);
1272
+ throw origin === "stored" ? new IntegrityError(msg) : new ValidationError(msg);
723
1273
  };
724
1274
  if (!Array.isArray(value)) fail("wrappedDeks must be an array");
725
1275
  const list = value;
@@ -744,11 +1294,11 @@ function base64PadBitsAreZero(text) {
744
1294
  const last = BASE64_ALPHABET.indexOf(text[text.length - pad - 1]);
745
1295
  return pad === 2 ? (last & 15) === 0 : (last & 3) === 0;
746
1296
  }
747
- function validateSummary(value, isStored, ctx) {
1297
+ function validateSummary(value, origin, ctx) {
748
1298
  if (value === void 0) return void 0;
749
1299
  const fail = (msg) => {
750
1300
  const where = ctx === void 0 ? "" : `: ${ctx}`;
751
- throw isStored ? new IntegrityError(`registry record summary: ${msg}${where}`) : new ValidationError(`summary: ${msg}${where}`);
1301
+ throw origin === "stored" ? new IntegrityError(`registry record summary: ${msg}${where}`) : new ValidationError(`summary: ${msg}${where}`);
752
1302
  };
753
1303
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
754
1304
  fail(
@@ -758,7 +1308,7 @@ function validateSummary(value, isStored, ctx) {
758
1308
  const s = value;
759
1309
  const keys = Object.keys(s);
760
1310
  const isSealed = keys.includes("sealed");
761
- const allowed = isSealed ? ["generation", "sealed"] : ["generation", "cardinality", "fingerprint", "metadata"];
1311
+ const allowed = isSealed ? ["generation", "sealed"] : ["generation", "cardinality", "fingerprint", "tailBytes", "metadata"];
762
1312
  const extra = keys.filter((k) => !allowed.includes(k));
763
1313
  if (extra.length > 0) {
764
1314
  fail(
@@ -771,14 +1321,14 @@ function validateSummary(value, isStored, ctx) {
771
1321
  }
772
1322
  if (isSealed) {
773
1323
  const sealed = s.sealed;
774
- if (typeof sealed !== "string" || !BASE64.test(sealed) || !base64PadBitsAreZero(sealed)) {
1324
+ if (typeof sealed !== "string" || !BASE642.test(sealed) || !base64PadBitsAreZero(sealed)) {
775
1325
  fail("sealed must be canonical, padded standard base64");
776
1326
  }
777
1327
  const text = sealed;
778
1328
  const bytes = text.length / 4 * 3 - (text.endsWith("==") ? 2 : text.endsWith("=") ? 1 : 0);
779
1329
  if (bytes < SEALED_SUMMARY_MIN_BYTES || bytes > SEALED_SUMMARY_MAX_BYTES) {
780
1330
  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)`
1331
+ `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
1332
  );
783
1333
  }
784
1334
  return Object.freeze({ generation, sealed: text });
@@ -788,26 +1338,35 @@ function validateSummary(value, isStored, ctx) {
788
1338
  fail(`cardinality must be an integer from 0 to 2^32 (got ${String(n)})`);
789
1339
  }
790
1340
  const fingerprint = s.fingerprint;
791
- if (fingerprintParts(fingerprint) === void 0) {
1341
+ const parts = fingerprintParts(fingerprint);
1342
+ if (parts === void 0) {
1343
+ fail(
1344
+ `fingerprint must be an object's, <size>:<footer checksum> in decimal (got ${shown3(JSON.stringify(fingerprint))})`
1345
+ );
1346
+ }
1347
+ const tailBytes = s.tailBytes;
1348
+ if (!tailBytesFit(tailBytes, parts.size)) {
792
1349
  fail(
793
- `fingerprint must be an object's, <size>:<footer checksum> in decimal (got ${shown(JSON.stringify(fingerprint))})`
1350
+ `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
1351
  );
795
1352
  }
796
1353
  if (s.metadata === void 0) {
797
1354
  return Object.freeze({
798
1355
  generation,
799
1356
  cardinality: n,
800
- fingerprint
1357
+ fingerprint,
1358
+ tailBytes
801
1359
  });
802
1360
  }
803
1361
  const canonical = canonicalMetadataJson(s.metadata, fail);
804
1362
  if (canonical === "{}") fail("metadata is empty; a generation without metadata carries none");
805
- if (isStored) return s;
1363
+ if (origin === "stored") return s;
806
1364
  const metadata = Object.freeze(JSON.parse(canonical));
807
1365
  return Object.freeze({
808
1366
  generation,
809
1367
  cardinality: n,
810
1368
  fingerprint,
1369
+ tailBytes,
811
1370
  metadata
812
1371
  });
813
1372
  }
@@ -828,33 +1387,30 @@ function validateSummaryKeys(summary, wrappedDeks) {
828
1387
  );
829
1388
  }
830
1389
  }
831
- function validateKeptGens(value, isStored, ctx) {
1390
+ function validateKnownGens(value, origin, ctx) {
832
1391
  if (value === void 0) return void 0;
833
1392
  const fail = (msg) => {
834
1393
  const where = ctx === void 0 ? "" : `: ${ctx}`;
835
- throw isStored ? new IntegrityError(`registry record keptGens: ${msg}${where}`) : new ValidationError(`keptGens: ${msg}`);
1394
+ throw origin === "stored" ? new IntegrityError(`registry record knownGens: ${msg}${where}`) : new ValidationError(`knownGens: ${msg}`);
836
1395
  };
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;
1396
+ const max = origin === "stored" ? MAX_STORED_KNOWN_GENERATIONS : MAX_KNOWN_GENERATIONS;
1397
+ const fault = knownGensShapeFault(value, max, shown3);
1398
+ if (fault !== void 0) fail(fault);
1399
+ const out = [];
1400
+ for (const entry of value) {
1401
+ out.push(
1402
+ origin === "stored" ? entry : Object.freeze({ generation: entry.generation, footerCrc: entry.footerCrc })
1403
+ );
848
1404
  }
849
- return isStored ? list : Object.freeze([...list]);
1405
+ return origin === "stored" ? out : Object.freeze(out);
850
1406
  }
851
1407
  var HOLDER = /^[0-9a-f]{16}$/;
852
1408
  var LEASE_ENTRY_KEYS = ["holder", "generation", "until"];
853
- function validateLeases(value, isStored, ctx) {
1409
+ function validateLeases(value, origin, ctx) {
854
1410
  if (value === void 0) return void 0;
855
1411
  const fail = (msg) => {
856
1412
  const where = ctx === void 0 ? "" : `: ${ctx}`;
857
- throw isStored ? new IntegrityError(`registry record leases: ${msg}${where}`) : new ValidationError(`leases: ${msg}`);
1413
+ throw origin === "stored" ? new IntegrityError(`registry record leases: ${msg}${where}`) : new ValidationError(`leases: ${msg}`);
858
1414
  };
859
1415
  if (!Array.isArray(value)) fail("must be an array");
860
1416
  const list = value;
@@ -882,14 +1438,14 @@ function validateLeases(value, isStored, ctx) {
882
1438
  }
883
1439
  }
884
1440
  out.push(
885
- isStored ? entry : Object.freeze({
1441
+ origin === "stored" ? entry : Object.freeze({
886
1442
  holder,
887
1443
  generation,
888
1444
  until
889
1445
  })
890
1446
  );
891
1447
  }
892
- if (isStored) return out;
1448
+ if (origin === "stored") return out;
893
1449
  return out.length === 0 ? void 0 : Object.freeze(out);
894
1450
  }
895
1451
  function validateLeasesPointer(leases, currentGen) {
@@ -897,17 +1453,20 @@ function validateLeasesPointer(leases, currentGen) {
897
1453
  throw new ValidationError("leases: a row with no pointer has no generation to lease");
898
1454
  }
899
1455
  }
900
- function validateKeptGensBelow(keptGens, currentGen) {
901
- if (keptGens === void 0) return;
1456
+ function validateKnownGensFit(knownGens, currentGen, summary) {
1457
+ if (knownGens === void 0) return;
902
1458
  if (currentGen === null) {
1459
+ throw new ValidationError("knownGens: a row with no pointer has no generation to know");
1460
+ }
1461
+ const last = knownGens[knownGens.length - 1];
1462
+ if (last === void 0 || last.generation !== currentGen) {
903
1463
  throw new ValidationError(
904
- "keptGens: a row with no pointer has no generations below it to keep"
1464
+ `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
1465
  );
906
1466
  }
907
- const top = keptGens[keptGens.length - 1];
908
- if (top !== void 0 && top >= currentGen) {
1467
+ if (summary !== void 0 && summary.generation === currentGen && "fingerprint" in summary && fingerprintParts(summary.fingerprint)?.footerCrc !== last.footerCrc) {
909
1468
  throw new ValidationError(
910
- `keptGens names generation ${top}, which is not below the row's pointer ${currentGen}`
1469
+ `knownGens records footerCrc ${last.footerCrc} for generation ${currentGen}, but the summary's fingerprint ${summary.fingerprint} carries another: both describe the one current object`
911
1470
  );
912
1471
  }
913
1472
  }
@@ -919,45 +1478,52 @@ function refuseDriverManaged(write, what) {
919
1478
  );
920
1479
  }
921
1480
  }
1481
+ function validateKeyId(keyId) {
1482
+ if (keyId !== void 0 && (typeof keyId !== "string" || keyId.length === 0)) {
1483
+ throw new ValidationError("keyId must be a non-empty string, or absent");
1484
+ }
1485
+ }
922
1486
  function validateNewRegistryRecord(rec) {
923
1487
  refuseDriverManaged(rec, "a new record");
1488
+ validateKeyId(rec.keyId);
924
1489
  validateGeneration(rec.currentGen);
925
1490
  if (rec.status !== void 0) validateStatus(rec.status);
926
- validateWrappedDeks(rec.wrappedDeks, false);
1491
+ validateWrappedDeks(rec.wrappedDeks, "caller");
927
1492
  validateGovernance(rec.retention, "retention");
928
1493
  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);
1494
+ const knownGens = validateKnownGens(rec.knownGens, "caller");
1495
+ const withKnown = knownGens === void 0 ? rec : { ...rec, knownGens };
1496
+ const summary = "summary" in rec ? validateSummary(rec.summary, "caller") : void 0;
1497
+ validateKnownGensFit(knownGens, rec.currentGen, summary);
1498
+ if (!("summary" in rec)) return withKnown;
934
1499
  validateSummaryNames(summary, rec.currentGen);
935
1500
  validateSummaryKeys(summary, rec.wrappedDeks);
936
- return { ...withKept, summary };
1501
+ return { ...withKnown, summary };
937
1502
  }
938
1503
  function validateRegistryPatch(patch) {
939
1504
  refuseDriverManaged(patch, "a patch");
1505
+ if ("keyId" in patch) validateKeyId(patch.keyId);
940
1506
  validatePatchGeneration(patch);
941
1507
  if (patch.status !== void 0) validateStatus(patch.status);
942
- if ("wrappedDeks" in patch) validateWrappedDeks(patch.wrappedDeks, false);
1508
+ if ("wrappedDeks" in patch) validateWrappedDeks(patch.wrappedDeks, "caller");
943
1509
  if ("retention" in patch) validateGovernance(patch.retention, "retention");
944
1510
  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;
1511
+ const withKnown = patch.knownGens === void 0 ? patch : { ...patch, knownGens: validateKnownGens(patch.knownGens, "caller") };
1512
+ const withLeases = "leases" in withKnown ? { ...withKnown, leases: validateLeases(withKnown.leases, "caller") } : withKnown;
947
1513
  if (!("summary" in patch)) return withLeases;
948
- return { ...withLeases, summary: validateSummary(patch.summary, false) };
1514
+ return { ...withLeases, summary: validateSummary(patch.summary, "caller") };
949
1515
  }
950
- var REGISTRY_SCHEMA_VERSION = 4;
1516
+ var REGISTRY_SCHEMA_VERSION = 5;
951
1517
  function assertRegistrySchemaVersion(raw, ctx) {
952
1518
  if (raw === void 0) {
953
1519
  throw new IntegrityError(`registry row has no schemaVersion: ${ctx}`);
954
1520
  }
955
1521
  if (typeof raw !== "number" || !Number.isInteger(raw) || raw < 1) {
956
- throw new IntegrityError(`registry row has a malformed schemaVersion (${shown(raw)}): ${ctx}`);
1522
+ throw new IntegrityError(`registry row has a malformed schemaVersion (${shown3(raw)}): ${ctx}`);
957
1523
  }
958
1524
  if (raw < REGISTRY_SCHEMA_VERSION) {
959
1525
  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}`
1526
+ `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
1527
  );
962
1528
  }
963
1529
  if (raw > REGISTRY_SCHEMA_VERSION) {
@@ -980,7 +1546,7 @@ function tokenParts(token, ctx, what = "token") {
980
1546
  const match = typeof token === "string" ? INCARNATION_TOKEN.exec(token) : null;
981
1547
  if (match === null) {
982
1548
  throw new IntegrityError(
983
- `registry row ${what} is not one a shipped registry writes (${shown(JSON.stringify(token))}): ${ctx}`
1549
+ `registry row ${what} is not one a shipped registry writes (${shown3(JSON.stringify(token))}): ${ctx}`
984
1550
  );
985
1551
  }
986
1552
  const counter = Number(match[2]);
@@ -1025,7 +1591,7 @@ function newIncarnationToken(entropy, tombstone) {
1025
1591
  const counter = tombstone === void 0 ? 0 : tokenParts(tombstone.token, tombstone.segment).counter + 1;
1026
1592
  return `${drawIncarnation(entropy)}.${counter}.${drawWrite(entropy)}`;
1027
1593
  }
1028
- function shown(v) {
1594
+ function shown3(v) {
1029
1595
  const text = String(v);
1030
1596
  return text.length > 64 ? `${text.slice(0, 64)}\u2026` : text;
1031
1597
  }
@@ -1044,7 +1610,7 @@ function parseRegistryEnvelope(text, ctx, expected) {
1044
1610
  const extra = Object.keys(env).filter((k) => !ENVELOPE_FIELDS.includes(k));
1045
1611
  if (extra.length > 0) {
1046
1612
  throw new IntegrityError(
1047
- `registry row has fields its envelope does not declare (${shown(extra.join(", "))}): ${ctx}`
1613
+ `registry row has fields its envelope does not declare (${shown3(extra.join(", "))}): ${ctx}`
1048
1614
  );
1049
1615
  }
1050
1616
  if (typeof env.deleted !== "boolean" || env.record === null || typeof env.record !== "object") {
@@ -1074,7 +1640,7 @@ function recordFromNew(ref, rec, now, token) {
1074
1640
  retention: rec.retention,
1075
1641
  residency: rec.residency,
1076
1642
  summary: rec.summary,
1077
- keptGens: rec.keptGens,
1643
+ knownGens: rec.knownGens,
1078
1644
  // A new row has no holder: a lease is written only against a row that already exists.
1079
1645
  leases: void 0,
1080
1646
  createdAt: now,
@@ -1089,32 +1655,32 @@ function assertStoredRecordShape(r, ctx) {
1089
1655
  }
1090
1656
  if (r.currentGen !== null && (!Number.isSafeInteger(r.currentGen) || r.currentGen < 0)) {
1091
1657
  throw new IntegrityError(
1092
- `registry record has an invalid currentGen (${shown(r.currentGen)}): ${ctx}`
1658
+ `registry record has an invalid currentGen (${shown3(r.currentGen)}): ${ctx}`
1093
1659
  );
1094
1660
  }
1095
1661
  if (!STATUSES.includes(r.status)) {
1096
- throw new IntegrityError(`registry record has an unknown status (${shown(r.status)}): ${ctx}`);
1662
+ throw new IntegrityError(`registry record has an unknown status (${shown3(r.status)}): ${ctx}`);
1097
1663
  }
1098
1664
  const declared = RECORD_FIELDS;
1099
1665
  const extra = Object.keys(r).filter((k) => !declared.includes(k));
1100
1666
  if (extra.length > 0) {
1101
1667
  throw new IntegrityError(
1102
- `registry record has fields a schema-${REGISTRY_SCHEMA_VERSION} row does not declare (${shown(extra.join(", "))}): ${ctx}`
1668
+ `registry record has fields a schema-${REGISTRY_SCHEMA_VERSION} row does not declare (${shown3(extra.join(", "))}): ${ctx}`
1103
1669
  );
1104
1670
  }
1105
1671
  if (r.keyId !== void 0 && typeof r.keyId !== "string") {
1106
1672
  throw new IntegrityError(`registry record has an invalid keyId: ${ctx}`);
1107
1673
  }
1108
- validateWrappedDeks(r.wrappedDeks, true);
1674
+ validateWrappedDeks(r.wrappedDeks, "stored");
1109
1675
  for (const field of ["retention", "residency"]) {
1110
1676
  const meta = r[field];
1111
1677
  if (meta !== void 0 && (meta === null || typeof meta !== "object" || Array.isArray(meta))) {
1112
1678
  throw new IntegrityError(`registry record has a non-object ${field}: ${ctx}`);
1113
1679
  }
1114
1680
  }
1115
- validateSummary(r.summary, true, ctx);
1116
- validateKeptGens(r.keptGens, true, ctx);
1117
- validateLeases(r.leases, true, ctx);
1681
+ validateSummary(r.summary, "stored", ctx);
1682
+ validateKnownGens(r.knownGens, "stored", ctx);
1683
+ validateLeases(r.leases, "stored", ctx);
1118
1684
  }
1119
1685
  function applyRegistryPatch(prev, patch, now, token) {
1120
1686
  const currentGen = "currentGen" in patch ? patch.currentGen ?? null : prev.currentGen;
@@ -1128,12 +1694,12 @@ function applyRegistryPatch(prev, patch, now, token) {
1128
1694
  const kept = currentGen === prev.currentGen ? prev.summary : void 0;
1129
1695
  summary = kept !== void 0 && summaryAgreesWithKeys(kept, wrappedDeks) ? kept : void 0;
1130
1696
  }
1131
- let keptGens;
1132
- if ("keptGens" in patch) {
1133
- validateKeptGensBelow(patch.keptGens, currentGen);
1134
- keptGens = patch.keptGens;
1697
+ let knownGens;
1698
+ if ("knownGens" in patch) {
1699
+ validateKnownGensFit(patch.knownGens, currentGen, summary);
1700
+ knownGens = patch.knownGens;
1135
1701
  } else {
1136
- keptGens = currentGen === prev.currentGen ? prev.keptGens : void 0;
1702
+ knownGens = currentGen === prev.currentGen ? prev.knownGens : void 0;
1137
1703
  }
1138
1704
  let leases;
1139
1705
  if ("leases" in patch) {
@@ -1156,7 +1722,7 @@ function applyRegistryPatch(prev, patch, now, token) {
1156
1722
  retention: "retention" in patch ? patch.retention : prev.retention,
1157
1723
  residency: "residency" in patch ? patch.residency : prev.residency,
1158
1724
  summary,
1159
- keptGens,
1725
+ knownGens,
1160
1726
  leases,
1161
1727
  createdAt: prev.createdAt,
1162
1728
  updatedAt: now,
@@ -1210,6 +1776,16 @@ export {
1210
1776
  isCapabilityError,
1211
1777
  isBudgetExceededError,
1212
1778
  isKeyUnavailableError,
1779
+ requireInteger,
1780
+ requirePositiveInteger,
1781
+ requireFiniteNumber,
1782
+ U32_MAX,
1783
+ CHUNK_COUNT,
1784
+ splitId,
1785
+ MAX_REMAINDER,
1786
+ joinId,
1787
+ mapWithConcurrency,
1788
+ boundedAll,
1213
1789
  PREAMBLE_BYTES,
1214
1790
  FOOTER_BYTES,
1215
1791
  encodeNameForKey,
@@ -1225,19 +1801,19 @@ export {
1225
1801
  validateUserRef,
1226
1802
  validateChunkKeyOrder,
1227
1803
  validateChunkRef,
1228
- mapWithConcurrency,
1804
+ validateChunkKey,
1229
1805
  assertRegistryCanWrite,
1230
1806
  STORAGE_BACKEND,
1231
1807
  brandAsBackend,
1232
1808
  isStorageBackend,
1233
1809
  MAX_METADATA_BYTES,
1234
- canonicalMetadataJson,
1235
1810
  metadataBytes,
1236
1811
  copiedMetadata,
1237
1812
  metadataFromBytes,
1238
1813
  sizePart,
1239
1814
  fingerprintFor,
1240
1815
  fingerprintParts,
1816
+ footerCrcOf,
1241
1817
  compactFingerprint,
1242
1818
  incarnationOf,
1243
1819
  incarnationField,
@@ -1246,6 +1822,19 @@ export {
1246
1822
  renewsPointer,
1247
1823
  renewPointer,
1248
1824
  pointerIdOf,
1825
+ aadFor,
1826
+ sameKeys,
1827
+ sizeOfFingerprint,
1828
+ usableSummary,
1829
+ summaryAgrees,
1830
+ metadataToCarry,
1831
+ summaryOf,
1832
+ ownSummary,
1833
+ DEFAULT_RETRY_POLICY,
1834
+ jitteredWait,
1835
+ retryRead,
1836
+ checkedRetryPolicy,
1837
+ withRetry,
1249
1838
  MAX_LEASE_MS,
1250
1839
  LEASE_SKEW_MS,
1251
1840
  MAX_LEASES_PER_SEGMENT,
@@ -1257,9 +1846,19 @@ export {
1257
1846
  leaseChurn,
1258
1847
  takeLease,
1259
1848
  releaseLease,
1260
- MAX_KEPT_GENERATIONS,
1261
- usableKeptGens,
1262
- nextKept,
1849
+ knownGensShapeFault,
1850
+ ascending,
1851
+ MAX_STORED_KNOWN_GENERATIONS,
1852
+ usableKnownGens,
1853
+ windowOf,
1854
+ KNOWN_SPAN,
1855
+ keptWindow,
1856
+ nextKnown,
1857
+ rollbackKnown,
1858
+ pruneKnown,
1859
+ describingKnown,
1860
+ isFullLedger,
1861
+ fallbackBound,
1263
1862
  validateNewRegistryRecord,
1264
1863
  validateRegistryPatch,
1265
1864
  serializeRegistryEnvelope,
@@ -1274,6 +1873,10 @@ export {
1274
1873
  entropyIsAvailable,
1275
1874
  DEFAULT_NAMESPACE,
1276
1875
  namespaceKeyPart,
1277
- namespacePathPart
1876
+ namespacePathPart,
1877
+ refuseUnknownOptions,
1878
+ checkNow,
1879
+ requirePositiveInt,
1880
+ requireBool
1278
1881
  };
1279
- //# sourceMappingURL=chunk-YSC7NJWV.js.map
1882
+ //# sourceMappingURL=chunk-IGLIZBR7.js.map