@cloudbitmaps/core 0.1.0-rc.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 (76) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +12 -0
  3. package/README.md +22 -0
  4. package/dist/azure/index.cjs +368 -0
  5. package/dist/azure/index.cjs.map +1 -0
  6. package/dist/azure/index.d.cts +48 -0
  7. package/dist/azure/index.d.ts +48 -0
  8. package/dist/azure/index.js +304 -0
  9. package/dist/azure/index.js.map +1 -0
  10. package/dist/cassandra/index.cjs +265 -0
  11. package/dist/cassandra/index.cjs.map +1 -0
  12. package/dist/cassandra/index.d.cts +52 -0
  13. package/dist/cassandra/index.d.ts +52 -0
  14. package/dist/cassandra/index.js +204 -0
  15. package/dist/cassandra/index.js.map +1 -0
  16. package/dist/chunk-2YDULGXS.js +203 -0
  17. package/dist/chunk-2YDULGXS.js.map +1 -0
  18. package/dist/chunk-7LMLYSVJ.js +43 -0
  19. package/dist/chunk-7LMLYSVJ.js.map +1 -0
  20. package/dist/chunk-AS6ODRLT.js +6 -0
  21. package/dist/chunk-AS6ODRLT.js.map +1 -0
  22. package/dist/chunk-NUIDEEFZ.js +91 -0
  23. package/dist/chunk-NUIDEEFZ.js.map +1 -0
  24. package/dist/chunk-SNJVZ227.js +35 -0
  25. package/dist/chunk-SNJVZ227.js.map +1 -0
  26. package/dist/dynamodb/index.cjs +731 -0
  27. package/dist/dynamodb/index.cjs.map +1 -0
  28. package/dist/dynamodb/index.d.cts +106 -0
  29. package/dist/dynamodb/index.d.ts +106 -0
  30. package/dist/dynamodb/index.js +460 -0
  31. package/dist/dynamodb/index.js.map +1 -0
  32. package/dist/gcs/index.cjs +343 -0
  33. package/dist/gcs/index.cjs.map +1 -0
  34. package/dist/gcs/index.d.cts +46 -0
  35. package/dist/gcs/index.d.ts +46 -0
  36. package/dist/gcs/index.js +279 -0
  37. package/dist/gcs/index.js.map +1 -0
  38. package/dist/index.cjs +4031 -0
  39. package/dist/index.cjs.map +1 -0
  40. package/dist/index.d.cts +1945 -0
  41. package/dist/index.d.ts +1945 -0
  42. package/dist/index.js +3642 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/mongodb/index.cjs +260 -0
  45. package/dist/mongodb/index.cjs.map +1 -0
  46. package/dist/mongodb/index.d.cts +45 -0
  47. package/dist/mongodb/index.d.ts +45 -0
  48. package/dist/mongodb/index.js +199 -0
  49. package/dist/mongodb/index.js.map +1 -0
  50. package/dist/mysql/index.cjs +281 -0
  51. package/dist/mysql/index.cjs.map +1 -0
  52. package/dist/mysql/index.d.cts +56 -0
  53. package/dist/mysql/index.d.ts +56 -0
  54. package/dist/mysql/index.js +211 -0
  55. package/dist/mysql/index.js.map +1 -0
  56. package/dist/ports-D3BrJ6ax.d.cts +357 -0
  57. package/dist/ports-D3BrJ6ax.d.ts +357 -0
  58. package/dist/postgres/index.cjs +273 -0
  59. package/dist/postgres/index.cjs.map +1 -0
  60. package/dist/postgres/index.d.cts +54 -0
  61. package/dist/postgres/index.d.ts +54 -0
  62. package/dist/postgres/index.js +203 -0
  63. package/dist/postgres/index.js.map +1 -0
  64. package/dist/redis/index.cjs +257 -0
  65. package/dist/redis/index.cjs.map +1 -0
  66. package/dist/redis/index.d.cts +42 -0
  67. package/dist/redis/index.d.ts +42 -0
  68. package/dist/redis/index.js +197 -0
  69. package/dist/redis/index.js.map +1 -0
  70. package/dist/s3/index.cjs +891 -0
  71. package/dist/s3/index.cjs.map +1 -0
  72. package/dist/s3/index.d.cts +112 -0
  73. package/dist/s3/index.d.ts +112 -0
  74. package/dist/s3/index.js +555 -0
  75. package/dist/s3/index.js.map +1 -0
  76. package/package.json +172 -0
@@ -0,0 +1,1945 @@
1
+ import { a as IWarmDriver, h as ColdChunkSource, S as SegmentRef, W as WarmReadOptions, i as SegmentSize, I as IColdDriver, d as IRegistryDriver, j as IKeystore, b as ChunkRef, C as ColdCaps, G as GenKey, B as BlobSink, R as RegCaps, e as RegistryRecord, f as NewRegistryRecord, T as Token, g as RegistryPatch, c as WarmRow, N as NoRow, k as CrbmCrypto, l as BlobReader, m as WrappedDek, A as Aead, n as AeadSealed } from './ports-D3BrJ6ax.js';
2
+ export { o as BufferReader, p as BufferSink, q as GovernanceMeta, r as NO_ROW, s as RegistryStatus, t as aadFor } from './ports-D3BrJ6ax.js';
3
+
4
+ /**
5
+ * The **bitmap-codec seam**.
6
+ *
7
+ * `core/` is **codec-agnostic**: `SegmentEngine`, compaction, and the `.crbm` read/write helpers only ever
8
+ * construct and combine bitmaps through the {@link CodecInterface} factory + the {@link CodecBitmap} value type
9
+ * defined here — never a concrete implementation. The flagship codec is roaring (`roaringCodec`, today in
10
+ * `core/bitmap.ts`; it moves to `@cloudbitmaps/roaring` when the package split lands); `@cloudbitmaps/bitmap`
11
+ * (plain bitset) and `@cloudbitmaps/soaring` plug in behind the same seam with zero engine or driver changes.
12
+ *
13
+ * **Homogeneity contract:** a single store uses a single codec, so every {@link CodecBitmap} an operation sees
14
+ * was produced by the same {@link CodecInterface}. The binary set ops ({@link CodecBitmap.orInPlace} etc.) may
15
+ * therefore assume `other` is the same concrete type and are not required to interoperate across codecs.
16
+ * (Corollary: a decoded-chunk HOT cache is codec-specific — never share one `cache` across engines built with
17
+ * different codecs, or a cached bitmap from codec A could reach codec B's in-place op. The `CloudRoaring` facade
18
+ * mints the cache per store, so this cannot arise in normal use.)
19
+ *
20
+ * **Interface surface = exactly what the engine needs**, kept general enough for the known fast-follow codecs
21
+ * (positional/rank-select access + raw-bitset interop that `@cloudbitmaps/bitmap` will add live on that
22
+ * package's own extended value type, not here — the engine never calls them). The one codec-linked concern
23
+ * this seam intentionally leaves in the format layer is the `.crbm` **serialization id** (`ROARING_PORTABLE_ID`
24
+ * in `crbm/format.ts`): it is stamped in the footer and validated on read, and generalizes to a per-codec id
25
+ * *with* the second codec's format work — a documented follow-up, not part of this interface.
26
+ */
27
+ /**
28
+ * The value type a codec produces — a mutable set of `u32` with set algebra and portable (de)serialization.
29
+ * This is the shape `SafeBitmap` already has; the engine holds these, caches them, and merges tiers with them.
30
+ */
31
+ interface CodecBitmap {
32
+ /** Serialize with the codec's **stable, portable** format (never a frozen/unsafe variant). */
33
+ serialize(): Uint8Array;
34
+ add(value: number): void;
35
+ addMany(values: Iterable<number>): void;
36
+ remove(value: number): void;
37
+ removeMany(values: Iterable<number>): void;
38
+ has(value: number): boolean;
39
+ /** Cardinality. */
40
+ readonly size: number;
41
+ readonly isEmpty: boolean;
42
+ /** A deep copy — mutating the clone must not touch the original (the HOT cache relies on this). */
43
+ clone(): CodecBitmap;
44
+ /** In-place union `this = this ∪ other`. `other` is from the same codec (see the homogeneity contract). */
45
+ orInPlace(other: CodecBitmap): void;
46
+ /** In-place difference `this = this \ other`. */
47
+ andNotInPlace(other: CodecBitmap): void;
48
+ /** In-place intersection `this = this ∩ other`. */
49
+ andInPlace(other: CodecBitmap): void;
50
+ /** Ascending iterator over the set values. */
51
+ [Symbol.iterator](): IterableIterator<number>;
52
+ toArray(): number[];
53
+ }
54
+ /**
55
+ * A pluggable bitmap codec — the factory the codec-agnostic engine constructs {@link CodecBitmap}s through.
56
+ * Implementations **must** size-cap before handing untrusted bytes to any native decoder and use a safe
57
+ * (never a trusting/frozen) deserializer (threat model S1).
58
+ */
59
+ interface CodecInterface {
60
+ /** An empty set. */
61
+ empty(): CodecBitmap;
62
+ /** A set seeded from an iterable of `u32` values. */
63
+ fromValues(values: Iterable<number>): CodecBitmap;
64
+ /**
65
+ * Size-cap (`bytes.length <= maxBytes`) then portable-deserialize. Throws `IntegrityError` when the input
66
+ * exceeds the cap or fails to decode — the native decoder is never handed unbounded or unsafe-format input.
67
+ */
68
+ safeDeserialize(bytes: Uint8Array, maxBytes: number): CodecBitmap;
69
+ }
70
+
71
+ /**
72
+ * Per-op denial-of-wallet budget (Decision #3 / invariant T3). A read/admin op is refused
73
+ * **before it fans out** if its projected fan-out would exceed `maxRequests` — so one runaway
74
+ * `count`/`iterate`/`intersect`/`subjectReport`/`eraseSubject` can't drive unbounded RCU/GET cost on a shared
75
+ * backend. The check is O(1) (a single comparison against the already-known fan-out size), so the hot path is
76
+ * untouched. Each request's payload is separately size-capped (the safe-deserialize ceiling), so bounding the
77
+ * fan-out count transitively bounds bytes — the budget is intentionally count-based, not a second byte
78
+ * accumulator that would add per-chunk work.
79
+ *
80
+ * **What the count is.** For the read ops it is the number of Cold **chunk fetches** (`count`/`iterate`: one per
81
+ * effective chunk; `intersect`: surviving keys × operands). For the admin scans it is the number of **segments
82
+ * fanned out to** (`subjectReport`: one tier-merged `has()` each; `eraseSubject`: one force-compaction each) —
83
+ * i.e. it bounds the *breadth* of the fan-out, not the request total of each segment's own compaction. That's
84
+ * the right lever for a runaway (a million-segment sweep); it is not a byte/request meter.
85
+ */
86
+ interface Budget {
87
+ /** Max units of fan-out (Cold chunk fetches, or segments scanned) one op may issue before it is refused. */
88
+ readonly maxRequests: number;
89
+ }
90
+ /**
91
+ * Default: **on but generous** — even a full single-segment scan (≤ 65 536 chunks) or a large single-namespace
92
+ * admin scan stays well under it; it exists to catch a true runaway (a million-segment scan, a pathological
93
+ * intersection). Tune it down for tighter multi-tenant control, or set `budget: false` to disable.
94
+ */
95
+ declare const DEFAULT_BUDGET: Budget;
96
+ /** A store/per-op budget option: partial overrides, or `false` to disable entirely. */
97
+ type BudgetOption = Partial<Budget> | false;
98
+ /** Resolve a budget option against a fallback to a concrete `Budget`, or `null` when disabled. Validates. */
99
+ declare function resolveBudget(opt: BudgetOption | undefined, fallback: Budget): Budget | null;
100
+ /**
101
+ * Resolve a **per-op** budget override against the store's own resolved budget (never the raw global default):
102
+ * `undefined` inherits the store budget as-is (including a disabled `null`); a partial `{}` / omitted
103
+ * `maxRequests` inherits the store's *tightening* (so a per-op passthrough of an absent config value can't
104
+ * silently lift a tight tenant ceiling back to the generous default); `{ maxRequests }` replaces it; `false`
105
+ * disables. Falls back to {@link DEFAULT_BUDGET} only when the store itself has no budget (disabled).
106
+ */
107
+ declare function resolvePerOpBudget(opt: BudgetOption | undefined, storeBudget: Budget | null): Budget | null;
108
+ /**
109
+ * Refuse an op whose projected fan-out exceeds the budget. **Call before fan-out**, with the exact fan-out size
110
+ * the op is about to issue (Cold chunk fetches, or segments to scan). O(1). No-op when `budget` is `null`
111
+ * (disabled).
112
+ */
113
+ declare function checkBudget(budget: Budget | null, projectedRequests: number, op: string): void;
114
+
115
+ /**
116
+ * The determinism seam.
117
+ *
118
+ * `core/` is a pure function of its inputs + injected dependencies — it never reaches for
119
+ * ambient time or randomness. Production wiring supplies real implementations; the deterministic
120
+ * simulator supplies controlled, seeded ones, so any run can be replayed exactly from a seed.
121
+ *
122
+ * These are interfaces only (Phase 0 scaffold) — no feature code yet.
123
+ */
124
+ /** Injected time source. `core/` must never call `Date.now()` directly. */
125
+ interface Clock {
126
+ /** Milliseconds since the Unix epoch. */
127
+ now(): number;
128
+ /**
129
+ * Resolve after at least `ms` have elapsed. The retry/backoff path's only way to wait — `core/` must
130
+ * never reach for `setTimeout` directly (it's timer-free, lint-enforced). Production wiring supplies a
131
+ * `setTimeout`-backed implementation; the simulator supplies a virtual-time one so a backoff schedule is
132
+ * replayable. `ms <= 0` resolves on a microtask without scheduling a timer.
133
+ */
134
+ sleep(ms: number): Promise<void>;
135
+ }
136
+ /** Injected randomness. `core/` must never call `Math.random()` directly. Seedable for simulation. */
137
+ interface Rng {
138
+ /** A float in the half-open interval [0, 1). */
139
+ next(): number;
140
+ }
141
+
142
+ /**
143
+ * Bounded LRU cache with optional TTL — the HOT tier's memory ceiling (finding C6).
144
+ *
145
+ * Time comes from an injected `Clock` (never `Date.now()`) so the cache is deterministic
146
+ * under simulation.
147
+ *
148
+ * The cache can be bounded by entry **count** (`maxEntries`, always) and, optionally, by an aggregate
149
+ * **byte weight** (`maxBytes`). The byte bound exists for values whose footprint is large and uneven — the
150
+ * cold reader cache, where one entry pins a fully-parsed `.crbm` index that can be ~6 MB for a wide segment
151
+ * yet a few hundred bytes for a small one (gap #1: a count-only bound lets 1024 wide indices blow a Lambda's
152
+ * memory even though the count is "in bounds"). Weights arrive **after** insertion (a cached reader is a
153
+ * pending promise; its size is known only once it resolves), so weight is reported via {@link setWeight} and
154
+ * eviction re-runs then.
155
+ */
156
+
157
+ interface LruOptions {
158
+ /** Hard ceiling on entries; the least-recently-used is evicted past it. */
159
+ readonly maxEntries: number;
160
+ /**
161
+ * Optional aggregate byte ceiling across all entries' reported weights (see {@link BoundedLru.setWeight}).
162
+ * When set, the least-recently-used entries are evicted until the total is within budget — in addition to
163
+ * the `maxEntries` bound. Omit for a pure count-bounded cache (weights stay 0, no byte eviction).
164
+ */
165
+ readonly maxBytes?: number;
166
+ /** Optional time-to-live in ms; an entry older than this is treated as absent. */
167
+ readonly ttlMs?: number;
168
+ /** Only `now()` is used (never `sleep`), so a count-only cache can pass a minimal time source. */
169
+ readonly clock: Pick<Clock, 'now'>;
170
+ }
171
+ declare class BoundedLru<K, V> {
172
+ private readonly map;
173
+ private readonly maxEntries;
174
+ private readonly maxBytes;
175
+ private readonly ttlMs;
176
+ private readonly clock;
177
+ private totalBytes;
178
+ constructor(options: LruOptions);
179
+ get(key: K): V | undefined;
180
+ /** Read a value **without** counting as an access (recency unchanged, no purge on expiry). */
181
+ peek(key: K): V | undefined;
182
+ set(key: K, value: V): void;
183
+ /**
184
+ * Report an entry's byte weight (typically once a cached promise resolves and its size is known). Updates
185
+ * the running total and evicts the least-recently-used entries if the aggregate now exceeds `maxBytes`.
186
+ * Recency is **not** changed. The `key` being weighted is protected from this eviction pass (we just
187
+ * populated it) — so a single entry larger than the whole budget is kept rather than immediately dropped.
188
+ */
189
+ setWeight(key: K, bytes: number): void;
190
+ private evictToBudget;
191
+ /** The least-recently-used key that is not `protectKey`, or undefined if none other exists. */
192
+ private oldestExcept;
193
+ private dropKey;
194
+ private drop;
195
+ /** Membership test that does NOT count as an access (recency is unchanged). Purges if expired. */
196
+ has(key: K): boolean;
197
+ delete(key: K): boolean;
198
+ clear(): void;
199
+ /** Current entry count (may include not-yet-evicted expired entries). */
200
+ get size(): number;
201
+ /** Current aggregate reported weight (bytes) across all entries. */
202
+ get weightBytes(): number;
203
+ }
204
+
205
+ /**
206
+ * Metrics seam (Phase 5a) — an injected sink the engine pushes typed events
207
+ * to, exactly like the `Clock`/`Rng`/driver seams keep `core/` pure and storage-agnostic.
208
+ *
209
+ * Design: the library emits **neutral domain events** and stays vendor-agnostic — you (or a ~12-line
210
+ * adapter) map them to OpenTelemetry / Datadog / a log line. The default is a **no-op**, and emission is
211
+ * skipped entirely when no sink is wired (near-zero overhead, no telemetry dependency). Events carry **raw
212
+ * observations** (bytes, counts, ms); turning those into billable units / dollars is the cost model's job
213
+ * (Phase 5b), which keeps rates pluggable.
214
+ *
215
+ * Two caveats for sink authors: `onEvent` runs **synchronously on the I/O path**, so keep it cheap and
216
+ * non-blocking (offload batching/network to your own async queue); and `segment`/`namespace` are
217
+ * caller-controlled strings that may be PII and are **unbounded-cardinality** — do not map them to
218
+ * per-series metric labels unless they're known low-cardinality and PII-free. See the getting-started
219
+ * "Observability" section.
220
+ */
221
+ /** The segment operations that emit an `op` latency event (timed with the injected clock, at the facade). */
222
+ type MetricOpName = 'add' | 'remove' | 'addMany' | 'removeMany' | 'has' | 'count' | 'intersectInto';
223
+ /**
224
+ * One observability event. A discriminated union on `kind` — new variants can be added over time without
225
+ * breaking consumers (they switch on the kinds they care about). `namespace` is the segment's namespace
226
+ * (absent ⇒ the default namespace). Segment names are identifiers, never bitmap contents (threat model:
227
+ * never log PII / bitmap bits).
228
+ */
229
+ type MetricEvent = {
230
+ readonly kind: 'cold.get';
231
+ readonly namespace?: string;
232
+ readonly segment: string;
233
+ /** Bytes returned (0 if the chunk was absent — a GET still happened). */
234
+ readonly bytes: number;
235
+ /**
236
+ * Elapsed wall time of the read — includes any transient-retry backoff on the cold call. From the
237
+ * injected clock, clamped to ≥ 0 (0 under the no-wait test clock).
238
+ */
239
+ readonly ms: number;
240
+ } | {
241
+ readonly kind: 'cache';
242
+ readonly hit: boolean;
243
+ } | {
244
+ readonly kind: 'warm.read';
245
+ readonly namespace?: string;
246
+ readonly segment: string;
247
+ /** Bytes read — a single-row get, or the summed rows of a list scan. */
248
+ readonly bytes: number;
249
+ } | {
250
+ readonly kind: 'warm.write';
251
+ readonly namespace?: string;
252
+ readonly segment: string;
253
+ /** Bytes of the committed delta row (emitted only on a successful conditional write). */
254
+ readonly bytes: number;
255
+ } | {
256
+ readonly kind: 'retry';
257
+ /** `occ` = optimistic-concurrency conflict backoff; `transient` = infrastructure-fault backoff. */
258
+ readonly reason: 'occ' | 'transient';
259
+ /** 1-based number of the attempt about to be retried. */
260
+ readonly attempt: number;
261
+ readonly delayMs: number;
262
+ } | {
263
+ readonly kind: 'intersect';
264
+ readonly operands: number;
265
+ /** Shared **distinct** chunk-keys selected to fetch (present in every operand). */
266
+ readonly fetchedChunks: number;
267
+ /**
268
+ * **Distinct** chunk-keys pruned — never fetched (the chunk-skipping saving). Counts distinct keys,
269
+ * not per-operand GETs, so it under-states the true GET saving when 3+ operands partially overlap.
270
+ */
271
+ readonly skippedChunks: number;
272
+ } | {
273
+ readonly kind: 'compaction';
274
+ readonly namespace?: string;
275
+ readonly segment: string;
276
+ /** True iff this call committed a new generation. */
277
+ readonly compacted: boolean;
278
+ /** Why nothing was committed (when `compacted` is false) — the `CompactionResult.reason` (Phase D). */
279
+ readonly reason?: string;
280
+ /** Dirty Warm chunks pinned at scan time. */
281
+ readonly dirtyChunks: number;
282
+ /** Archived Warm rows actually purged (≤ dirtyChunks). */
283
+ readonly purged: number;
284
+ /** Elapsed wall time of the attempt (injected clock, ≥ 0; 0 under the no-wait test clock). */
285
+ readonly ms: number;
286
+ } | {
287
+ readonly kind: 'op';
288
+ readonly name: MetricOpName;
289
+ readonly ms: number;
290
+ };
291
+ /**
292
+ * The sink you plug in. One method, synchronous, fire-and-forget. It must never throw back into the
293
+ * caller — `CloudRoaring` wraps a user sink with {@link safeMetrics} so a buggy sink can't break I/O.
294
+ */
295
+ interface IMetricsSink {
296
+ onEvent(event: MetricEvent): void;
297
+ }
298
+ /** The default sink: discards everything, zero overhead. Used whenever no sink is wired. */
299
+ declare const NOOP_METRICS: IMetricsSink;
300
+ /**
301
+ * Wrap a sink so a throwing/buggy `onEvent` can never break the data path — observability is strictly
302
+ * best-effort. `CloudRoaring` applies this to the user-supplied sink at construction.
303
+ */
304
+ declare function safeMetrics(sink: IMetricsSink): IMetricsSink;
305
+ /** Accumulated totals — the shape returned by {@link CountingMetricsSink.snapshot}. */
306
+ interface MetricsSnapshot {
307
+ readonly cold: {
308
+ readonly gets: number;
309
+ readonly bytes: number;
310
+ readonly totalMs: number;
311
+ };
312
+ readonly warm: {
313
+ readonly reads: number;
314
+ readonly readBytes: number;
315
+ readonly writes: number;
316
+ readonly writeBytes: number;
317
+ };
318
+ readonly cache: {
319
+ readonly hits: number;
320
+ readonly misses: number;
321
+ };
322
+ readonly retries: {
323
+ readonly occ: number;
324
+ readonly transient: number;
325
+ };
326
+ readonly intersect: {
327
+ readonly calls: number;
328
+ readonly fetchedChunks: number;
329
+ readonly skippedChunks: number;
330
+ };
331
+ readonly compaction: {
332
+ readonly attempts: number;
333
+ readonly committed: number;
334
+ readonly dirtyChunks: number;
335
+ readonly purged: number;
336
+ readonly totalMs: number;
337
+ };
338
+ readonly ops: Readonly<Record<MetricOpName, {
339
+ readonly count: number;
340
+ readonly totalMs: number;
341
+ }>>;
342
+ }
343
+ /**
344
+ * A ready-made sink that tallies events into a {@link MetricsSnapshot} — handy for tests, quick scripts,
345
+ * and (Phase 5b) as the grounded-request-cost source behind `costReport()`. `snapshot()` returns an
346
+ * independent copy; `reset()` zeroes the counters.
347
+ */
348
+ declare class CountingMetricsSink implements IMetricsSink {
349
+ private cold;
350
+ private warm;
351
+ private cache;
352
+ private retries;
353
+ private intersect;
354
+ private compaction;
355
+ private ops;
356
+ private static zeroOps;
357
+ onEvent(event: MetricEvent): void;
358
+ snapshot(): MetricsSnapshot;
359
+ reset(): void;
360
+ }
361
+
362
+ /**
363
+ * `withRetry` — the one retry/backoff primitive.
364
+ *
365
+ * A pure, storage-agnostic helper: it knows nothing about HTTP, AWS, or any SDK. It retries an operation
366
+ * while the operation keeps failing with a **retryable** error (by default {@link TransientError}), waiting
367
+ * a bounded, jittered, exponentially-growing delay between attempts. Time and randomness are **injected**
368
+ * (the determinism seam) — production wires a `setTimeout`-backed clock + a real RNG; the
369
+ * simulator wires virtual ones — so a backoff schedule is replayable from a seed and unit tests run with no
370
+ * real sleeping.
371
+ *
372
+ * It deliberately does **not** retry deterministic failures ({@link WriteConflictError},
373
+ * {@link ValidationError}, {@link IntegrityError}, {@link NotFoundError}, …): retrying those either can't
374
+ * help or would be incorrect. OCC conflicts are retried by a *separate* loop (the engine's read-modify-write)
375
+ * because each retry must re-read and re-apply, not blindly replay the same call.
376
+ */
377
+
378
+ interface RetryPolicy {
379
+ /** Total attempts including the first (so `maxAttempts: 4` = 1 try + 3 retries). Must be ≥ 1. */
380
+ readonly maxAttempts: number;
381
+ /** Delay before the first retry, in ms. Grows by `backoffFactor` each subsequent retry. */
382
+ readonly baseDelayMs: number;
383
+ /** Upper bound on a single (pre-jitter) delay, in ms — caps the exponential growth. */
384
+ readonly maxDelayMs: number;
385
+ /** Exponential growth factor between retries (e.g. 2 ⇒ base, 2·base, 4·base, …). */
386
+ readonly backoffFactor: number;
387
+ /**
388
+ * `'full'` ⇒ the actual wait is uniform in `[0, computed]` (AWS-recommended full jitter: decorrelates
389
+ * concurrent retriers so they don't reconverge into a thundering herd). `'none'` ⇒ wait exactly `computed`.
390
+ */
391
+ readonly jitter: 'full' | 'none';
392
+ }
393
+ /**
394
+ * Conservative defaults: 4 attempts, 50ms → 100 → 200 (×2), capped at 2s, full jitter. Tuned for a cloud
395
+ * backend's brief throttle/5xx blip — enough to ride out a transient fault without turning a hard outage
396
+ * into a long hang. Override per store/driver if your latency budget differs.
397
+ */
398
+ declare const DEFAULT_RETRY_POLICY: RetryPolicy;
399
+ /** Backoff for the engine's OCC conflict loop — local contention resolves fast, so smaller + tighter. */
400
+ declare const DEFAULT_OCC_BACKOFF: RetryPolicy;
401
+ interface RetryDeps {
402
+ readonly clock: Clock;
403
+ readonly rng: Rng;
404
+ /** Override which errors are retryable. Default: any {@link TransientError} (incl. `TimeoutError`). */
405
+ readonly isRetryable?: (err: unknown) => boolean;
406
+ /** Optional hook (observability) fired before each backoff wait. `attempt` is 1-based (the one that failed). */
407
+ readonly onRetry?: (info: {
408
+ attempt: number;
409
+ delayMs: number;
410
+ err: unknown;
411
+ }) => void;
412
+ }
413
+ /** Default classifier: retry transient infrastructure faults only. */
414
+ declare function isTransient(err: unknown): boolean;
415
+ /**
416
+ * Run `op`, retrying transient failures per `policy`. Resolves with `op`'s result, or rejects with the last
417
+ * error once attempts are exhausted (or immediately for a non-retryable error). The thrown error is always
418
+ * the *operation's* error — never a wrapper — so callers keep their typed-error branching.
419
+ */
420
+ declare function withRetry<T>(op: () => Promise<T>, policy: RetryPolicy, deps: RetryDeps): Promise<T>;
421
+
422
+ interface EngineDeps {
423
+ readonly warm: IWarmDriver;
424
+ readonly cold: ColdChunkSource;
425
+ /** Optional HOT cache of decoded (immutable) Cold chunks. */
426
+ readonly cache?: BoundedLru<string, CodecBitmap>;
427
+ readonly maxBitmapBytes?: number;
428
+ /**
429
+ * The bitmap codec (DECISIONS #58) — **required**. `core/` is
430
+ * codec-agnostic: it can have no default, because the concrete codec lives in a *flavor* package that depends
431
+ * on core (a default here would invert that arrow). A flavor's facade injects it — `@cloudbitmaps/roaring`
432
+ * passes `roaringCodec` — so applications never see this.
433
+ */
434
+ readonly codec: CodecInterface;
435
+ readonly maxRetries?: number;
436
+ /** Injected for jittered OCC backoff + replayable tests; defaults to an instant clock / zero RNG. */
437
+ readonly clock?: Clock;
438
+ readonly rng?: Rng;
439
+ /** Backoff schedule between OCC conflict retries; defaults to {@link DEFAULT_OCC_BACKOFF}. */
440
+ readonly occBackoff?: RetryPolicy;
441
+ /** Observability sink (Phase 5a); defaults to a no-op. Assumed exception-safe (the facade wraps it). */
442
+ readonly metrics?: IMetricsSink;
443
+ /**
444
+ * Warm read consistency for the READ paths (`has`/`count`/`iterate`/`intersect`); default `'strong'`
445
+ * (read-your-writes). `'eventual'` trades read-after-write for ~½ the DynamoDB RCU (gap #9). The OCC
446
+ * read-modify-write path is always strong regardless (correctness).
447
+ */
448
+ readonly warmReadConsistency?: 'strong' | 'eventual';
449
+ /**
450
+ * Max Warm chunk writes in flight per `addMany`/`removeMany` (the bounded flusher). Default **1** (serial —
451
+ * unchanged behavior). Higher values fan out distinct-chunk writes for throughput; each chunk is its own OCC
452
+ * row, so bounded concurrency is safe (`addMany` is already non-atomic on partial failure).
453
+ */
454
+ readonly writeConcurrency?: number;
455
+ /**
456
+ * Per-op denial-of-wallet budget (07 Decision #3 / T3), already resolved by the facade: a {@link Budget} to
457
+ * enforce, or `null` to disable. Undefined ⇒ {@link DEFAULT_BUDGET} (a direct engine construction still gets
458
+ * the generous default). Read ops refuse before fan-out if they'd exceed it.
459
+ */
460
+ readonly budget?: Budget | null;
461
+ }
462
+ declare class SegmentEngine {
463
+ private readonly warm;
464
+ private readonly cold;
465
+ private readonly cache;
466
+ private readonly codec;
467
+ private readonly maxBitmapBytes;
468
+ private readonly maxRetries;
469
+ private readonly clock;
470
+ private readonly rng;
471
+ private readonly occBackoff;
472
+ private readonly metrics;
473
+ private readonly metricsOn;
474
+ /** Consistency for READ-path Warm fetches (precomputed; OCC RMW passes strong explicitly, see `warmGet`). */
475
+ private readonly readOpts;
476
+ private readonly writeConcurrency;
477
+ /** Resolved per-op budget (null = disabled); undefined deps ⇒ the generous default. See {@link checkBudget}. */
478
+ private readonly budget;
479
+ constructor(deps: EngineDeps);
480
+ add(seg: SegmentRef, id: number): Promise<void>;
481
+ remove(seg: SegmentRef, id: number): Promise<void>;
482
+ addMany(seg: SegmentRef, ids: Iterable<number>): Promise<void>;
483
+ removeMany(seg: SegmentRef, ids: Iterable<number>): Promise<void>;
484
+ has(seg: SegmentRef, id: number, opts?: WarmReadOptions): Promise<boolean>;
485
+ count(seg: SegmentRef): Promise<number>;
486
+ /** Whether the Cold source can measure segment size (for grounded cost); false ⇒ storage isn't grounded. */
487
+ get supportsColdSize(): boolean;
488
+ /** Grounded Cold size of a segment's current generation (cost reporting), or null if it has no generation. */
489
+ segmentSize(seg: SegmentRef): Promise<SegmentSize | null>;
490
+ iterate(seg: SegmentRef): AsyncGenerator<number>;
491
+ /**
492
+ * Chunk-skipping intersection. Computes
493
+ * `seg[0] ∩ seg[1] ∩ …` and streams the result ids ascending. It aligns each segment's *effective*
494
+ * chunk-key set (Warm ∪ Cold), keeps only keys present in **all** operands (a key missing from any
495
+ * operand can't contribute → its **Cold chunks are never fetched** — the core saving), then for each
496
+ * surviving key merges tiers per operand (operand fetches run in parallel) and hands the buffers to the
497
+ * roaring engine for the AND, streaming results through a bounded in-flight window.
498
+ *
499
+ * **Memory:** the Cold payload footprint is bounded by the window (`concurrency × operands × chunk`), not
500
+ * by segment size — that's the Lambda-friendly property. The **Warm** side, however, is snapshotted up
501
+ * front (`collectWarm` per operand), so total memory also carries each operand's Warm size. Under
502
+ * Topology-A (read-mostly; compaction keeps Warm small) that's negligible; a streaming Warm/Cold
503
+ * merge-join for large Warm is Phase 4.
504
+ *
505
+ * Generation-consistent within the call (normal case): each operand's current generation is resolved **once**
506
+ * up front (before the fan-out) and threaded into every chunk read, so a concurrent compaction can't corrupt
507
+ * or tear the result — every chunk read is a whole, checksum-verified, immutable generation (finding C10).
508
+ * The edge a *long* call can hit: if it straddles a mid-call `coldGenTtlMs` boundary and a compaction has
509
+ * committed, an operand's not-yet-read chunks may re-resolve forward to the newer generation (a generation
510
+ * hop within one long call) — the call never crashes or returns a torn object, but may mix generations. That
511
+ * window is a single TTL (default 2 s), not the multi-cycle GC-sweep window it took pre-Phase-B. A shorter call
512
+ * is unaffected **unless the reader cache evicts an operand mid-call** (`maxOpenSegments`, gap #1): the re-read
513
+ * re-resolves fresh (bypassing the TTL), which can hop generations even sub-TTL — still whole/immutable per
514
+ * read, never torn. Full intra-op snapshot isolation is deferred (gap #4).
515
+ */
516
+ intersect(segs: readonly SegmentRef[], options?: {
517
+ concurrency?: number;
518
+ budget?: BudgetOption;
519
+ }): AsyncGenerator<number>;
520
+ /**
521
+ * The AND of one chunk key across all operands (tier-merged per operand), or `null` if empty. Operand
522
+ * chunks are fetched **in parallel** (the spec's parallel byte-range reads). `acc` takes ownership of the
523
+ * first operand's effective chunk and mutates it in place — safe because `effectiveChunk` always returns
524
+ * a FRESH bitmap (never a cached/shared Cold instance), so this can't poison the HOT cache.
525
+ */
526
+ private intersectChunk;
527
+ /**
528
+ * Materialize `seg[0] ∩ seg[1] ∩ …` into `dest` via batched `addMany` (streaming, bounded memory).
529
+ * **Not atomic** — like `addMany`, ids land chunk-by-chunk; a failure mid-stream leaves a partial result.
530
+ * `dest` is added to, not replaced.
531
+ */
532
+ intersectInto(dest: SegmentRef, segs: readonly SegmentRef[], options?: {
533
+ concurrency?: number;
534
+ batchSize?: number;
535
+ }): Promise<void>;
536
+ private mutateMany;
537
+ /** OCC read-modify-write on a chunk's Warm delta, retrying on conflict (bounded). */
538
+ private readModifyWrite;
539
+ /** `warm.get` + a `warm.read` metric (read bytes = the row size, 0 if absent). */
540
+ private warmGet;
541
+ private collectWarm;
542
+ private chunkKeys;
543
+ /**
544
+ * Tier-derived keys are untrusted (invariant 5) — fail fast on a corrupt/out-of-range key rather than
545
+ * letting it flow into `joinId` and produce a bogus id.
546
+ */
547
+ private assertChunkKeyInRange;
548
+ private effectiveChunk;
549
+ /** The segment's current generation, resolved once per op (`undefined` ⇒ source can't report it). */
550
+ private currentGen;
551
+ /**
552
+ * Decode a Cold chunk. `gen` is the segment's current generation, resolved **once per op** by the caller (not
553
+ * per chunk — that would put a registry re-resolve on every chunk of a count/intersect). The HOT cache is
554
+ * keyed by it (gap #4), so a compaction that advances the generation misses the cache and re-reads the new
555
+ * bytes instead of serving a stale decoded chunk (an erased id can't resurrect from a cached pre-compaction
556
+ * chunk). `gen === null` ⇒ the source reports no current generation ⇒ no cold bytes for any chunk, so skip the
557
+ * fetch entirely (also avoids a redundant re-resolve on the warm-only path). `gen === undefined` ⇒ the source
558
+ * can't report a generation (pins one for its lifetime) ⇒ the key stays generation-free (unchanged behaviour).
559
+ * Superseded-generation entries age out under the LRU ceiling — no active purge.
560
+ */
561
+ private coldChunk;
562
+ }
563
+
564
+ /** Pluggable rate card. Rates differ by cloud/region/term and drift over time; the formulas don't. */
565
+ interface PricingProfile {
566
+ readonly name: string;
567
+ readonly cold: {
568
+ /** S3 GET (per **million** requests). A ranged GET bills as a full GET. */
569
+ readonly getPerMillion: number;
570
+ /** S3 PUT (per million). */
571
+ readonly putPerMillion: number;
572
+ readonly storagePerGiBMonth: number;
573
+ };
574
+ readonly warm: {
575
+ /** DynamoDB read-request-units (per million). */
576
+ readonly rruPerMillion: number;
577
+ /** DynamoDB write-request-units (per million). **0 models a provisioned/flat tier** (no per-write charge). */
578
+ readonly wruPerMillion: number;
579
+ /** KiB billed per read unit (DynamoDB: 4). */
580
+ readonly readUnitKiB: number;
581
+ /** KiB billed per write unit (DynamoDB: 1 — the write cost driver). */
582
+ readonly writeUnitKiB: number;
583
+ readonly storagePerGiBMonth: number;
584
+ /** Strongly-consistent reads bill 1 RRU/unit; eventually-consistent bill 0.5. */
585
+ readonly stronglyConsistent: boolean;
586
+ };
587
+ /** The always-on baseline to compare against (e.g. an ElastiCache HA cluster). */
588
+ readonly redis: {
589
+ readonly monthlyUSD: number;
590
+ };
591
+ }
592
+ /**
593
+ * Default profile — **AWS us-east-1, on-demand**, mid-2026, from the fact-checked
594
+ * published pricing rather than copied from a blog post. Override it for your region, cloud, or committed term.
595
+ */
596
+ declare const AWS_US_EAST_1_ONDEMAND: PricingProfile;
597
+ declare const DEFAULT_PRICING: PricingProfile;
598
+ /** Which write path is live. `A` = Hot+Cold read-mostly (writes via bulk-load); `B` = live Warm writes. */
599
+ type Topology = 'A' | 'B';
600
+ /** Sustained access pattern. All rates default to 0; unspecified ⇒ that op contributes nothing. */
601
+ interface Workload {
602
+ readonly readsPerSec?: number;
603
+ readonly writesPerSec?: number;
604
+ readonly intersectsPerSec?: number;
605
+ /** HOT-cache hit rate in `[0, 1]` — hits are free; only misses cost. Default 0. */
606
+ readonly cacheHitRate?: number;
607
+ /** Avg serialized chunk/item size (KiB) — drives read/write units. Default 8 (a full roaring bitmap container). */
608
+ readonly avgItemKiB?: number;
609
+ /** Cold chunks fetched per intersection (the chunk-skipping survivors). Default 1. */
610
+ readonly chunksPerIntersect?: number;
611
+ /**
612
+ * Compaction cycles per month across the modeled data (gap #10). Default **0** ⇒ compaction is not modeled
613
+ * and the report *discloses* the omission (rather than silently under-reporting). Set it to fold in the
614
+ * background job that usually dominates operational cost.
615
+ */
616
+ readonly compactionsPerMonth?: number;
617
+ /**
618
+ * Cold chunks re-read per compaction cycle — the whole generation (clean chunks are copied through, 1 GET
619
+ * each). Default: derived from `coldBytes / avgItemKiB` (i.e. the whole modeled cold set is one generation —
620
+ * exact for a single-segment/grounded report; for a multi-segment fleet set this to your *per-segment* chunk
621
+ * count). This whole-generation re-read is the dominant compaction term.
622
+ */
623
+ readonly chunksPerCompaction?: number;
624
+ /** Dirty Warm rows purged per compaction — the secondary RRU-read + WRU-delete term. Default 0. */
625
+ readonly dirtyChunksPerCompaction?: number;
626
+ }
627
+ /** One (group of) segment(s) for planning. `count` = how many like this (default 1). */
628
+ interface SegmentSizing {
629
+ readonly sizeBytes?: number;
630
+ readonly cardinality?: number;
631
+ /** Number of segments with these characteristics. Default 1. */
632
+ readonly count?: number;
633
+ }
634
+ interface EstimateInput {
635
+ readonly segments: readonly SegmentSizing[];
636
+ readonly workload?: Workload;
637
+ readonly topology?: Topology;
638
+ readonly pricing?: PricingProfile;
639
+ }
640
+ /**
641
+ * A self-relative cost hint. See {@link CostReport.advisories}.
642
+ *
643
+ * `code` is the stable, machine-readable identity — branch on it rather than parsing `message`.
644
+ */
645
+ interface CostAdvisory {
646
+ /**
647
+ * `'batchable-writes'` — the modeled write count far exceeds the number of distinct Warm rows the data can
648
+ * occupy, so each row is being rewritten many times over. If those ids ever arrive together, `addMany()`
649
+ * collapses them to one write per chunk and bulk-load writes Cold directly.
650
+ */
651
+ readonly code: 'batchable-writes';
652
+ /** Human-readable, safe to log or print. Carries the numbers that triggered it. */
653
+ readonly message: string;
654
+ /** The modeled cost of the term this advisory is about (USD/month). */
655
+ readonly currentUSD: number;
656
+ /**
657
+ * A **floor**, not a promise: the same term if the writes were perfectly batched into one write per distinct
658
+ * Warm row. Real batching lands between this and {@link CostAdvisory.currentUSD}, depending on how the ids
659
+ * actually arrive — which the estimator cannot know.
660
+ */
661
+ readonly batchedFloorUSD: number;
662
+ }
663
+ interface CostReport {
664
+ readonly monthlyUSD: {
665
+ readonly byTier: {
666
+ readonly hot: number;
667
+ readonly warm: number;
668
+ readonly cold: number;
669
+ };
670
+ readonly byOp: {
671
+ readonly reads: number;
672
+ readonly writes: number;
673
+ readonly intersects: number;
674
+ readonly storage: number;
675
+ /** Background compaction (whole-generation re-read + PUT + Warm purge). 0 unless `compactionsPerMonth` is set. */
676
+ readonly compaction: number;
677
+ };
678
+ readonly total: number;
679
+ };
680
+ /**
681
+ * Sustained rate at which the pay-per-use model's cost passes the flat Redis baseline (per axis, the other
682
+ * at 0), **evaluated at this report's `cacheHitRate` + `avgItemKiB`** — so a higher cache-hit rate raises
683
+ * `readsPerSec` (cache hits are free). `Infinity` means that axis never crosses (e.g. a flat/provisioned
684
+ * tier, or a 100% cache-hit rate). The published anchors (~26 writes/s, ~329 reads/s) are at `cacheHitRate: 0`.
685
+ */
686
+ readonly redisCrossover: {
687
+ readonly writesPerSec: number;
688
+ readonly readsPerSec: number;
689
+ };
690
+ readonly verdict: 'win-big' | 'win' | 'lose-zone';
691
+ readonly rationale: string;
692
+ /**
693
+ * Zero or more **self-relative** hints: places this workload is paying more than *this same library* would
694
+ * charge for the same outcome. Distinct from {@link CostReport.verdict}, which only compares against the flat
695
+ * Redis baseline — a workload can beat Redis by 40× and still be 100× more expensive than it needs to be, and
696
+ * the verdict alone would call that `win-big` and say nothing.
697
+ *
698
+ * Empty is the normal case. Advisories never change the dollar figures; they are additive commentary, and each
699
+ * carries a machine-readable {@link CostAdvisory.code} so consumers don't string-match.
700
+ */
701
+ readonly advisories: readonly CostAdvisory[];
702
+ readonly assumptions: {
703
+ readonly cacheHitRate: number;
704
+ readonly pricingName: string;
705
+ /** True when segment **sizes** were real (grounded `costReport`), false for a pure `estimateCost`. */
706
+ readonly grounded: boolean;
707
+ readonly topology: Topology;
708
+ readonly notes: readonly string[];
709
+ };
710
+ }
711
+ /**
712
+ * **Planning** cost estimate — pure, no instance or live data needed (sizing, sales, what-if). Segment sizes
713
+ * are taken as given (or roughly derived from cardinality); use the grounded `segment.costReport()` for
714
+ * exact, real sizes. See {@link CostReport}.
715
+ */
716
+ declare function estimateCost(input: EstimateInput): CostReport;
717
+ /**
718
+ * **Grounded** report from a real segment byte total (from the `.crbm` index) + a supplied workload. Used by
719
+ * `Segment.costReport()` in `index.ts`. `grounded` defaults to true (the size is exact, not estimated); the
720
+ * caller passes `grounded: false` + a note when the Cold source can't measure size.
721
+ */
722
+ declare function groundedReport(input: {
723
+ readonly coldBytes: number;
724
+ readonly grounded?: boolean;
725
+ readonly workload?: Workload;
726
+ readonly topology?: Topology;
727
+ readonly pricing?: PricingProfile;
728
+ readonly extraNotes?: readonly string[];
729
+ /**
730
+ * Hard upper bound on distinct Warm rows, for the advisories. Defaults to {@link CHUNK_COUNT} — this models
731
+ * ONE segment, and a 16-bit chunk key caps a segment there. `SegmentSize` carries only `sizeBytes` today, so
732
+ * an exact per-segment chunk count would need a driver-interface change; the id-space cap needs nothing and is
733
+ * sound in the safe direction (it can only make the advisory quieter).
734
+ */
735
+ readonly maxWarmRows?: number;
736
+ }): CostReport;
737
+
738
+ /**
739
+ * Audit sink (Phase 5d) — an injected, no-op-by-default seam for **security/compliance** events, distinct
740
+ * from the metrics sink (5a). Different audience (an audit log / SIEM, not a dashboard), different retention,
741
+ * and only the compliance-relevant *state changes* — never routine reads/writes (that's the metrics sink).
742
+ * It doubles as the GDPR Art. 30 "record of processing" surface: publishes, compactions, and erasures.
743
+ *
744
+ * Like `Clock`/`Rng`/`IMetricsSink`, it's injected (into the lifecycle operations that emit — compaction,
745
+ * bulk-load, erasure) and wrapped exception-safe, so a buggy sink can never break the operation it observes.
746
+ * Events are vendor-neutral and carry no timestamp/actor — the sink runs synchronously at the event, so it
747
+ * stamps its own time / attaches the caller identity (keeps `core/` free of ambient time). As with metrics,
748
+ * `segment`/`namespace` are caller-controlled strings that may be PII — treat them accordingly when routing.
749
+ *
750
+ * Not (yet) emitted: **KEK rotation** — rotation here is operator-side keystore reconfiguration (key-id-tagged
751
+ * wrappings, no data re-encryption), so there is no library call to hook. A future `rewrapSegment()` op would
752
+ * add a `kek.rotate` variant; until then, audit key changes at your keystore-config layer.
753
+ */
754
+ /** A security/compliance-relevant state change. Vendor-neutral; the sink adds its own timestamp/actor. */
755
+ type AuditEvent = {
756
+ /** A new immutable Cold generation *became the segment's current generation* (via bulk-load publish). */
757
+ readonly kind: 'segment.publish';
758
+ readonly namespace?: string;
759
+ readonly segment: string;
760
+ readonly generation: number;
761
+ } | {
762
+ /** Compaction committed a new generation (emitted at the durable commit, before Warm-row purge). */
763
+ readonly kind: 'segment.compact';
764
+ readonly namespace?: string;
765
+ readonly segment: string;
766
+ readonly generation: number;
767
+ } | {
768
+ /**
769
+ * A segment was **crypto-shredded** — its wrapped DEK(s) are gone, so its at-rest Cold bytes are now
770
+ * permanently unreadable. Emitted only for a genuine key shred, never for a cleartext tombstone (which
771
+ * leaves the Cold bytes readable) or an idempotent re-run.
772
+ */
773
+ readonly kind: 'segment.erase';
774
+ readonly namespace?: string;
775
+ readonly segment: string;
776
+ } | {
777
+ /** A namespace erasure was executed. `segmentsShredded` is the count actually crypto-shredded (may be 0). */
778
+ readonly kind: 'namespace.erase';
779
+ readonly namespace: string;
780
+ readonly segmentsShredded: number;
781
+ };
782
+ /** The `kind` discriminant of an {@link AuditEvent}. */
783
+ type AuditEventKind = AuditEvent['kind'];
784
+ /** Sink for {@link AuditEvent}s. Injected via the lifecycle options; the default is {@link NOOP_AUDIT}. */
785
+ interface IAuditSink {
786
+ onEvent(event: AuditEvent): void;
787
+ }
788
+ /** The default sink: records nothing. */
789
+ declare const NOOP_AUDIT: IAuditSink;
790
+ /** A ready-made sink that records events into an in-memory list — handy for tests + simple audit trails. */
791
+ declare class RecordingAuditSink implements IAuditSink {
792
+ private readonly recorded;
793
+ onEvent(event: AuditEvent): void;
794
+ /** An independent copy of the recorded events, in emission order. */
795
+ snapshot(): AuditEvent[];
796
+ reset(): void;
797
+ }
798
+
799
+ /**
800
+ * Crash-safe compaction — the 2-phase commit that folds Warm deltas into a fresh immutable Cold generation
801
+ * (invariants I3/I4/I5).
802
+ *
803
+ * Memory (Phase 4f): a generation is merged **and written as a stream** — {@link mergeChunksStream} yields one
804
+ * merged chunk at a time and {@link writeCrbmGenerationStream} feeds each to a streaming cold sink (S3 multipart
805
+ * / LocalFs temp file), freeing it. The cold generation is merged and written as a stream, so peak COLD-side
806
+ * memory is ~one chunk + one upload part + the cold reader's index. Caveat: `collectDirty` first materializes
807
+ * the whole dirty Warm delta set in memory, so total peak also scales with the dirty-warm working set (up to a
808
+ * whole segment for an all-warm/never-compacted segment) — pinning only `{chunkKey,token}` + lazy re-read is a
809
+ * deferred fix (gap #12).
810
+ *
811
+ * The algorithm, per chunk: `newCold(k) = (cold_g(k) ∪ adds(k)) \ removes(k)` — exactly the read-time
812
+ * effective set ({@link effective}), so the segment's visible set is unchanged (**I3**). Steps:
813
+ *
814
+ * 1. LEASE acquire the per-segment compaction lease (status→compacting; steal an expired one)
815
+ * 2. PIN g := registry.currentGen; archive each dirty chunk's OCC token (`v_archived`)
816
+ * 3. RECONCILE delete orphan Cold generations > g (a crashed prior stage), safe under the lease
817
+ * 4. MERGE build gen g+1: dirty chunks merged, clean chunks copied through
818
+ * 5. STAGE write segment.<g+1>.crbm (immutable, write-once)
819
+ * 6. VERIFY re-open it; its chunk-key set + footer cardinality must equal the merge (+ the codec's CRC checks)
820
+ * 7. SWAP registry.currentGen := g+1, release the lease — CONDITIONAL on the lease token (atomic commit)
821
+ * 8. PURGE delete each archived Warm row CONDITIONAL on token == v_archived (a write after the scan has a
822
+ * newer token → its purge fails → it survives into the next dirty set: **no lost writes, I4**)
823
+ *
824
+ * **Correctness without a lock:** the commit is the conditional `currentGen` swap; write-once generations +
825
+ * version-fenced purge make even two concurrent compactions safe (the loser's staged generation is an orphan
826
+ * GC'd later; no torn read — readers are pinned to a committed generation, **I5**). The lease only avoids that
827
+ * wasted duplicate work (see DECISIONS #18).
828
+ *
829
+ * Pure orchestration over the injected driver interfaces + an injected {@link Clock} (timer-free; the daemon
830
+ * layer owns scheduling/heartbeat). A crash at *any* step is recoverable: nothing is destructive until after
831
+ * the commit, and the swap is atomic.
832
+ */
833
+
834
+ interface CompactionDeps {
835
+ readonly cold: IColdDriver;
836
+ readonly warm: IWarmDriver;
837
+ readonly registry: IRegistryDriver;
838
+ /** Injected time source for lease expiry (the determinism seam — `core/` never reads ambient time). */
839
+ readonly clock: Pick<Clock, 'now'>;
840
+ readonly maxBitmapBytes?: number;
841
+ /**
842
+ * Bitmap codec ([DECISIONS #58]) — compaction only merges bitmaps through it. Optional in the type so this
843
+ * stays call-compatible public API; a **flavor** package binds it (see {@link requireCodec}).
844
+ */
845
+ readonly codec?: CodecInterface;
846
+ /**
847
+ * Keystore for **encrypted** segments (Phase 4e). An encrypted segment (its registry record carries wrapped
848
+ * DEK(s)) reuses that same DEK: compaction unwraps it to decrypt the old generation and encrypt the new one.
849
+ * A bootstrap (gen 0) with a keystore mints a fresh DEK. Compacting an encrypted segment without a keystore
850
+ * throws {@link KeyUnavailableError}. Absent ⇒ cleartext compaction (unless {@link requireEncryption}).
851
+ */
852
+ readonly keystore?: IKeystore;
853
+ /** When true, refuse to write a cleartext generation — a segment without a keystore-backed DEK throws. */
854
+ readonly requireEncryption?: boolean;
855
+ /**
856
+ * Optional observability sink (Phase D, gap #2). `compactSegment` emits one `compaction` event per attempt
857
+ * (committed or not, timed). Assumed exception-safe (the `CloudRoaring` facade wraps the user sink); the emit
858
+ * is additionally guarded so a throwing sink can never break compaction. Absent ⇒ no emission.
859
+ */
860
+ readonly metrics?: IMetricsSink;
861
+ }
862
+ interface CompactionOptions {
863
+ /** Opaque id of this worker — the lease owner (e.g. `host:pid:uuid`). Required so a steal is attributable. */
864
+ readonly owner: string;
865
+ /** Lease duration in ms (default 60s). */
866
+ readonly leaseMs?: number;
867
+ /**
868
+ * Optional audit sink (Phase 5d). A `segment.compact` event is emitted once per **committed** generation
869
+ * (never on a no-op/contention path). Exception-safe; the default records nothing.
870
+ */
871
+ readonly audit?: IAuditSink;
872
+ }
873
+ interface CompactionResult {
874
+ readonly segment: string;
875
+ readonly namespace?: string;
876
+ /** True iff a new generation was committed. */
877
+ readonly compacted: boolean;
878
+ /**
879
+ * Why nothing was committed, when `compacted` is false: `'clean'` (no dirty rows), `'leased-by-other'`
880
+ * (a live lease elsewhere), `'lease-lost'` (our lease was stolen before the commit — staged gen is an
881
+ * orphan, no rows purged), `'superseded'` (a concurrent publish/bulk-load advanced `currentGen` past the
882
+ * generation we pinned — we abort so RECONCILE never deletes the just-published generation; the next cycle
883
+ * re-pins the new current gen; gap #5), `'bootstrap-raced'` (another worker wrote gen 0; our rows are left
884
+ * for the next compaction), `'stage-conflict'` (a generation write-conflict we couldn't reconcile),
885
+ * `'destroyed'` (the segment was crypto-shredded — terminal), or `'error'` (an isolated per-segment fault
886
+ * recorded by {@link runCompactionCycle}).
887
+ */
888
+ readonly reason?: string;
889
+ /** The fault message when `reason === 'error'` (a per-segment failure isolated by {@link runCompactionCycle}). */
890
+ readonly error?: string;
891
+ readonly fromGen: number | null;
892
+ readonly toGen?: number;
893
+ readonly dirtyChunks: number;
894
+ /** Archived Warm rows actually purged (≤ dirtyChunks; the rest were rewritten post-scan and survive). */
895
+ readonly purged: number;
896
+ readonly survived: number;
897
+ }
898
+ /**
899
+ * Fail fast on bad lease config at the boundary: an empty owner makes a lease steal unattributable, and a
900
+ * non-positive/NaN leaseMs would produce a lease that's already expired (or never), silently defeating the
901
+ * lease's whole job. Cheap to check once, up front, so a misconfigured daemon errors immediately. Exported so
902
+ * callers that mutate **before** compacting (e.g. `CloudRoaring.eraseSubject`, which writes a `remove` tombstone
903
+ * first) can validate the options up front rather than after the tombstone is written.
904
+ */
905
+ declare function validateCompactionOptions(options: CompactionOptions): void;
906
+ /**
907
+ * Compact one segment. A no-op (returns `compacted: false`) if it has no dirty Warm rows or another worker
908
+ * holds a live lease. Never throws on the normal contention/lease-loss paths — those are reported in `reason`.
909
+ * Emits a `compaction` metric event per attempt (Phase D, gap #2) when a sink is wired, timing the attempt.
910
+ */
911
+ declare function compactSegment(ref: SegmentRef, deps: CompactionDeps, options: CompactionOptions): Promise<CompactionResult>;
912
+ /**
913
+ * Garbage-collect superseded Cold generations for a segment: everything strictly below `currentGen`, keeping
914
+ * the most recent `keep` of them as a grace window for in-flight readers pinned to a just-superseded
915
+ * generation (**I5**). Generations ≥ `currentGen` are never touched. Returns the generations deleted.
916
+ */
917
+ declare function gcOrphanGenerations(ref: SegmentRef, deps: Pick<CompactionDeps, 'cold' | 'registry'>, options?: {
918
+ keep?: number;
919
+ }): Promise<number[]>;
920
+ interface DiscoveryOptions {
921
+ /** Scope discovery to one namespace (registry `list(namespace)`). */
922
+ readonly namespace?: string;
923
+ /** Minimum dirty Warm rows for a segment to be a candidate (default 1). */
924
+ readonly threshold?: number;
925
+ /**
926
+ * Extra segments to consider beyond the registry's known set. Discovery enumerates `registry.list()`, which
927
+ * only contains segments that have a committed generation — a brand-new **all-warm** segment isn't there
928
+ * yet, so name it here (or bulk-load it once) for the daemon to pick it up.
929
+ */
930
+ readonly candidates?: readonly SegmentRef[];
931
+ /**
932
+ * Shard this worker's slice of the fleet (gap #3). With `totalShards > 1`, discovery considers only segments
933
+ * whose **stable hash** (FNV-1a of the segment key, mod `totalShards`) falls in `shard` (0-based). Run N
934
+ * daemons with `{ shard: i, totalShards: N }` to partition the fleet N ways — each **drains and compacts** only
935
+ * its ~1/N of segments per cycle. NB: the registry enumeration + in-memory candidate set stay O(total segments)
936
+ * per worker (the hash needs the key, which only the scan yields); only the Warm drain + compaction shard. The
937
+ * O(dirty) indexed-enumeration seam that would shard the scan too is deferred (gap #3). Default: handle all.
938
+ */
939
+ readonly shard?: number;
940
+ readonly totalShards?: number;
941
+ /**
942
+ * Poison-segment quarantine (gap #2): a segment whose `consecutiveFailures` has reached this threshold is
943
+ * skipped by discovery (not even drained) until `quarantineCooldownMs` since its last registry update has
944
+ * elapsed — then it's retried once (a transient fault self-heals; a persistent one re-quarantines). Default 5.
945
+ */
946
+ readonly quarantineThreshold?: number;
947
+ /** Cooldown (ms) before a quarantined segment is retried once (default 5 min). */
948
+ readonly quarantineCooldownMs?: number;
949
+ }
950
+ interface CompactionCandidate {
951
+ readonly ref: SegmentRef;
952
+ readonly dirtyChunks: number;
953
+ readonly currentGen: number | null;
954
+ /** Epoch-ms of the segment's last successful compaction (undefined = never) — the urgency tie-break. */
955
+ readonly lastCompactedAt?: number;
956
+ }
957
+ /**
958
+ * Find segments worth compacting: those in this worker's shard with ≥ `threshold` dirty Warm rows, excluding
959
+ * quarantined poison segments (gap #2). Enumerates the registry (optionally one namespace) plus explicit
960
+ * `candidates`, then for each in-shard, non-quarantined segment drains `warm.listChunks` to count dirty rows —
961
+ * O(total warm / shards) (turning that into O(dirty) needs the deferred GSI/Select:COUNT seam, gap #3). Refreshes
962
+ * the `dirtyChunkCount` hint only when it **changed** (change-guarded CAS — no idle-segment write storm).
963
+ */
964
+ declare function findCompactable(deps: Pick<CompactionDeps, 'warm' | 'registry' | 'clock'>, options?: DiscoveryOptions): Promise<CompactionCandidate[]>;
965
+ /** What one {@link runCompactionCycle} did: candidates found, how many committed, how many the budget deferred. */
966
+ interface CompactionCycleResult {
967
+ /** In-shard, non-quarantined segments over threshold this cycle. */
968
+ readonly candidates: number;
969
+ /** How many committed a new generation. */
970
+ readonly compacted: number;
971
+ /** Candidates left unprocessed by the `maxSegments` budget — run again to drain them. */
972
+ readonly deferred: number;
973
+ readonly results: CompactionResult[];
974
+ }
975
+ /**
976
+ * Run one discovery → compact → GC cycle. The CLI/daemon calls this once (Lambda) or on a loop (K8s/ECS).
977
+ * Candidates are processed **most-dirty first** (tie-broken by oldest/never-compacted), and an optional
978
+ * `maxSegments` budget caps the **compact/GC** work per cycle — the rest are `deferred` to the next cycle
979
+ * (urgency ordering keeps the hottest from starving). The budget bounds only compaction: discovery (the registry
980
+ * scan + in-shard Warm drain + urgency sort) is paid in full each cycle, so at very large fleets discovery — not
981
+ * compaction — is the per-cycle floor; the deferred resumable cursor (gap #3) is what will bound it. Per-segment
982
+ * faults are isolated **and** counted toward quarantine (gap #2), so one poison segment can neither abort the
983
+ * cycle nor silently freeze.
984
+ */
985
+ declare function runCompactionCycle(deps: CompactionDeps, options: CompactionOptions & DiscoveryOptions & {
986
+ keep?: number;
987
+ maxSegments?: number;
988
+ }): Promise<CompactionCycleResult>;
989
+
990
+ /**
991
+ * Segment **export ("eject")** — dump every registered segment's current *effective* set to a portable file,
992
+ * using only public read APIs, so your data is readable **without CloudRoaring** (a first-class exit path;
993
+ * see the README "Your data stays yours" section).
994
+ *
995
+ * Two formats:
996
+ * - `'roaring'` (default) — one **portable RoaringBitmap32** per segment (the cross-language CRoaring format,
997
+ * loadable by any roaring library in Java/Go/Python/Rust/C++/…). Compact; builds one bitmap in memory per
998
+ * segment.
999
+ * - `'ndjson'` — newline-delimited ids, streamed (constant memory, any segment size); zero dependencies to read.
1000
+ *
1001
+ * The engine is **storage-agnostic and I/O-free**: it enumerates via an {@link IRegistryDriver}, reads the
1002
+ * tier-merged effective set via a store's `segment(...).iterate()`, and writes bytes through an injected
1003
+ * {@link ExportSink} — so it never imports `node:fs` (the `export-segments` CLI supplies a filesystem sink; a
1004
+ * test supplies an in-memory one; you could supply an S3 uploader). Crypto-shredded (`destroyed`) segments are
1005
+ * skipped (their bytes are unrecoverable); an encrypted segment is decrypted transparently **iff** the store was
1006
+ * wired with its keystore — so the export is **cleartext**, and protecting it is on you.
1007
+ *
1008
+ * **Enumeration** = the registry's known set (segments with a committed cold generation) **plus** any explicit
1009
+ * {@link ExportOptions.candidates}. A brand-new **all-warm** segment — written only via real-time
1010
+ * `add()`/`remove()`, never compacted — isn't in the registry yet, so name it in `candidates` (CLI:
1011
+ * `CR_EXPORT_SEGMENTS`) to include it, or compact/bulk-load it once first. This mirrors the compaction daemon's
1012
+ * discovery contract (see `findCompactable`).
1013
+ *
1014
+ * **Per-segment fault isolation:** a segment that can't be read (a corrupt cold object, or an encrypted segment
1015
+ * when the store has no keystore) is recorded in the manifest's {@link ExportManifest.failed} list and the export
1016
+ * **continues** — one bad segment never blocks the rest. Its partial output is aborted, so a truncated file never
1017
+ * masquerades as complete. Therefore "a manifest exists" means *the run finished*, not that every segment
1018
+ * succeeded — check `failed` (the CLI also exits non-zero when it's non-empty).
1019
+ */
1020
+
1021
+ /** Output format for {@link runExport} / `CloudRoaring.exportSegments`. */
1022
+ type ExportFormat = 'roaring' | 'ndjson';
1023
+ /** A writer for one segment's output — inject a filesystem file, an S3 upload, stdout, or a test buffer. */
1024
+ interface ExportWriter {
1025
+ /** Append bytes (called one or more times; for `ndjson`, batched line chunks; for `roaring`, once). */
1026
+ write(bytes: Uint8Array): Promise<void> | void;
1027
+ /** **Commit** the output — called once, only on success (all bytes written). Finalize/flush/rename here. */
1028
+ close(): Promise<void> | void;
1029
+ /**
1030
+ * **Discard** a partial output — called instead of `close()` if a fault occurs mid-segment, so a truncated file
1031
+ * never masquerades as complete (e.g. delete the temp file). Optional; sinks with nothing to undo can omit it.
1032
+ */
1033
+ abort?(): Promise<void> | void;
1034
+ }
1035
+ /** A destination the export writes each segment to. `ext` is `.roaring` or `.ndjson`. */
1036
+ interface ExportSink {
1037
+ open(ref: SegmentRef, ext: string): Promise<ExportWriter> | ExportWriter;
1038
+ }
1039
+ interface ExportOptions {
1040
+ /** Output format; defaults to `'roaring'`. */
1041
+ readonly format?: ExportFormat;
1042
+ /**
1043
+ * Bitmap codec used to build the exported bitmap for the `'roaring'` format ([DECISIONS #58]). Optional in the
1044
+ * type so this stays call-compatible public API; a **flavor** package binds it (see `requireCodec`). Not needed
1045
+ * for `'ndjson'`, which streams plain ids.
1046
+ */
1047
+ readonly codec?: CodecInterface;
1048
+ /** Scope the export to one namespace; omit to export every registered segment. */
1049
+ readonly namespace?: string;
1050
+ /** `ndjson` flush threshold in ~bytes (ids are ASCII, so ≈ chars); defaults to 64 KiB. */
1051
+ readonly ndjsonBatchBytes?: number;
1052
+ /**
1053
+ * Extra segments to export beyond the registry's known set. The registry only lists segments with a committed
1054
+ * cold generation, so an **all-warm** segment (written via real-time `add()`/`remove()`, never compacted) must
1055
+ * be named here to be included (CLI: `CR_EXPORT_SEGMENTS`). Deduped against the registry set; when `namespace`
1056
+ * is set, only candidates in that namespace are exported.
1057
+ */
1058
+ readonly candidates?: readonly SegmentRef[];
1059
+ }
1060
+ /** Per-segment result recorded in the {@link ExportManifest}. */
1061
+ interface ExportedSegment {
1062
+ readonly segment: string;
1063
+ readonly namespace?: string;
1064
+ /** Ids written (the segment's effective cardinality at export time). */
1065
+ readonly count: number;
1066
+ /** Bytes written to the sink for this segment. */
1067
+ readonly bytes: number;
1068
+ }
1069
+ /**
1070
+ * A segment that could **not** be exported — recorded rather than aborting the whole run (fault isolation), so a
1071
+ * single corrupt/undecryptable segment never blocks the rest of the dump.
1072
+ */
1073
+ interface ExportFailure {
1074
+ readonly segment: string;
1075
+ readonly namespace?: string;
1076
+ /** Why it failed (an error message) — e.g. a corrupt cold object, or an encrypted segment with no keystore. */
1077
+ readonly error: string;
1078
+ }
1079
+ /** What {@link runExport} produced — a self-describing summary the CLI persists as `manifest.json`. */
1080
+ interface ExportManifest {
1081
+ /** Schema version of this manifest shape (currently `1`) — insurance for evolving the format post-1.0. */
1082
+ readonly version: number;
1083
+ readonly format: ExportFormat;
1084
+ readonly totalSegments: number;
1085
+ readonly totalIds: number;
1086
+ readonly segments: readonly ExportedSegment[];
1087
+ /**
1088
+ * Segments that could not be read (empty when everything succeeded). Its presence in the manifest is the point:
1089
+ * "a manifest exists" ⇒ the run finished, **not** that every segment succeeded — always check this list.
1090
+ */
1091
+ readonly failed: readonly ExportFailure[];
1092
+ }
1093
+ /** The minimal read surface the export needs from a store — a `CloudRoaring` satisfies this structurally. */
1094
+ interface SegmentReader {
1095
+ segment(name: string, options?: {
1096
+ namespace?: string;
1097
+ }): {
1098
+ iterate(): AsyncIterable<number>;
1099
+ };
1100
+ }
1101
+ /**
1102
+ * Export every enumerated segment's effective set through `sink`. Enumerates via `registry` (+ any
1103
+ * `options.candidates`), reads via `reader` (the store). Returns a manifest. `CloudRoaring.exportSegments` is the
1104
+ * public entry — it calls this with its own store + registry (so enumeration and the read path provably share one
1105
+ * registry).
1106
+ *
1107
+ * **Fault isolation:** a per-segment read/write fault aborts that segment's partial (so a truncated file never
1108
+ * looks complete), records it in `failed`, and the run continues — one bad segment never blocks the rest. A
1109
+ * `close()` fault is likewise recorded (never triggering `abort()` — they're mutually exclusive).
1110
+ *
1111
+ * Re-running overwrites the segments it re-exports but does **not** prune files for segments that have since
1112
+ * disappeared — export to a fresh directory for a clean dump. For a *current* image, run against a
1113
+ * freshly-constructed store: a store's cold source pins each segment's generation for its lifetime, so a
1114
+ * long-lived store that has since been compacted would export the pinned (stale) view.
1115
+ */
1116
+ declare function runExport(reader: SegmentReader, registry: IRegistryDriver, sink: ExportSink, options?: ExportOptions): Promise<ExportManifest>;
1117
+
1118
+ interface BitRoute {
1119
+ /** Top 16 bits — the chunk key (0..65535). */
1120
+ readonly chunkKey: number;
1121
+ /** Bottom 16 bits — the value within the chunk (0..65535). */
1122
+ readonly remainder: number;
1123
+ }
1124
+ /** Split a u32 id into `{ chunkKey, remainder }`. Throws `ValidationError` for non-u32 input. */
1125
+ declare function splitId(id: number): BitRoute;
1126
+ /**
1127
+ * Recombine a chunk key + remainder back into the u32 id. Precondition: `chunkKey` is a validated
1128
+ * 16-bit value (tier-derived keys are validated upstream in the engine before reaching here, per
1129
+ * invariant 5); `remainder` is masked to 16 bits.
1130
+ */
1131
+ declare function joinId(chunkKey: number, remainder: number): number;
1132
+
1133
+ /**
1134
+ * Bounded-concurrency fan-out — the missing primitive for running async work over many items without either
1135
+ * a thundering herd (unbounded `Promise.all` → N simultaneous S3/DynamoDB calls) or full serial latency
1136
+ * (`for await`). Pure + I/O-free, so it lives in `core/`; callers inject the async work.
1137
+ */
1138
+ /**
1139
+ * Run `fn` over `items` with at most `limit` promises in flight at once, returning results in **input order**
1140
+ * (regardless of completion order). If any `fn` rejects, the pool stops scheduling new work, lets the in-flight
1141
+ * tasks settle, and rejects with the **first** error — callers that must not abort on one failure (e.g. an
1142
+ * erasure ledger that records per-item faults) should catch inside `fn` so it never rejects.
1143
+ *
1144
+ * `items` is a materialized array on purpose: every caller here already enumerates its source (registry list,
1145
+ * S3 page, chunk map), so an array keeps the pool trivial and race-free (no shared-iterator hazard).
1146
+ */
1147
+ declare function mapWithConcurrency<T, R>(items: readonly T[], limit: number, fn: (item: T, index: number) => Promise<R>): Promise<R[]>;
1148
+
1149
+ declare function validateSegmentRef(ref: SegmentRef): void;
1150
+
1151
+ /**
1152
+ * Canonical key encoding for a segment / chunk — one source of truth, used by both the storage
1153
+ * drivers and the HOT cache (DRY). The encoding is collision-proof and injection-proof:
1154
+ *
1155
+ * - The field delimiter (a space) and the "no namespace" sentinel (`/`) are characters the name
1156
+ * grammar forbids in a namespace/segment — so they can never appear inside
1157
+ * a name, can't be injected across the delimiter, and the absent-namespace case (`undefined`) is
1158
+ * encoded distinctly from any real namespace (an empty string is rejected by the grammar anyway).
1159
+ */
1160
+
1161
+ declare function segmentKey(ref: SegmentRef): string;
1162
+ declare function chunkRefKey(ref: ChunkRef): string;
1163
+
1164
+ declare class MemoryWarmDriver implements IWarmDriver {
1165
+ private readonly rows;
1166
+ private seq;
1167
+ private nextToken;
1168
+ get(ref: ChunkRef): Promise<WarmRow | null>;
1169
+ putConditional(ref: ChunkRef, bytes: Uint8Array, expected: Token | NoRow): Promise<{
1170
+ token: Token;
1171
+ }>;
1172
+ deleteConditional(ref: ChunkRef, expected: Token): Promise<void>;
1173
+ listChunks(ref: SegmentRef): AsyncIterable<{
1174
+ chunkKey: number;
1175
+ } & WarmRow>;
1176
+ }
1177
+ declare class MemoryColdChunkSource implements ColdChunkSource {
1178
+ private readonly chunks;
1179
+ getChunk(ref: ChunkRef): Promise<Uint8Array | null>;
1180
+ listChunkKeys(ref: SegmentRef): Promise<number[]>;
1181
+ sizeOf(ref: SegmentRef): Promise<SegmentSize | null>;
1182
+ /** Test/seed helper — populate immutable Cold bytes for a chunk (no bulk-load path until Phase 3). */
1183
+ seed(ref: ChunkRef, bytes: Uint8Array): void;
1184
+ }
1185
+ interface MemoryRegistryDriverOptions {
1186
+ /** Injected clock for `createdAt`/`updatedAt`; defaults to `Date.now` (drivers may use ambient time). */
1187
+ readonly now?: () => number;
1188
+ }
1189
+ /**
1190
+ * In-memory {@link IRegistryDriver} — one record per segment under OCC. The token is a single global,
1191
+ * monotonic, never-reused counter (so a record recreated after `delete` always gets a strictly-greater
1192
+ * token → ABA-safe even though `delete` removes the row physically). Mirrors {@link MemoryWarmDriver}.
1193
+ */
1194
+ declare class MemoryRegistryDriver implements IRegistryDriver {
1195
+ private readonly rows;
1196
+ private readonly now;
1197
+ private seq;
1198
+ constructor(options?: MemoryRegistryDriverOptions);
1199
+ private nextToken;
1200
+ capabilities(): RegCaps;
1201
+ get(ref: SegmentRef): Promise<RegistryRecord | null>;
1202
+ create(ref: SegmentRef, record: NewRegistryRecord): Promise<{
1203
+ token: Token;
1204
+ }>;
1205
+ compareAndSwap(ref: SegmentRef, expected: Token, patch: RegistryPatch): Promise<{
1206
+ token: Token;
1207
+ }>;
1208
+ list(namespace?: string): AsyncIterable<RegistryRecord>;
1209
+ delete(ref: SegmentRef): Promise<void>;
1210
+ }
1211
+ /**
1212
+ * In-memory {@link IColdDriver} — write-once immutable generation objects as opaque bytes. A "dumb byte
1213
+ * mover" (understands neither roaring nor `.crbm`), so it's a faithful cold backend for tests, the compaction
1214
+ * simulator, and a zero-setup local cold tier. Mirrors the LocalFs/S3 contract (write-once, range/tail reads).
1215
+ */
1216
+ declare class MemoryColdDriver implements IColdDriver {
1217
+ private readonly objects;
1218
+ capabilities(): ColdCaps;
1219
+ putImmutable(key: GenKey, write: (sink: BlobSink) => Promise<void>): Promise<{
1220
+ size: number;
1221
+ sha256: string;
1222
+ }>;
1223
+ getRange(key: GenKey, offset: number, length: number): Promise<Uint8Array>;
1224
+ getTail(key: GenKey, maxBytes: number): Promise<{
1225
+ bytes: Uint8Array;
1226
+ size: number;
1227
+ }>;
1228
+ delete(key: GenKey): Promise<void>;
1229
+ list(ref: SegmentRef): AsyncIterable<GenKey>;
1230
+ private require;
1231
+ }
1232
+
1233
+ declare class LocalFsColdDriver implements IColdDriver {
1234
+ private readonly root;
1235
+ constructor(root: string);
1236
+ capabilities(): ColdCaps;
1237
+ putImmutable(key: GenKey, write: (sink: BlobSink) => Promise<void>): Promise<{
1238
+ size: number;
1239
+ sha256: string;
1240
+ }>;
1241
+ getRange(key: GenKey, offset: number, length: number): Promise<Uint8Array>;
1242
+ getTail(key: GenKey, maxBytes: number): Promise<{
1243
+ bytes: Uint8Array;
1244
+ size: number;
1245
+ }>;
1246
+ delete(key: GenKey): Promise<void>;
1247
+ list(ref: SegmentRef): AsyncIterable<GenKey>;
1248
+ private openRead;
1249
+ }
1250
+
1251
+ declare class LocalFsWarmDriver implements IWarmDriver {
1252
+ private readonly root;
1253
+ private readonly maxRowBytes;
1254
+ /** Per-row promise chain — serializes read-modify-write so an in-process CAS never loses an update. */
1255
+ private readonly chain;
1256
+ constructor(root: string, maxRowBytes?: number);
1257
+ get(ref: ChunkRef): Promise<WarmRow | null>;
1258
+ putConditional(ref: ChunkRef, bytes: Uint8Array, expected: Token | NoRow): Promise<{
1259
+ token: Token;
1260
+ }>;
1261
+ deleteConditional(ref: ChunkRef, expected: Token): Promise<void>;
1262
+ listChunks(ref: SegmentRef): AsyncIterable<{
1263
+ chunkKey: number;
1264
+ } & WarmRow>;
1265
+ private readRow;
1266
+ private writeRow;
1267
+ /**
1268
+ * Serialize callbacks for a given row path so read-modify-write is atomic in-process. `prev.then(fn,
1269
+ * fn)` runs `fn` regardless of whether the predecessor fulfilled or rejected — a conflict on one
1270
+ * writer must not stall the row's chain.
1271
+ */
1272
+ private withRowLock;
1273
+ }
1274
+
1275
+ interface LocalFsRegistryDriverOptions {
1276
+ /** Injected clock for `createdAt`/`updatedAt`; defaults to `Date.now`. */
1277
+ readonly now?: () => number;
1278
+ }
1279
+ declare class LocalFsRegistryDriver implements IRegistryDriver {
1280
+ private readonly root;
1281
+ /** Per-row promise chain — serializes read-modify-write so an in-process CAS never loses an update. */
1282
+ private readonly chain;
1283
+ private readonly now;
1284
+ constructor(root: string, options?: LocalFsRegistryDriverOptions);
1285
+ capabilities(): RegCaps;
1286
+ get(ref: SegmentRef): Promise<RegistryRecord | null>;
1287
+ create(ref: SegmentRef, record: NewRegistryRecord): Promise<{
1288
+ token: Token;
1289
+ }>;
1290
+ compareAndSwap(ref: SegmentRef, expected: Token, patch: RegistryPatch): Promise<{
1291
+ token: Token;
1292
+ }>;
1293
+ list(namespace?: string): AsyncIterable<RegistryRecord>;
1294
+ delete(ref: SegmentRef): Promise<void>;
1295
+ /** Namespaces to scan: just the one requested, or every namespace dir under the root. */
1296
+ private namespaceDirs;
1297
+ private readRow;
1298
+ private writeRow;
1299
+ /** Serialize callbacks for a row path so read-modify-write is atomic in-process (mirrors the Warm tier). */
1300
+ private withRowLock;
1301
+ }
1302
+
1303
+ interface CrbmReaderOptions {
1304
+ /** Speculative tail size in bytes (default 256 KB; clamped up to at least the footer size). */
1305
+ readonly tailBytes?: number;
1306
+ /** Hard cap on a single chunk payload length (default 16 MB). */
1307
+ readonly maxPayloadBytes?: number;
1308
+ /** Hard cap on the whole index region fetched/parsed from one object (default 8 MB). */
1309
+ readonly maxIndexBytes?: number;
1310
+ /** Decryption context for an encrypted object (its DEK's AEAD + AAD builder). Required iff `FLAG_ENCRYPTED`. */
1311
+ readonly crypto?: CrbmCrypto;
1312
+ }
1313
+ declare class CrbmReader {
1314
+ private readonly blob;
1315
+ private readonly objectSize;
1316
+ readonly generation: number;
1317
+ readonly totalCardinality: number;
1318
+ private readonly entries;
1319
+ private readonly orderedKeys;
1320
+ /** True when `open()` satisfied the index from the tail GET alone (no second range read). */
1321
+ readonly servedFromTail: boolean;
1322
+ /** Set iff the object is encrypted — used to decrypt each chunk payload in {@link getChunk}. */
1323
+ private readonly crypto;
1324
+ private constructor();
1325
+ /** Total object bytes (from the one-GET tail read) — for grounded storage cost (Phase 5b). */
1326
+ get sizeBytes(): number;
1327
+ /**
1328
+ * Estimated retained JS heap of this reader's parsed index — the weight the cold reader cache bounds on
1329
+ * (gap #1: a wide segment's index dominates the reader's footprint). `entries.size` scales with the number
1330
+ * of resident chunks (≤ 65536), so this is `entries.size × {@link RETAINED_BYTES_PER_INDEX_ENTRY}`.
1331
+ */
1332
+ get retainedIndexBytes(): number;
1333
+ /** Per-chunk cardinality (`chunkKey → count`) from the parsed index — no payload reads (Phase 5c cheap count). */
1334
+ cardinalities(): Map<number, number>;
1335
+ static open(blob: BlobReader, options?: CrbmReaderOptions): Promise<CrbmReader>;
1336
+ /** Chunk keys present in this generation, ascending. */
1337
+ chunkKeys(): number[];
1338
+ /** Segment cardinality from the footer (no payload reads) — the cheap `count()` path. */
1339
+ count(): number;
1340
+ has(chunkKey: number): boolean;
1341
+ /**
1342
+ * Range-GET one chunk's payload, verifying its CRC32C before returning. Returns `null` if the chunk is
1343
+ * absent. Throws `IntegrityError` on a CRC mismatch (the bytes must never reach the native
1344
+ * deserializer). The returned buffer is a **read-only view** owned by the reader/driver — callers must
1345
+ * not mutate it.
1346
+ */
1347
+ getChunk(chunkKey: number): Promise<Uint8Array | null>;
1348
+ }
1349
+
1350
+ /**
1351
+ * Bridges the `.crbm` archive format to the engine's Cold seam (decision 4).
1352
+ *
1353
+ * `CrbmColdChunkSource` implements the Phase-1 {@link ColdChunkSource} over an {@link IColdDriver}: it
1354
+ * pins a segment's latest generation, opens its {@link CrbmReader} once, and serves per-chunk payloads —
1355
+ * so the **engine is unchanged**, it just reads real on-disk generations now. `writeCrbmGeneration` is
1356
+ * the inverse seed primitive (a generation built from in-memory bitmaps), the basis for the Phase-3
1357
+ * bulk-load and Phase-4 compaction writers.
1358
+ */
1359
+
1360
+ interface CrbmColdChunkSourceOptions extends CrbmReaderOptions {
1361
+ /**
1362
+ * Optional {@link IRegistryDriver}. When provided, the current generation is resolved via the registry's
1363
+ * authoritative `currentGen` (one cheap strong read) instead of a `list` scan of every generation — the
1364
+ * Phase-4c retirement of that scan. When absent, the source falls back to the `list`-scan (so the
1365
+ * in-memory / simple setups keep working with no registry). The resolved generation is cached and
1366
+ * **re-resolved on a short TTL** ({@link currentGenTtlMs}, needs a {@link clock}) so a long-lived source
1367
+ * observes a compaction's new generation within the TTL instead of pinning one generation forever (gap #4).
1368
+ */
1369
+ readonly registry?: IRegistryDriver;
1370
+ /**
1371
+ * Optional {@link IKeystore} for reading **encrypted** segments (Phase 4e). When a segment's registry record
1372
+ * carries wrapped DEK(s), the source unwraps the DEK via this keystore and decrypts each chunk/index. Reading
1373
+ * an encrypted segment without a keystore throws {@link KeyUnavailableError}; cleartext segments ignore it.
1374
+ * Requires a `registry` (that's where the wrapped DEKs live).
1375
+ */
1376
+ readonly keystore?: IKeystore;
1377
+ /**
1378
+ * Enforce that every segment read is encrypted (Phase 4e). When true, resolving a **cleartext** segment (no
1379
+ * wrapped DEKs) throws {@link KeyUnavailableError} — a guard against silently reading a segment that should
1380
+ * have been encrypted. Off by default (encryption is opt-in).
1381
+ */
1382
+ readonly requireEncryption?: boolean;
1383
+ /**
1384
+ * Time source for the current-generation TTL refresh (gap #4) — the determinism seam; `core/` never reads
1385
+ * ambient time. Refresh needs **both** a clock and a `registry`; without either the source **pins** the
1386
+ * first-resolved generation for its lifetime (the pre-Phase-B behaviour). The `CloudRoaring` facade passes its
1387
+ * clock automatically, so wiring a `registry` is enough to get live cross-generation invalidation.
1388
+ */
1389
+ readonly clock?: Pick<Clock, 'now'>;
1390
+ /**
1391
+ * How long (ms) a resolved `currentGen` is trusted before the next read re-resolves it (default 2000) — the
1392
+ * bound on read staleness after a compaction: a reader may serve the prior generation for up to this long,
1393
+ * then converges. Lazy (checked on read — no timer); ≤ one cheap registry read per segment per window, and a
1394
+ * new {@link CrbmReader} is opened only when the generation actually changed. `0` (or no clock) ⇒ pin forever.
1395
+ */
1396
+ readonly currentGenTtlMs?: number;
1397
+ /**
1398
+ * Hard ceiling on how many segments' readers (each holding a fully-parsed `.crbm` index) are cached at once
1399
+ * (default 1024) — a memory bound for a long-running server (gap #1). Past it, the least-recently-used
1400
+ * segment's reader is evicted; the next read of an evicted segment re-opens it (one cheap tail GET, since
1401
+ * generations are immutable). Raise it for a big hot working set of small segments.
1402
+ */
1403
+ readonly maxOpenSegments?: number;
1404
+ /**
1405
+ * Aggregate byte ceiling on the parsed `.crbm` indices resident in the reader cache (default 64 MiB) — the
1406
+ * **second half of the gap #1 fix**. `maxOpenSegments` alone bounds by *count*, but a wide/dense segment's
1407
+ * parsed index can be several MB, so 1024 wide indices could pin ~GBs and blow a small heap (e.g. a 128 MB
1408
+ * Lambda) while the count is nominally "in bounds". This caps the summed {@link CrbmReader.retainedIndexBytes}
1409
+ * across cached readers; the least-recently-used reader is evicted once the total would exceed it — whichever
1410
+ * of the count/byte bounds binds first. Lower it for memory-tight deployments with wide segments; a single
1411
+ * segment whose index alone exceeds the budget is still cached (it can't be shrunk) but nothing else alongside.
1412
+ */
1413
+ readonly maxOpenIndexBytes?: number;
1414
+ }
1415
+ declare class CrbmColdChunkSource implements ColdChunkSource {
1416
+ private readonly driver;
1417
+ /**
1418
+ * One resolved reader per segment, re-resolved on a short TTL ({@link CrbmColdChunkSourceOptions.currentGenTtlMs},
1419
+ * needs a clock). Within the TTL a segment's Cold bytes are treated as an immutable snapshot; when the TTL
1420
+ * elapses the next read cheaply re-resolves `currentGen` and, only if it advanced (a compaction committed),
1421
+ * opens the new generation — so a long-lived source observes new generations within the TTL rather than
1422
+ * pinning one forever (gap #4). The engine pairs this with a **generation-keyed** HOT cache so a bump never
1423
+ * serves a stale decoded chunk. Without a clock or a registry the source pins the first generation for its
1424
+ * lifetime (pre-Phase-B behaviour). A segment with no generation yet is not memoized, so it's re-checked until
1425
+ * one exists. **Bounded** by a {@link BoundedLru} ({@link CrbmColdChunkSourceOptions.maxOpenSegments}, default
1426
+ * 1024): past the ceiling the least-recently-used segment's reader (and its parsed index) is evicted — the
1427
+ * steady-state memory bound (gap #1); re-opening an evicted segment is one cheap tail GET.
1428
+ */
1429
+ private readonly snapshots;
1430
+ private readonly registry;
1431
+ private readonly keystore;
1432
+ private readonly requireEncryption;
1433
+ private readonly readerOptions;
1434
+ private readonly clock;
1435
+ private readonly currentGenTtlMs;
1436
+ constructor(driver: IColdDriver, options?: CrbmColdChunkSourceOptions);
1437
+ /** The current resolved reader for a segment, refreshing on the TTL (gap #4). Cheap within the TTL window. */
1438
+ private resolvedReader;
1439
+ /** TTL elapsed: cheaply re-resolve `currentGen`; reopen only if it actually changed, else reuse the prior reader. */
1440
+ private computeRefreshedReader;
1441
+ /**
1442
+ * Memoize a reader-promise as the segment's snapshot at `now()`, forgetting it later if it resolves to
1443
+ * null / throws (identity-guarded, so a stale cleanup never clobbers a fresher snapshot). Installing the
1444
+ * (pending) promise **synchronously** is what lets concurrent callers coalesce onto one in-flight resolve.
1445
+ */
1446
+ private install;
1447
+ private now;
1448
+ private expired;
1449
+ /**
1450
+ * The segment's current generation number (gap #4) — the engine keys its HOT chunk cache by this so a
1451
+ * compaction bump is observed instead of serving a stale decoded chunk. Served from the (TTL-refreshed)
1452
+ * snapshot, so no extra backend read within the TTL window. `null` if the segment has no committed generation.
1453
+ */
1454
+ currentGeneration(ref: SegmentRef): Promise<number | null>;
1455
+ private resolveLatest;
1456
+ /** Open a {@link CrbmReader} for an already-resolved generation target (decrypting if the segment is encrypted). */
1457
+ private openForTarget;
1458
+ /**
1459
+ * The current generation + its DEK wrappings: the registry's authoritative record (one strong read), or a
1460
+ * `list` scan for the max generation when there's no registry (a registry-less source can only read cleartext
1461
+ * — there's nowhere a wrapped DEK could live).
1462
+ */
1463
+ private resolveTarget;
1464
+ /** Build the per-generation decryption context for an encrypted segment (undefined for cleartext). */
1465
+ private cryptoForRead;
1466
+ getChunk(ref: ChunkRef): Promise<Uint8Array | null>;
1467
+ listChunkKeys(ref: SegmentRef): Promise<number[]>;
1468
+ sizeOf(ref: SegmentRef): Promise<SegmentSize | null>;
1469
+ cardinalities(ref: SegmentRef): Promise<ReadonlyMap<number, number> | null>;
1470
+ /**
1471
+ * Run `read` against the pinned snapshot, healing the one torn-read window compaction's GC can open: if the
1472
+ * generation we pinned was superseded *and* swept (the grace window elapsed) mid-read, the Cold driver throws
1473
+ * {@link NotFoundError}. Rather than surface that as a query failure (**I5**), we drop the stale snapshot,
1474
+ * re-resolve `currentGen`, and retry once — the read then serves the newer (committed, immutable) generation,
1475
+ * a monotonic move forward. A *second* NotFound is pathological (GC outrunning resolution) and propagates
1476
+ * rather than fabricate an absent answer — never return a wrong result. `ifGone` is returned only when the
1477
+ * segment legitimately has no committed generation at all (cold is empty). The single retry is bounded.
1478
+ */
1479
+ private withFreshSnapshot;
1480
+ }
1481
+ /**
1482
+ * Write one immutable generation from in-memory bitmaps (the seed / bulk-load primitive). Chunks are
1483
+ * sorted ascending and empty bitmaps skipped (empty chunks are never stored). Returns the driver's
1484
+ * `{ size, sha256 }` for the written object. Pass `options.crypto` to AES-256-GCM-encrypt the generation
1485
+ * (the caller builds it from the segment's DEK + a `(segment, generation)`-bound {@link aadFor}).
1486
+ */
1487
+ declare function writeCrbmGeneration(driver: IColdDriver, key: GenKey, chunks: Iterable<{
1488
+ chunkKey: number;
1489
+ bitmap: CodecBitmap;
1490
+ }>, options?: {
1491
+ crypto?: CrbmCrypto;
1492
+ }): Promise<{
1493
+ size: number;
1494
+ sha256: string;
1495
+ }>;
1496
+ /**
1497
+ * Point a segment's registry `currentGen` at `key.generation` — the publish step that makes a freshly-written
1498
+ * generation the authoritative latest (so registry-aware readers see it). **Forward-only and idempotent:**
1499
+ * if the registry has no row it creates one; if it's already at/ahead of `key.generation` it's a no-op (an
1500
+ * out-of-order/duplicate publish never regresses the pointer); otherwise it advances via compare-and-swap,
1501
+ * retrying a few times under contention. Separated from the Cold write so callers can publish atomically
1502
+ * after the immutable object is durable (write-then-publish).
1503
+ */
1504
+ declare function publishGeneration(registry: IRegistryDriver, key: GenKey, options?: {
1505
+ wrappedDeks?: readonly WrappedDek[];
1506
+ }): Promise<boolean>;
1507
+ /** What a bulk-load wrote — the driver's `{ size, sha256 }` plus a summary of the built generation. */
1508
+ interface BulkLoadResult {
1509
+ /** Bytes written to the Cold object. */
1510
+ readonly size: number;
1511
+ /** The driver's content hash of the object. */
1512
+ readonly sha256: string;
1513
+ /** Distinct non-empty chunks written (≤ 65536). */
1514
+ readonly chunkCount: number;
1515
+ /** Total distinct ids in the generation (post-dedup). */
1516
+ readonly cardinality: number;
1517
+ }
1518
+ /**
1519
+ * **Bulk-load** a whole immutable generation from a flat id source — the batch "seed/sweep" entry point.
1520
+ * Unlike {@link writeCrbmGeneration} (which takes pre-grouped bitmaps), this
1521
+ * consumes an arbitrary, **unsorted** stream of u32 ids (sync or async) and routes each into its chunk's
1522
+ * Roaring bitmap as it goes. The input is consumed lazily and ids dedup on insert, so memory is bounded by
1523
+ * the **built generation** — the in-memory Roaring representation of the *distinct* set (≤ one bitmap per
1524
+ * non-empty chunk) — not by the input length: you can stream a billion duplicate-heavy ids and hold only the
1525
+ * distinct result. Note this is `O(distinct set)`, **not** window-bounded like {@link SegmentEngine.intersect}:
1526
+ * bulk-load holds the whole generation in RAM, which suits batch seed/sweep jobs but not an
1527
+ * unbounded-cardinality stream (a segment larger than RAM needs external-merge / pre-sorted input — later phase).
1528
+ *
1529
+ * Each id must be an integer in `[0, 2³²)` ({@link splitId} throws {@link ValidationError} otherwise) — the
1530
+ * source is consumed lazily, so a bad id aborts mid-stream without writing a partial object (the driver
1531
+ * commits only after the callback resolves). An empty source writes a valid empty generation.
1532
+ *
1533
+ * Writing a fresh full snapshot of a segment; merging a new delta into the *existing* Cold (compaction) is
1534
+ * Phase 4. The caller picks the generation number in `key` and a `ColdChunkSource` serves the **highest**, so
1535
+ * pick a known-fresh number (on an empty segment, `0`): a too-high number silently shadows real data, and
1536
+ * re-using an existing generation throws {@link WriteConflictError} (write-once). Registry-assigned
1537
+ * generations arrive in Phase 4.
1538
+ */
1539
+ declare function bulkLoadCrbmGeneration(driver: IColdDriver, key: GenKey, ids: Iterable<number> | AsyncIterable<number>, options?: {
1540
+ registry?: IRegistryDriver;
1541
+ keystore?: IKeystore;
1542
+ requireEncryption?: boolean;
1543
+ audit?: IAuditSink;
1544
+ /** Bitmap codec ([DECISIONS #58]). Optional in the type; a **flavor** package binds it ({@link requireCodec}). */
1545
+ codec?: CodecInterface;
1546
+ }): Promise<BulkLoadResult>;
1547
+
1548
+ /**
1549
+ * Typed errors — callers learn *why* something failed, never by parsing strings. Retry is the driver
1550
+ * decorators' job, not the engine's: a driver classifies its backend's failures into this vocabulary, and
1551
+ * `withRetry` decides what is transient.
1552
+ */
1553
+ /**
1554
+ * Registry-symbol brands. The package ships as multiple bundles — the core entry and the `./s3` / `./dynamodb`
1555
+ * subpaths — and the builder inlines `core/errors` into each. A driver in a subpath bundle therefore throws a
1556
+ * *different* class object than the one the core engine/retry code would `instanceof`-check, so `instanceof`
1557
+ * silently returns false across that boundary in the published CJS package (defeating OCC/transient retry and
1558
+ * compaction race-handling). These `Symbol.for` brands are identity-stable across bundles/realms; classify
1559
+ * errors with the exported predicates below (never `instanceof`) anywhere an error may cross the boundary.
1560
+ */
1561
+ declare const ERROR_BRAND: unique symbol;
1562
+ declare const TRANSIENT_BRAND: unique symbol;
1563
+ /** Base class for every error CloudRoaring throws. */
1564
+ declare class CloudRoaringError extends Error {
1565
+ /** Cross-bundle brand — see the predicates ({@link isCloudRoaringError}, …). Non-enumerable-ish (symbol key ⇒ not in JSON). */
1566
+ readonly [ERROR_BRAND]: true;
1567
+ constructor(message: string);
1568
+ }
1569
+ /** Invalid caller input (bad id, segment name, options). Raised before any storage call. */
1570
+ declare class ValidationError extends CloudRoaringError {
1571
+ }
1572
+ /** An OCC conditional write/delete lost the race — the row changed since it was read. */
1573
+ declare class WriteConflictError extends CloudRoaringError {
1574
+ }
1575
+ /** Bytes from a tier are corrupt, oversized, or fail a checksum/format check. */
1576
+ declare class IntegrityError extends CloudRoaringError {
1577
+ }
1578
+ /**
1579
+ * A requested object/row does not exist. Part of the driver error vocabulary; thrown by
1580
+ * persistent drivers from Phase 2 — the Phase-1 engine + in-memory drivers return `null` instead.
1581
+ */
1582
+ declare class NotFoundError extends CloudRoaringError {
1583
+ }
1584
+ /**
1585
+ * This build/configuration cannot perform the requested operation, though nothing is malformed. Two uses:
1586
+ * (1) **format** — the bytes are well-formed but unreadable here (an unknown `.crbm` major version, an
1587
+ * encrypted file before the crypto path exists) — distinct from `IntegrityError` (corruption); and (2)
1588
+ * **store configuration** — an operation this store's wiring doesn't support (e.g. a lifecycle helper like
1589
+ * `compact`/`eraseSubject` called on a store built without a raw cold driver + registry). Raised at
1590
+ * operation time, before any mutation.
1591
+ */
1592
+ declare class UnsupportedError extends CloudRoaringError {
1593
+ }
1594
+ /**
1595
+ * A driver cannot meet a capability the chosen topology requires (e.g. a Cold driver without range
1596
+ * reads). Raised fail-fast at wiring time, never mid-operation.
1597
+ */
1598
+ declare class CapabilityError extends CloudRoaringError {
1599
+ }
1600
+ /**
1601
+ * An operation would exceed its per-op **denial-of-wallet budget** — too many backend requests for a single
1602
+ * `count`/`iterate`/`intersect`/`subjectReport`/`eraseSubject` call — so it is refused **before** fanning out
1603
+ * (Decision #3 / invariant T3). Default-on but generous (normal ops never hit it); tune it
1604
+ * per store (`budget`) or per op, or disable with `budget: false`. Deterministic (never retried): the op is too
1605
+ * big by policy, not by luck. Each request's bytes are separately capped (the safe-deserialize ceiling), so
1606
+ * bounding the request count transitively bounds bytes. Carries the projected count + the limit, never data.
1607
+ */
1608
+ declare class BudgetExceededError extends CloudRoaringError {
1609
+ }
1610
+ /**
1611
+ * An encrypted segment's data key (DEK) cannot be unwrapped because the keystore holds none of the
1612
+ * key-encryption-keys (KEKs) its wrappings reference — the KEK was never configured, rotated away without
1613
+ * keeping the old key, or lost. Deterministic (never retried): without a KEK the ciphertext is unreadable by
1614
+ * design. The flip side of crypto-shred — when this is *intended* (a destroyed segment) the registry row is
1615
+ * already a `destroyed` tombstone; when it's *not*, restore the missing KEK (or its recovery KEK). Carries no
1616
+ * key material.
1617
+ */
1618
+ declare class KeyUnavailableError extends CloudRoaringError {
1619
+ }
1620
+ /**
1621
+ * A **transient** infrastructure fault that is safe to retry — throttling, a 5xx, a dropped connection,
1622
+ * a client-side request timeout. Drivers classify their backend's retryable faults and raise this (the
1623
+ * SDK-specific knowledge stays in the SDK-specific driver); the retry layer (`core/retry`) retries **only**
1624
+ * this class, never a deterministic error like {@link ValidationError}, {@link IntegrityError},
1625
+ * {@link NotFoundError}, or {@link WriteConflictError} (retrying those is pointless or wrong). The original
1626
+ * error is preserved in `cause` so callers can still inspect it.
1627
+ *
1628
+ * Note for logging hygiene (threat-model S12): `cause` is the **raw SDK error**, which may carry operational
1629
+ * metadata (endpoint host, request IDs, `$metadata`). The library's own `message` is identifier-only and safe
1630
+ * to log; if you serialize the whole error *chain*, be aware you're including that metadata.
1631
+ */
1632
+ declare class TransientError extends CloudRoaringError {
1633
+ /** A second brand so the whole transient subtree (incl. {@link TimeoutError}) is classifiable cross-bundle. */
1634
+ readonly [TRANSIENT_BRAND]: true;
1635
+ constructor(message: string, options?: {
1636
+ cause?: unknown;
1637
+ });
1638
+ }
1639
+ /**
1640
+ * A single attempt exceeded its time budget. Subclass of {@link TransientError} so the retry layer treats a
1641
+ * timeout as retryable by default — a stalled request often succeeds on a fresh connection. Raised by a
1642
+ * driver whose injected client reports a request timeout — setting one on your injected client is the
1643
+ * recommended way to bound a hang.
1644
+ */
1645
+ declare class TimeoutError extends TransientError {
1646
+ }
1647
+ /** Any error thrown by CloudRoaring (any tier, any bundle). */
1648
+ declare function isCloudRoaringError(err: unknown): err is CloudRoaringError;
1649
+ /** An OCC conditional write/delete lost the race — retry the read-modify-write, don't fail. */
1650
+ declare function isWriteConflictError(err: unknown): err is WriteConflictError;
1651
+ /** A retryable transient infrastructure fault (incl. {@link TimeoutError}). The retry layer keys on this. */
1652
+ declare function isTransientError(err: unknown): err is TransientError;
1653
+ /** A requested object/row does not exist. */
1654
+ declare function isNotFoundError(err: unknown): err is NotFoundError;
1655
+ /** Corrupt/oversized/failed-checksum bytes from a tier. */
1656
+ declare function isIntegrityError(err: unknown): err is IntegrityError;
1657
+ /** Invalid caller input. */
1658
+ declare function isValidationError(err: unknown): err is ValidationError;
1659
+
1660
+ /** AES-256-GCM AEAD bound to one 32-byte key. A fresh CSPRNG 96-bit nonce per `seal`; `aad` is authenticated,
1661
+ * never stored. Nonce-reuse safety: the random-96-bit birthday bound is ~q²/2⁹⁷, negligible below ~2³² seals
1662
+ * under one key. A per-segment DEK is **reused across all generations** (compaction re-encrypts under it), so
1663
+ * seal count is cumulative over the segment's life — a segment approaching ~2³² lifetime chunk-seals should be
1664
+ * re-seeded (a fresh segment ⇒ fresh DEK). Far beyond any realistic workload; noted for completeness. */
1665
+ declare class NodeAead implements Aead {
1666
+ private readonly key;
1667
+ constructor(key: Uint8Array);
1668
+ seal(plaintext: Uint8Array, aad: Uint8Array): AeadSealed;
1669
+ open(sealed: AeadSealed, aad: Uint8Array): Uint8Array;
1670
+ }
1671
+ /**
1672
+ * Options for {@link InProcessKeystore}. `keys` maps a `keyId` → its 32-byte KEK; `activeKeyId` names the KEK
1673
+ * new DEKs are wrapped under; an optional `recoveryKeyId` additionally wraps each new DEK under a second
1674
+ * (e.g. offline) KEK, so losing the active KEK isn't fatal. Rotating the active KEK needs no data
1675
+ * re-encryption: add the new KEK, point `activeKeyId` at it, keep the old one to unwrap existing segments.
1676
+ */
1677
+ interface InProcessKeystoreOptions {
1678
+ readonly keys: Readonly<Record<string, Uint8Array>>;
1679
+ readonly activeKeyId: string;
1680
+ readonly recoveryKeyId?: string;
1681
+ }
1682
+ /**
1683
+ * Dependency-free BYOK keystore: envelope-wraps a fresh random per-segment DEK under the active (and optional
1684
+ * recovery) KEK using `node:crypto`. No external service. The raw DEK never leaves a returned {@link NodeAead}.
1685
+ */
1686
+ declare class InProcessKeystore implements IKeystore {
1687
+ private readonly keys;
1688
+ private readonly activeKeyId;
1689
+ private readonly recoveryKeyId;
1690
+ constructor(options: InProcessKeystoreOptions);
1691
+ createDek(): Promise<{
1692
+ wrapped: WrappedDek[];
1693
+ aead: Aead;
1694
+ }>;
1695
+ openDek(wrapped: readonly WrappedDek[]): Promise<Aead>;
1696
+ }
1697
+
1698
+ /**
1699
+ * Crypto-shred erasure (Phase 4e, [DECISIONS #19]).
1700
+ *
1701
+ * GDPR "right to erasure" on immutable/backed-up Cold storage: you can't delete a `.crbm` object from every
1702
+ * backup, but you can **delete its key**. `destroySegment` drops the segment's wrapped DEK(s) from the registry
1703
+ * (a CAS to a `destroyed` tombstone) — the encrypted Cold bytes are then permanently unreadable, everywhere,
1704
+ * forever — and physically deletes the segment's (cleartext) Warm rows. **Immediate + irreversible**, so it's
1705
+ * gated behind an explicit confirmation (name the exact segment/namespace). A `destroyed` segment reads as
1706
+ * empty (its DEK is gone). Only works on an **encrypted** segment; a cleartext segment has no key to shred.
1707
+ *
1708
+ * Note (scope): this shreds the Cold key + clears Warm rows present *now*. Preventing *new* writes to a
1709
+ * destroyed segment (a registry-checked write path) is a later hardening; the write path stays uncoupled today.
1710
+ */
1711
+
1712
+ interface EraseDeps {
1713
+ readonly registry: IRegistryDriver;
1714
+ readonly warm: IWarmDriver;
1715
+ }
1716
+ interface DestroyResult {
1717
+ readonly segment: string;
1718
+ readonly namespace?: string;
1719
+ /** True iff the segment is now a `destroyed` tombstone (incl. the idempotent already-destroyed case). */
1720
+ readonly destroyed: boolean;
1721
+ /**
1722
+ * True iff **this call** performed a genuine crypto-shred — an encrypted segment whose wrapped DEK(s) were
1723
+ * dropped, so its Cold bytes are now permanently unreadable. False for a cleartext tombstone (bytes remain
1724
+ * readable), for the idempotent already-destroyed case, and for an absent segment. This is what the
1725
+ * `segment.erase` audit event keys off — a cleartext "erase" is not an irreversible destruction.
1726
+ */
1727
+ readonly cryptoShredded: boolean;
1728
+ /** `'absent'` (no registry row), `'already'` (already destroyed), or `'cleartext'` (no DEK to shred). */
1729
+ readonly reason?: string;
1730
+ /** Warm rows physically deleted as part of the erase. */
1731
+ readonly warmRowsDeleted: number;
1732
+ }
1733
+ /**
1734
+ * Crypto-shred one segment. **Irreversible.** `confirmSegment` must equal `ref.segment` (a guard against an
1735
+ * accidental destroy — you must name the exact segment). A non-encrypted segment is rejected unless you opt in
1736
+ * via `allowCleartext` (there's no key to shred; its Cold bytes stay readable, only Warm is cleared).
1737
+ */
1738
+ declare function destroySegment(ref: SegmentRef, deps: EraseDeps, options: {
1739
+ confirmSegment: string;
1740
+ allowCleartext?: boolean;
1741
+ audit?: IAuditSink;
1742
+ }): Promise<DestroyResult>;
1743
+ /**
1744
+ * Crypto-shred every segment in a namespace. **Irreversible.** `confirmNamespace` must equal `namespace`.
1745
+ * Returns a per-segment result (skips cleartext segments unless `allowCleartext`).
1746
+ */
1747
+ declare function eraseNamespace(namespace: string, deps: EraseDeps, options: {
1748
+ confirmNamespace: string;
1749
+ allowCleartext?: boolean;
1750
+ audit?: IAuditSink;
1751
+ }): Promise<{
1752
+ destroyed: DestroyResult[];
1753
+ }>;
1754
+
1755
+ /**
1756
+ * Fail-safe cross-tier DR check (gap #11). The registry (`currentGen`, e.g. in
1757
+ * DynamoDB) and the immutable `.crbm` generations (e.g. in S3) back up and restore **independently**, so a
1758
+ * failover can recover the registry *ahead of* the object store — leaving `currentGen` pointing at a generation
1759
+ * whose `.crbm` isn't present yet. That's a torn restore: reads of the affected segment then throw. This scan
1760
+ * detects it up front (run it at startup after a restore) instead of discovering it on the first read.
1761
+ *
1762
+ * Read-only; bounded fan-out. `destroyed` (crypto-shredded) segments are skipped — their Cold is intentionally
1763
+ * gone/unreadable, not a torn restore. A segment whose Cold/registry can't be read this pass is recorded in
1764
+ * `errored` (never aborts the scan). Each segment is checked against its **authoritative live pointer** — one
1765
+ * strong `registry.get` per segment — never the enumeration snapshot from `registry.list`, which can be
1766
+ * eventually-consistent (an unindexed Scan) and lag a recent in-place pointer advance: trusting it would both
1767
+ * miss a torn *live* generation and cry torn on a generation the pointer has already advanced past (GC'd during
1768
+ * the scan). Residual: a full compaction+GC landing in the tiny per-segment get→list gap can still yield a
1769
+ * transient false positive — run the scan against a quiesced fleet (the documented restore procedure), or re-run
1770
+ * to confirm a reported tear.
1771
+ */
1772
+
1773
+ interface ConsistencyIssue {
1774
+ readonly segment: string;
1775
+ readonly namespace?: string;
1776
+ /** The registry's `currentGen` for the segment — the generation whose `.crbm` is missing from Cold. */
1777
+ readonly currentGen: number;
1778
+ /** The only issue class today: `currentGen` references a Cold generation that is not present (torn restore). */
1779
+ readonly issue: 'missing-cold-generation';
1780
+ }
1781
+ /** A segment that could not be checked this pass (Cold/registry read fault) — not proof of a torn restore. */
1782
+ interface ConsistencyErrorEntry {
1783
+ readonly segment: string;
1784
+ readonly namespace?: string;
1785
+ /** The read error's message (never the raw error, so the report stays serializable/loggable). */
1786
+ readonly error: string;
1787
+ }
1788
+ interface ConsistencyReport {
1789
+ /** Registered segments scanned (a `destroyed` segment counts as scanned but is never an issue). */
1790
+ readonly checked: number;
1791
+ /** Segments whose `currentGen` `.crbm` is absent — recover the object store (or restore to a coherent point). */
1792
+ readonly inconsistent: ConsistencyIssue[];
1793
+ /**
1794
+ * Segments that couldn't be read this pass (a transient/partial object store during a restore is exactly when
1795
+ * this runs) — **triage these too**: an unread segment is neither proven-coherent nor proven-torn. Empty on a
1796
+ * clean pass.
1797
+ */
1798
+ readonly errored: ConsistencyErrorEntry[];
1799
+ }
1800
+ /**
1801
+ * Verify every registered segment's `currentGen` `.crbm` actually exists in Cold. Enumerates the registry
1802
+ * (optionally one namespace) and, for each non-`destroyed` segment, checks the object store lists that
1803
+ * generation. Returns the torn segments in `inconsistent` (empty ⇒ coherent) and any unreadable segments in
1804
+ * `errored`.
1805
+ */
1806
+ declare function runConsistencyCheck(deps: {
1807
+ readonly cold: IColdDriver;
1808
+ readonly registry: IRegistryDriver;
1809
+ }, options?: {
1810
+ namespace?: string;
1811
+ concurrency?: number;
1812
+ }): Promise<ConsistencyReport>;
1813
+
1814
+ /**
1815
+ * Retry decorators (Phase 4b).
1816
+ *
1817
+ * Transparent wrappers that add bounded, jittered retry of **transient** faults to any driver, using the one
1818
+ * shared `core/retry` primitive — so every backend (DynamoDB, S3, LocalFS, …) inherits the same, simulator-
1819
+ * replayable policy instead of each rolling its own. Pure composition over the port interfaces (no SDK, no
1820
+ * I/O of their own); the wrapped driver is responsible for *classifying* its transient faults (raising
1821
+ * {@link TransientError}); these decorators decide *whether and when* to retry.
1822
+ *
1823
+ * **Streaming methods** (`listChunks`, `list`) are retried by **re-enumerating from the start**, buffering
1824
+ * the result — a partially-consumed async iterator can't be safely resumed mid-stream (it would re-yield
1825
+ * earlier items). Their consumers in the engine already collect fully (the Warm dirty-set is small under
1826
+ * Topology-A; the cold `list` is a discovery scan), so buffering preserves current semantics. Point methods
1827
+ * are retried in place with no buffering.
1828
+ *
1829
+ * What is **not** retried here: {@link WriteConflictError} (OCC — the engine's read-modify-write loop owns
1830
+ * that; a blind replay would re-apply against a stale token), and every deterministic error
1831
+ * (`ValidationError`/`IntegrityError`/`NotFoundError`/…). Default classifier: {@link isTransient}.
1832
+ */
1833
+
1834
+ interface RetryingOptions {
1835
+ readonly clock: Clock;
1836
+ readonly rng: Rng;
1837
+ /** Defaults to {@link DEFAULT_RETRY_POLICY}. */
1838
+ readonly policy?: RetryPolicy;
1839
+ /** Override which errors are retryable. Default: {@link isTransient} (any `TransientError`). */
1840
+ readonly isRetryable?: (err: unknown) => boolean;
1841
+ /** Observability hook fired before each backoff wait. */
1842
+ readonly onRetry?: (info: {
1843
+ attempt: number;
1844
+ delayMs: number;
1845
+ err: unknown;
1846
+ }) => void;
1847
+ }
1848
+ /** Wrap a warm driver so its calls retry transient faults. OCC conflicts are deliberately not retried here. */
1849
+ declare class RetryingWarmDriver implements IWarmDriver {
1850
+ private readonly inner;
1851
+ private readonly policy;
1852
+ private readonly deps;
1853
+ constructor(inner: IWarmDriver, opts: RetryingOptions);
1854
+ get(ref: ChunkRef, opts?: WarmReadOptions): Promise<WarmRow | null>;
1855
+ putConditional(ref: ChunkRef, bytes: Uint8Array, expected: Token | NoRow): Promise<{
1856
+ token: Token;
1857
+ }>;
1858
+ deleteConditional(ref: ChunkRef, expected: Token): Promise<void>;
1859
+ listChunks(ref: SegmentRef, opts?: WarmReadOptions): AsyncIterable<{
1860
+ chunkKey: number;
1861
+ } & WarmRow>;
1862
+ }
1863
+ /** Wrap a cold chunk source so its reads retry transient faults. */
1864
+ declare class RetryingColdChunkSource implements ColdChunkSource {
1865
+ private readonly inner;
1866
+ private readonly policy;
1867
+ private readonly deps;
1868
+ /** Present only when the inner source supports it — so capability detection stays honest. */
1869
+ readonly sizeOf?: (ref: SegmentRef) => Promise<SegmentSize | null>;
1870
+ readonly cardinalities?: (ref: SegmentRef) => Promise<ReadonlyMap<number, number> | null>;
1871
+ readonly currentGeneration?: (ref: SegmentRef) => Promise<number | null>;
1872
+ constructor(inner: ColdChunkSource, opts: RetryingOptions);
1873
+ getChunk(ref: ChunkRef): Promise<Uint8Array | null>;
1874
+ listChunkKeys(ref: SegmentRef): Promise<number[]>;
1875
+ }
1876
+ /** Wrap a cold driver so its byte operations retry transient faults. */
1877
+ declare class RetryingColdDriver implements IColdDriver {
1878
+ private readonly inner;
1879
+ private readonly policy;
1880
+ private readonly deps;
1881
+ constructor(inner: IColdDriver, opts: RetryingOptions);
1882
+ capabilities(): ColdCaps;
1883
+ putImmutable(key: GenKey, write: (sink: BlobSink) => Promise<void>): Promise<{
1884
+ size: number;
1885
+ sha256: string;
1886
+ }>;
1887
+ getRange(key: GenKey, offset: number, length: number): Promise<Uint8Array>;
1888
+ getTail(key: GenKey, maxBytes: number): Promise<{
1889
+ bytes: Uint8Array;
1890
+ size: number;
1891
+ }>;
1892
+ delete(key: GenKey): Promise<void>;
1893
+ list(ref: SegmentRef): AsyncIterable<GenKey>;
1894
+ }
1895
+ /** Wrap a registry driver so its calls retry transient faults. CAS conflicts are not retried (caller-owned). */
1896
+ declare class RetryingRegistryDriver implements IRegistryDriver {
1897
+ private readonly inner;
1898
+ private readonly policy;
1899
+ private readonly deps;
1900
+ constructor(inner: IRegistryDriver, opts: RetryingOptions);
1901
+ capabilities(): RegCaps;
1902
+ get(ref: SegmentRef): Promise<RegistryRecord | null>;
1903
+ create(ref: SegmentRef, record: NewRegistryRecord): Promise<{
1904
+ token: Token;
1905
+ }>;
1906
+ compareAndSwap(ref: SegmentRef, expected: Token, patch: RegistryPatch): Promise<{
1907
+ token: Token;
1908
+ }>;
1909
+ list(namespace?: string): AsyncIterable<RegistryRecord>;
1910
+ delete(ref: SegmentRef): Promise<void>;
1911
+ }
1912
+
1913
+ interface CrbmWriterOptions {
1914
+ /** Self-describing generation number (also encoded in the object key). */
1915
+ readonly generation: number;
1916
+ readonly roaringSerializationId?: number;
1917
+ readonly elementWidth?: number;
1918
+ /** When set, payloads + index are AES-256-GCM-encrypted, bound to `(segment, generation, scope)` via AAD. */
1919
+ readonly crypto?: CrbmCrypto;
1920
+ }
1921
+ declare class CrbmWriter {
1922
+ private readonly sink;
1923
+ private readonly options;
1924
+ private readonly entries;
1925
+ private offset;
1926
+ private lastKey;
1927
+ private totalCardinality;
1928
+ private preambleWritten;
1929
+ private finished;
1930
+ constructor(sink: BlobSink, options: CrbmWriterOptions);
1931
+ /**
1932
+ * Append one chunk's payload (the roaring-serialized bytes) with its cardinality. `chunkKey` must be
1933
+ * strictly greater than the previous one (ascending, no duplicates); `cardinality` is `[1, 65536]`.
1934
+ */
1935
+ addChunk(chunkKey: number, payload: Uint8Array, cardinality: number): Promise<void>;
1936
+ /** Seal one chunk payload into the on-disk frame `nonce ‖ ciphertext ‖ tag`, bound to its chunkKey. */
1937
+ private sealChunk;
1938
+ /** Write the index region + the fixed footer. After this the object is complete and immutable. */
1939
+ finish(): Promise<void>;
1940
+ private ensurePreamble;
1941
+ private encodeIndex;
1942
+ private buildFooter;
1943
+ }
1944
+
1945
+ export { AWS_US_EAST_1_ONDEMAND, Aead, AeadSealed, type AuditEvent, type AuditEventKind, BlobReader, BlobSink, BoundedLru, type Budget, BudgetExceededError, type BudgetOption, type BulkLoadResult, CapabilityError, ChunkRef, type Clock, CloudRoaringError, type CodecBitmap, type CodecInterface, ColdCaps, ColdChunkSource, type CompactionCandidate, type CompactionCycleResult, type CompactionDeps, type CompactionOptions, type CompactionResult, type ConsistencyErrorEntry, type ConsistencyIssue, type ConsistencyReport, type CostAdvisory, type CostReport, CountingMetricsSink, CrbmColdChunkSource, type CrbmColdChunkSourceOptions, CrbmCrypto, CrbmReader, type CrbmReaderOptions, CrbmWriter, type CrbmWriterOptions, DEFAULT_BUDGET, DEFAULT_OCC_BACKOFF, DEFAULT_PRICING, DEFAULT_RETRY_POLICY, type DestroyResult, type DiscoveryOptions, type EngineDeps, type EraseDeps, type EstimateInput, type ExportFailure, type ExportFormat, type ExportManifest, type ExportOptions, type ExportSink, type ExportWriter, type ExportedSegment, GenKey, type IAuditSink, IColdDriver, IKeystore, type IMetricsSink, IRegistryDriver, IWarmDriver, InProcessKeystore, type InProcessKeystoreOptions, IntegrityError, KeyUnavailableError, LocalFsColdDriver, LocalFsRegistryDriver, type LocalFsRegistryDriverOptions, LocalFsWarmDriver, MemoryColdChunkSource, MemoryColdDriver, MemoryRegistryDriver, type MemoryRegistryDriverOptions, MemoryWarmDriver, type MetricEvent, type MetricOpName, type MetricsSnapshot, NOOP_AUDIT, NOOP_METRICS, NewRegistryRecord, NoRow, NodeAead, NotFoundError, type PricingProfile, RecordingAuditSink, RegCaps, RegistryPatch, RegistryRecord, type RetryDeps, type RetryPolicy, RetryingColdChunkSource, RetryingColdDriver, type RetryingOptions, RetryingRegistryDriver, RetryingWarmDriver, type Rng, SegmentEngine, SegmentRef, SegmentSize, type SegmentSizing, TimeoutError, Token, type Topology, TransientError, UnsupportedError, ValidationError, WarmReadOptions, WarmRow, type Workload, WrappedDek, WriteConflictError, bulkLoadCrbmGeneration, checkBudget, chunkRefKey, compactSegment, destroySegment, eraseNamespace, estimateCost, findCompactable, gcOrphanGenerations, groundedReport, isCloudRoaringError, isIntegrityError, isNotFoundError, isTransient, isTransientError, isValidationError, isWriteConflictError, joinId, mapWithConcurrency, publishGeneration, resolveBudget, resolvePerOpBudget, runCompactionCycle, runConsistencyCheck, runExport, safeMetrics, segmentKey, splitId, validateCompactionOptions, validateSegmentRef, withRetry, writeCrbmGeneration };