@cloudbitmaps/core 0.19.0 → 0.20.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 (39) hide show
  1. package/dist/{chunk-TNWTIAON.js → chunk-YSC7NJWV.js} +314 -216
  2. package/dist/chunk-YSC7NJWV.js.map +7 -0
  3. package/dist/core/audit.d.ts +56 -3
  4. package/dist/core/combine-many.d.ts +13 -4
  5. package/dist/core/consistency.d.ts +10 -4
  6. package/dist/core/crbm/fingerprint.d.ts +18 -0
  7. package/dist/core/crbm/format.d.ts +2 -4
  8. package/dist/core/crbm/frame.d.ts +10 -0
  9. package/dist/core/crbm/reader.d.ts +1 -3
  10. package/dist/core/crbm-storage-source.d.ts +249 -96
  11. package/dist/core/engine.d.ts +22 -14
  12. package/dist/core/erase-id.d.ts +62 -26
  13. package/dist/core/generation-gc.d.ts +3 -2
  14. package/dist/core/keys.d.ts +28 -0
  15. package/dist/core/leases.d.ts +7 -0
  16. package/dist/core/load.d.ts +11 -7
  17. package/dist/core/metrics.d.ts +5 -5
  18. package/dist/core/pinned-storage-source.d.ts +3 -3
  19. package/dist/core/pointer-id.d.ts +19 -0
  20. package/dist/core/ports.d.ts +131 -35
  21. package/dist/core/reader-defaults.d.ts +14 -3
  22. package/dist/core/rollback.d.ts +2 -1
  23. package/dist/core/summary.d.ts +38 -21
  24. package/dist/core/token.d.ts +13 -8
  25. package/dist/driver-kit.d.ts +2 -1
  26. package/dist/driver-kit.js +11 -4
  27. package/dist/driver-kit.js.map +2 -2
  28. package/dist/drivers/_shared/object-registry.d.ts +1 -1
  29. package/dist/drivers/_shared/registry.d.ts +26 -17
  30. package/dist/drivers/localfs/storage.d.ts +7 -6
  31. package/dist/drivers/memory.d.ts +8 -6
  32. package/dist/drivers/retry/retrying-chunk-source.d.ts +1 -5
  33. package/dist/export/index.d.ts +16 -0
  34. package/dist/index.d.ts +2 -5
  35. package/dist/index.js +1288 -1080
  36. package/dist/index.js.map +4 -4
  37. package/package.json +1 -1
  38. package/dist/chunk-TNWTIAON.js.map +0 -7
  39. package/dist/core/cost.d.ts +0 -351
@@ -3,7 +3,7 @@
3
3
  * from the metrics sink (`IMetricsSink`). Different audience (an audit log / SIEM, not a dashboard), different
4
4
  * retention, and only the compliance-relevant *state changes* — never routine reads/writes (that's the metrics
5
5
  * sink). It doubles as the GDPR Art. 30 "record of processing" surface: publishes, refused loads, rollbacks,
6
- * rewrites, erasures and disposals.
6
+ * rewrites, an erasure's deletion of the generations holding an id, crypto-shreds and disposals.
7
7
  *
8
8
  * Like `Clock`/`Rng`/`IMetricsSink`, it's injected (into the operations that emit — a load, the `*Into`
9
9
  * verbs, a rollback, the erasure rewrite, crypto-shred, disposal and the retention sweep) and wrapped
@@ -16,12 +16,24 @@
16
16
  * wrappings, no data re-encryption), so there is no library call to hook. A future `rewrapSegment()` op would
17
17
  * add a `kek.rotate` variant; until then, audit key changes at your keystore-config layer.
18
18
  */
19
- /** A security/compliance-relevant state change. Vendor-neutral; the sink adds its own timestamp/actor. */
19
+ /**
20
+ * A security/compliance-relevant state change. Vendor-neutral; the sink adds its own timestamp/actor.
21
+ *
22
+ * Every event about a segment carries `incarnation`: the id of the registry row's incarnation the operation acted on.
23
+ * A segment's name can be purged and created again, and its generations then start again at `0`; the incarnation
24
+ * tells those lives of the name apart, and every write of a row keeps it. It does not make a generation number name
25
+ * one object for good: within one incarnation, a number whose object was deleted (a refused load's object, or a
26
+ * generation an erasure deleted above the pointer) can be taken again by a later load. It is absent when the row's
27
+ * token carries no incarnation id, for example from a registry of the caller's own that issues tokens in another
28
+ * form, and on a `segment.load-refused` from a load that found no row.
29
+ */
20
30
  export type AuditEvent = {
21
31
  /** A new immutable Storage generation *became the segment's current generation*, by a load's publish. */
22
32
  readonly kind: 'segment.publish';
23
33
  readonly namespace?: string;
24
34
  readonly segment: string;
35
+ /** The incarnation of the row whose pointer the publish moved. */
36
+ readonly incarnation?: string;
25
37
  readonly generation: number;
26
38
  } | {
27
39
  /**
@@ -38,6 +50,8 @@ export type AuditEvent = {
38
50
  readonly kind: 'segment.rollback';
39
51
  readonly namespace?: string;
40
52
  readonly segment: string;
53
+ /** The incarnation of the row whose pointer the rollback moved. */
54
+ readonly incarnation?: string;
41
55
  readonly fromGeneration: number | null;
42
56
  readonly generation: number;
43
57
  } | {
@@ -59,6 +73,8 @@ export type AuditEvent = {
59
73
  readonly kind: 'segment.load-refused';
60
74
  readonly namespace?: string;
61
75
  readonly segment: string;
76
+ /** The incarnation of the row the load read before it wrote; absent when it found no row. */
77
+ readonly incarnation?: string;
62
78
  readonly generation: number;
63
79
  readonly reason: 'empty' | 'min-cardinality' | 'min-retained' | 'max-growth' | 'superseded';
64
80
  readonly cardinality: number;
@@ -69,13 +85,46 @@ export type AuditEvent = {
69
85
  * place. Today the one emitter is `eraseIdFromSegment` (a subject erasure clearing one id), and the event is
70
86
  * emitted at the publish — before the superseded generation is collected — so the record exists the moment
71
87
  * the generation without the id is authoritative. A `segment.publish` is NOT also emitted for a rewrite: a
72
- * rewrite derives its content from the segment itself, a publish brings content in from outside.
88
+ * rewrite derives its content from the segment itself, a publish brings content in from outside. An erasure
89
+ * that finds the id only outside the current generation, or deletes only objects no read of the segment can
90
+ * open, rewrites nothing and emits `segment.collect`.
73
91
  */
74
92
  readonly kind: 'segment.rewrite';
75
93
  readonly namespace?: string;
76
94
  readonly segment: string;
95
+ /** The incarnation of the row the rewrite read `fromGeneration` from and published on. */
96
+ readonly incarnation?: string;
77
97
  readonly fromGeneration: number;
78
98
  readonly generation: number;
99
+ } | {
100
+ /**
101
+ * An erasure of one id **deleted objects** no reader resolves, and rewrote none. It found the id only in such
102
+ * objects, or deleted only objects no read of the segment can open, which it cannot search and deletes whatever
103
+ * the id. The cases: an id that only generations other than the current one hold (someone who left the segment,
104
+ * whose bit the reader grace window or a rollback target keeps); objects left under a tombstone (a cleartext
105
+ * destroy, or a drop whose sweep left one); a first load's object on a segment with no generation yet (its load
106
+ * still running, or one that crashed before its publish); and an object sealed under a key the segment's row does
107
+ * not hold, or an encrypted one under a cleartext row (a first load's that lost the race, or crashed). The one
108
+ * emitter is `eraseIdFromSegment`, once a listing of the bucket shows nothing left holding the id, so the record
109
+ * exists only for a finished erasure. A call that ends otherwise throws or reports `erased: false` with another
110
+ * reason and emits nothing, even when it had deleted a holder; its re-run emits when it finishes, and reports
111
+ * `'not-member'` with no event when another collection deleted the last holder meanwhile, as its ledger entry
112
+ * does.
113
+ *
114
+ * `fromGeneration` is the newest generation the call found holding the id, and is absent when it found the id in
115
+ * none and deleted only objects it could not search: then the ledger lists no entry for the segment, and this
116
+ * event is the record of the deletion. `collected` is every generation the call deleted, ascending, and the same
117
+ * list as its result's: below the pointer, when a searched generation held the id, that is every generation,
118
+ * holder or not, since the deletion takes them all; above it, and wherever none did, only the holders and the
119
+ * objects it could not search. A holder another collection deleted first is not in it, so the list can be empty.
120
+ */
121
+ readonly kind: 'segment.collect';
122
+ readonly namespace?: string;
123
+ readonly segment: string;
124
+ /** The incarnation of the row the erasure read: the active row, or the tombstone. */
125
+ readonly incarnation?: string;
126
+ readonly fromGeneration?: number;
127
+ readonly collected: readonly number[];
79
128
  } | {
80
129
  /**
81
130
  * A segment was **crypto-shredded** — its wrapped DEK(s) are gone, so its at-rest Storage bytes are now
@@ -85,6 +134,8 @@ export type AuditEvent = {
85
134
  readonly kind: 'segment.erase';
86
135
  readonly namespace?: string;
87
136
  readonly segment: string;
137
+ /** The incarnation of the row the shred turned into a tombstone. */
138
+ readonly incarnation?: string;
88
139
  } | {
89
140
  /**
90
141
  * A segment was **disposed of** — tombstoned and its storage reclaimed (its Storage generations deleted) by
@@ -106,6 +157,8 @@ export type AuditEvent = {
106
157
  readonly kind: 'segment.dispose';
107
158
  readonly namespace?: string;
108
159
  readonly segment: string;
160
+ /** The incarnation of the tombstone the drop wrote or found. */
161
+ readonly incarnation?: string;
109
162
  readonly generationsDeleted: number;
110
163
  } | {
111
164
  /** A namespace erasure was executed. `segmentsShredded` is the count actually crypto-shredded (may be 0). */
@@ -26,16 +26,19 @@ export interface CombineManyOperand {
26
26
  /** The generation the operand is pinned to: `null` when pinned to a segment with none, `undefined` when not pinned. */
27
27
  readonly pinnedGeneration?: number | null;
28
28
  /**
29
- * The pin's version: the generation and the registry row's token it was taken under (`<generation>:<token>`), or just
30
- * the generation where there is no row. It is what tells a name deleted and created again, which restarts at
29
+ * The pin's version: the generation and the registry row's `pointerId` it was taken under (`<generation>:<pointerId>`),
30
+ * or just the generation where there is no row. It is what tells a name deleted and created again, which restarts at
31
31
  * generation 0, from the one that was pinned.
32
32
  */
33
33
  readonly pinnedVersion?: string | null;
34
34
  /** The fingerprint of the object the pin holds: the identity of the generation's bytes, where a number can be taken again. */
35
35
  readonly pinnedFingerprint?: string | null;
36
36
  /**
37
- * A fresh read of the segment's registry row: its current generation and token, or `null` when it has no row. Read
38
- * for every subtracted pinned operand just before the publishes, and once more at the end of the call for the rest.
37
+ * A fresh read of the segment's registry row: its current generation and, as `token`, its `pointerId` (the token of the
38
+ * last write that named a field a read resolves through, so a lease or a policy write does not change it), or `null`
39
+ * when it
40
+ * has no row. Read for every subtracted pinned operand just before the publishes, and once more at the end of the call
41
+ * for the rest.
39
42
  */
40
43
  readonly current?: (options?: {
41
44
  readonly fingerprint?: boolean;
@@ -310,3 +313,9 @@ export declare class GroupCost {
310
313
  * budget, both before any chunk is read.
311
314
  */
312
315
  export declare function runCombineMany<R>(compiled: CompiledCombineMany<R>, deps: CombineManyDeps): Promise<CombineManyRun<R>>;
316
+ /**
317
+ * The `pointerId` of a row's version (`<generation>:<pointerId>`, as a pin holds it), or `undefined` where the version
318
+ * names no row. A live version from the `.crbm` source ends with the opened object's fingerprint: read it through
319
+ * `rowVersionOf` first.
320
+ */
321
+ export declare function tokenOfVersion(version: string | null | undefined): string | undefined;
@@ -37,9 +37,13 @@ export interface ConsistencyIssue {
37
37
  readonly currentGen: number;
38
38
  /**
39
39
  * `missing-storage-generation`: `currentGen` references a Storage generation that is not present (torn restore).
40
- * `summary-mismatch` (only when asked for): the generation is there, but its row's summary says a different id
41
- * count or metadata than the object holds (a row restored from another point than its bucket, or a number re-taken
42
- * since). A count answers from that summary until a read opens the object, so it is what a restore leaves wrong.
40
+ * `summary-mismatch` (only when asked for): the generation is there, but its row's summary names another object
41
+ * than the one under its number, says a different id count or metadata than the object holds (a row restored from
42
+ * another point than its bucket, or an object put back from outside the library), or records a fingerprint that
43
+ * names no object. A summary that names another object is what a count and a `stat()` answer from while every read
44
+ * that opens the object refuses it; one whose count or metadata disagrees is answered from until a read opens the
45
+ * object, which stops that store using it; one whose fingerprint names no object is not used, so every read opens the
46
+ * object unchecked.
43
47
  */
44
48
  readonly issue: 'missing-storage-generation' | 'summary-mismatch';
45
49
  }
@@ -75,7 +79,9 @@ export interface ConsistencyReport {
75
79
  * `errored`.
76
80
  *
77
81
  * With `summaries: true` it also opens each segment's current object (one tail read, and a second for an index longer
78
- * than it) and holds the row's summary against it, reporting `summary-mismatch` where they disagree. A sealed summary
82
+ * than it) and holds the row's summary against it: the object it names by its fingerprint, and the count and metadata it
83
+ * says, reporting `summary-mismatch` where they disagree, and for a clear summary whose fingerprint names no object,
84
+ * which no read uses. A sealed summary
79
85
  * needs `deps.keystore`; without it the segment is counted in `summariesUnchecked`. Off by default, since the
80
86
  * default check only lists.
81
87
  */
@@ -0,0 +1,18 @@
1
+ /** What names an object: its size, then its footer's CRC. One spelling, for an open reader and a footer read alone. */
2
+ export declare const sizePart: (size: number) => string;
3
+ /** An object's fingerprint, from its size and its footer's CRC (see `CrbmReader.fingerprint`). */
4
+ export declare const fingerprintFor: (size: number, footerCrc: number) => string;
5
+ /**
6
+ * A fingerprint taken apart, or `undefined` for text that is not one an object can have: a size that is a safe integer
7
+ * no smaller than a `.crbm` preamble and footer, and a footer CRC that fits in 32 bits. For a row's summary, which
8
+ * names its object by it and reports the size from it.
9
+ */
10
+ export declare function fingerprintParts(fingerprint: unknown): {
11
+ readonly size: number;
12
+ readonly footerCrc: number;
13
+ } | undefined;
14
+ /**
15
+ * A fingerprint spelled short, for a key a read builds on every chunk: its size and its footer's CRC in base 36,
16
+ * `<size>.<crc>`. Two compact spellings are equal exactly when the fingerprints are.
17
+ */
18
+ export declare function compactFingerprint(fingerprint: string): string;
@@ -4,16 +4,14 @@
4
4
  * These byte widths/offsets are pinned by the golden corpus and must never change for v1 —
5
5
  * a new layout is a new format version. All multi-byte integers are little-endian (v1 fixes LE).
6
6
  */
7
+ import { FOOTER_BYTES, PREAMBLE_BYTES } from './frame.js';
7
8
  /** 4-byte magic at both file ends: "CRBM". */
8
9
  export declare const MAGIC: Uint8Array<ArrayBuffer>;
9
10
  export declare const VERSION_MAJOR = 1;
10
11
  export declare const VERSION_MINOR = 0;
11
- /** Front preamble: magic(4) + version_major(1) + version_minor(1) + reserved(2). */
12
- export declare const PREAMBLE_BYTES = 8;
12
+ export { FOOTER_BYTES, PREAMBLE_BYTES };
13
13
  /** Payloads begin immediately after the preamble. */
14
14
  export declare const PAYLOAD_START = 8;
15
- /** Fixed footer size (v1.0). */
16
- export declare const FOOTER_BYTES = 104;
17
15
  /** Byte offsets of each field within the 104-byte footer. */
18
16
  export declare const FOOTER: {
19
17
  readonly indexOffset: 0;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The two widths every `.crbm` v1 object is framed by: its front preamble and its footer. Frozen with the rest of the
3
+ * layout in `format.ts`, which re-exports them. They live in a module of their own, with no imports, so that what needs
4
+ * only the smallest size an object can have (the registry's check of a summary's fingerprint) does not bring the
5
+ * format's other constants, or the reader, with it.
6
+ */
7
+ /** Front preamble: magic(4) + version_major(1) + version_minor(1) + reserved(2). */
8
+ export declare const PREAMBLE_BYTES = 8;
9
+ /** Fixed footer size (v1.0). */
10
+ export declare const FOOTER_BYTES = 104;
@@ -49,8 +49,6 @@ export interface CrbmReaderOptions {
49
49
  */
50
50
  readonly lineage?: unknown;
51
51
  }
52
- /** An object's fingerprint, from its size and its footer's CRC (see {@link CrbmReader.fingerprint}). */
53
- export declare const fingerprintFor: (size: number, footerCrc: number) => string;
54
52
  /**
55
53
  * Whether the object behind `blob` says it is encrypted, from one tail read of a footer's worth, with no key and no
56
54
  * index; a footer that fails its own checks throws, as an open would. For a caller that must not open an object with
@@ -102,7 +100,7 @@ export declare class CrbmReader {
102
100
  * the object it reopens is the one it pinned, since a purged and reloaded name reuses the generation number.
103
101
  */
104
102
  get fingerprint(): string;
105
- /** Total object bytes (from the one-GET tail read) — for grounded storage cost. */
103
+ /** Total object bytes (from the one-GET tail read): the `sizeBytes` a segment's `stat()` reports. */
106
104
  get sizeBytes(): number;
107
105
  /**
108
106
  * Retained JS heap of this reader's parsed index — the weight the storage reader cache bounds on (a wide