@cloudbitmaps/core 0.11.2 → 0.12.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.
- package/dist/{chunk-5OMC5U7U.js → chunk-5W3XS2IU.js} +292 -22
- package/dist/chunk-5W3XS2IU.js.map +7 -0
- package/dist/core/audit.d.ts +6 -0
- package/dist/core/codec.d.ts +33 -1
- package/dist/core/consistency.d.ts +21 -2
- package/dist/core/cost.d.ts +91 -10
- package/dist/core/crbm/format.d.ts +31 -1
- package/dist/core/crbm/reader.d.ts +44 -2
- package/dist/core/crbm/writer.d.ts +32 -3
- package/dist/core/crbm-storage-source.d.ts +203 -22
- package/dist/core/crypto.d.ts +11 -7
- package/dist/core/determinism.d.ts +7 -0
- package/dist/core/engine.d.ts +17 -5
- package/dist/core/erase-id.d.ts +20 -0
- package/dist/core/erasure.d.ts +8 -4
- package/dist/core/generation-gc.d.ts +102 -4
- package/dist/core/load-input.d.ts +55 -0
- package/dist/core/load.d.ts +65 -10
- package/dist/core/metadata.d.ts +42 -0
- package/dist/core/pinned-storage-source.d.ts +2 -1
- package/dist/core/ports.d.ts +119 -13
- package/dist/core/registry-scan.d.ts +6 -0
- package/dist/core/retention-sweep.d.ts +47 -2
- package/dist/core/retry.d.ts +14 -0
- package/dist/core/rollback.d.ts +34 -4
- package/dist/core/summary.d.ts +61 -0
- package/dist/core/token.d.ts +36 -0
- package/dist/core/validate.d.ts +2 -0
- package/dist/driver-kit.js +39 -20
- package/dist/driver-kit.js.map +2 -2
- package/dist/drivers/_shared/entropy.d.ts +5 -0
- package/dist/drivers/_shared/object-registry.d.ts +36 -6
- package/dist/drivers/_shared/registry.d.ts +47 -19
- package/dist/drivers/localfs/registry.d.ts +10 -0
- package/dist/drivers/memory.d.ts +9 -3
- package/dist/drivers/retry/retrying-chunk-source.d.ts +2 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +1794 -461
- package/dist/index.js.map +4 -4
- package/dist/testing/fuzz-core.d.ts +6 -5
- package/package.json +1 -1
- package/dist/chunk-5OMC5U7U.js.map +0 -7
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Frozen `.crbm` v1
|
|
2
|
+
* Frozen `.crbm` v1 layout constants (format 1.0, with its optional extension block).
|
|
3
3
|
*
|
|
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).
|
|
@@ -41,6 +41,12 @@ export declare const FOOTER_CRC_COVERAGE: 96;
|
|
|
41
41
|
export declare const FLAG_ENCRYPTED: number;
|
|
42
42
|
export declare const FLAG_INDEX_COMPRESSED: number;
|
|
43
43
|
export declare const FLAG_LITTLE_ENDIAN: number;
|
|
44
|
+
/**
|
|
45
|
+
* The object carries an extension block just before its index (a generation's metadata). Set only when there is
|
|
46
|
+
* something to put in it, so an object without metadata is the same bytes as before the block existed; a reader that
|
|
47
|
+
* does not know the bit refuses the object rather than reading past what it cannot see.
|
|
48
|
+
*/
|
|
49
|
+
export declare const FLAG_EXTENSION: number;
|
|
44
50
|
/** Flag bits a v1 reader understands; any bit outside this mask is an unsupported feature. */
|
|
45
51
|
export declare const KNOWN_FLAGS: number;
|
|
46
52
|
/**
|
|
@@ -102,6 +108,30 @@ export declare const DEFAULT_MAX_INDEX_BYTES: number;
|
|
|
102
108
|
export declare const DEFAULT_MAX_PAYLOAD_BYTES: number;
|
|
103
109
|
/** A chunk's cardinality is in `[1, 65536]` (empty chunks are never written). */
|
|
104
110
|
export declare const MAX_CHUNK_CARDINALITY = 65536;
|
|
111
|
+
/**
|
|
112
|
+
* The extension block, present when the footer sets {@link FLAG_EXTENSION}: `section* ‖ u32 sectionsLength ‖ u32
|
|
113
|
+
* crc32c ‖ "CRBX"`, between the last payload and the index, so the index starts right after it. A reader finds it from
|
|
114
|
+
* `indexOffset`: the trailer is the {@link EXT_TRAILER_BYTES} bytes before the index, and the CRC32C covers the sections
|
|
115
|
+
* and the length field. Each section is `u8 type ‖ u32 length ‖ bytes`, in strictly ascending type order; a reader
|
|
116
|
+
* skips a type it does not know, so a section must be safe to ignore.
|
|
117
|
+
*/
|
|
118
|
+
export declare const EXT_MAGIC: Uint8Array<ArrayBuffer>;
|
|
119
|
+
/** The trailer: the sections' length (u32), their CRC32C (u32) and {@link EXT_MAGIC}. */
|
|
120
|
+
export declare const EXT_TRAILER_BYTES = 12;
|
|
121
|
+
/** A section's header: its type (u8) and its length (u32). */
|
|
122
|
+
export declare const EXT_SECTION_HEADER_BYTES = 5;
|
|
123
|
+
/**
|
|
124
|
+
* Cap on the sections of one block, in bytes. A reader refuses a larger block from its trailer alone, before it reads
|
|
125
|
+
* any sections beyond what it already holds (the read that brings the index may already hold up to this much before
|
|
126
|
+
* it), and a writer of any 1.x minor keeps within it, so a later section type stays readable by this reader.
|
|
127
|
+
*/
|
|
128
|
+
export declare const MAX_EXT_BYTES: number;
|
|
129
|
+
/**
|
|
130
|
+
* Section type 1: the generation's metadata, its canonical JSON in UTF-8. On an encrypted object the section is
|
|
131
|
+
* `nonce ‖ ciphertext ‖ tag`, sealed under the segment's key with the metadata scope of the associated data.
|
|
132
|
+
* Type 0 is not a section type: a reader refuses it.
|
|
133
|
+
*/
|
|
134
|
+
export declare const EXT_SECTION_METADATA = 1;
|
|
105
135
|
/** Fixed width of a per-chunk CRC32C field in the index (high-entropy → not varint). */
|
|
106
136
|
export declare const CRC32C_BYTES = 4;
|
|
107
137
|
/**
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { BlobReader } from '../blob.js';
|
|
2
2
|
import type { CrbmCrypto } from '../crypto.js';
|
|
3
|
+
import type { GenerationMetadata } from '../ports.js';
|
|
3
4
|
/**
|
|
4
5
|
* A parsed index as parallel typed arrays, one slot per entry, ascending by key. Typed arrays hold each field at
|
|
5
6
|
* its own width with no per-entry object, so the retained heap is the arrays' byte length and nothing else.
|
|
@@ -34,7 +35,11 @@ export interface CrbmReaderOptions {
|
|
|
34
35
|
readonly maxPayloadBytes?: number;
|
|
35
36
|
/** Hard cap on the whole index region fetched/parsed from one object (default 8 MB). */
|
|
36
37
|
readonly maxIndexBytes?: number;
|
|
37
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* Decryption context for an encrypted object (its DEK's AEAD + AAD builder). Required iff the object's footer sets
|
|
40
|
+
* `FLAG_ENCRYPTED`: an encrypted object opened without one throws {@link ValidationError}, and an object that is
|
|
41
|
+
* not encrypted, opened with one, is refused with {@link IntegrityError}.
|
|
42
|
+
*/
|
|
38
43
|
readonly crypto?: CrbmCrypto;
|
|
39
44
|
/**
|
|
40
45
|
* Opaque marker for the *incarnation of the name* this object belongs to — the caller's own identity for the
|
|
@@ -44,6 +49,15 @@ export interface CrbmReaderOptions {
|
|
|
44
49
|
*/
|
|
45
50
|
readonly lineage?: unknown;
|
|
46
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
|
+
/**
|
|
55
|
+
* Whether the object behind `blob` says it is encrypted, from one tail read of a footer's worth, with no key and no
|
|
56
|
+
* index; a footer that fails its own checks throws, as an open would. For a caller that must not open an object with
|
|
57
|
+
* the key it holds: an erasure looking for an id in a cleartext object under an encrypted segment, and a rollback
|
|
58
|
+
* checking that its target is what the row says.
|
|
59
|
+
*/
|
|
60
|
+
export declare function footerSaysEncrypted(blob: BlobReader): Promise<boolean>;
|
|
47
61
|
export declare class CrbmReader {
|
|
48
62
|
private readonly blob;
|
|
49
63
|
private readonly objectSize;
|
|
@@ -52,12 +66,22 @@ export declare class CrbmReader {
|
|
|
52
66
|
readonly lineage: unknown;
|
|
53
67
|
readonly totalCardinality: number;
|
|
54
68
|
private readonly index;
|
|
55
|
-
/**
|
|
69
|
+
/**
|
|
70
|
+
* True when `open()` satisfied the index, and any extension block, from the tail GET alone (no second range
|
|
71
|
+
* read).
|
|
72
|
+
*/
|
|
56
73
|
readonly servedFromTail: boolean;
|
|
57
74
|
/** Set iff the object is encrypted — used to decrypt each chunk payload in {@link getChunk}. */
|
|
58
75
|
private readonly crypto;
|
|
59
76
|
/** The footer's own CRC, as verified at open: it covers the index's CRC, the chunk count and the generation. */
|
|
60
77
|
private readonly footerCrc;
|
|
78
|
+
/**
|
|
79
|
+
* The metadata the generation was written with, checked and decrypted at open; `undefined` when it has none
|
|
80
|
+
* (every format 1.0 object). Frozen: every caller shares one copy.
|
|
81
|
+
*/
|
|
82
|
+
readonly metadata: GenerationMetadata | undefined;
|
|
83
|
+
/** What that metadata adds to {@link retainedBytes}; 0 when there is none. */
|
|
84
|
+
private readonly metadataWeight;
|
|
61
85
|
private constructor();
|
|
62
86
|
/**
|
|
63
87
|
* Which object this reader opened, as its size and its footer's CRC. The footer covers the index's CRC, so two
|
|
@@ -73,6 +97,13 @@ export declare class CrbmReader {
|
|
|
73
97
|
* arrays that hold it, {@link RETAINED_BYTES_PER_INDEX_ENTRY} per entry.
|
|
74
98
|
*/
|
|
75
99
|
get retainedIndexBytes(): number;
|
|
100
|
+
/**
|
|
101
|
+
* What this reader holds, the weight the storage reader cache bounds on: its parsed index
|
|
102
|
+
* ({@link retainedIndexBytes}), plus a weight for the metadata it decoded, from its canonical length and its number
|
|
103
|
+
* of keys. Equal to {@link retainedIndexBytes} for a generation with no metadata. A cache heuristic, not a contract:
|
|
104
|
+
* how the metadata is weighed may change in any release.
|
|
105
|
+
*/
|
|
106
|
+
get retainedBytes(): number;
|
|
76
107
|
/** Per-chunk cardinality (`chunkKey → count`) from the parsed index — no payload reads. */
|
|
77
108
|
cardinalities(): Map<number, number>;
|
|
78
109
|
/**
|
|
@@ -100,4 +131,15 @@ export declare class CrbmReader {
|
|
|
100
131
|
*/
|
|
101
132
|
getChunk(chunkKey: number): Promise<Uint8Array | null>;
|
|
102
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Read the sections of an extension block, its bytes before the trailer, once their CRC has passed. Each section is
|
|
136
|
+
* `u8 type ‖ u32 length ‖ bytes`, in strictly ascending type order, and together they fill the region exactly. Type 1
|
|
137
|
+
* is the generation's metadata, held to the metadata rules and its canonical form (and opened first with `crypto`,
|
|
138
|
+
* under the metadata scope, when the object is encrypted); a type this build does not know is a later build's, and
|
|
139
|
+
* is skipped. Returns the metadata, or `undefined` when the block has none. Every other shape is an
|
|
140
|
+
* {@link IntegrityError}.
|
|
141
|
+
*
|
|
142
|
+
* Exported for the coverage-guided fuzz harness, which drives it directly, past the block's CRC; not public API.
|
|
143
|
+
*/
|
|
144
|
+
export declare function parseExtension(sections: Uint8Array, crypto: CrbmCrypto | undefined): GenerationMetadata | undefined;
|
|
103
145
|
export declare function parseIndex(indexBytes: Uint8Array, payloadEnd: number, maxPayloadBytes: number, minPayloadBytes?: number): ParsedIndex;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CrbmCrypto } from '../crypto.js';
|
|
2
|
+
import type { GenerationMetadata } from '../ports.js';
|
|
2
3
|
import type { BlobSink } from '../blob.js';
|
|
3
4
|
export interface CrbmWriterOptions {
|
|
4
5
|
/** Self-describing generation number (also encoded in the object key). */
|
|
@@ -13,6 +14,13 @@ export interface CrbmWriterOptions {
|
|
|
13
14
|
readonly elementWidth?: number;
|
|
14
15
|
/** When set, payloads + index are AES-256-GCM-encrypted, bound to `(segment, generation, scope)` via AAD. */
|
|
15
16
|
readonly crypto?: CrbmCrypto;
|
|
17
|
+
/**
|
|
18
|
+
* The generation's metadata, checked against the metadata rules and copied as its canonical JSON when the writer
|
|
19
|
+
* is constructed, so a later change to the caller's object never reaches the object. A value that breaks a rule
|
|
20
|
+
* throws {@link ValidationError} from the constructor, before a byte is written. None, or the empty object, writes
|
|
21
|
+
* format 1.0.
|
|
22
|
+
*/
|
|
23
|
+
readonly metadata?: GenerationMetadata;
|
|
16
24
|
}
|
|
17
25
|
export declare class CrbmWriter {
|
|
18
26
|
private readonly sink;
|
|
@@ -23,17 +31,38 @@ export declare class CrbmWriter {
|
|
|
23
31
|
private totalCardinality;
|
|
24
32
|
private preambleWritten;
|
|
25
33
|
private finished;
|
|
34
|
+
/** The metadata's canonical JSON, copied at construction; `undefined` leaves the footer's extension flag clear. */
|
|
35
|
+
private readonly metadata;
|
|
36
|
+
/** Bytes written so far, the footer included once {@link finish} has run. */
|
|
37
|
+
private written;
|
|
38
|
+
private footerCrc32c;
|
|
26
39
|
constructor(sink: BlobSink, options: CrbmWriterOptions);
|
|
27
40
|
/**
|
|
28
41
|
* Append one chunk's payload (the roaring-serialized bytes) with its cardinality. `chunkKey` must be
|
|
29
42
|
* strictly greater than the previous one (ascending, no duplicates); `cardinality` is `[1, 65536]`.
|
|
30
43
|
*/
|
|
31
44
|
addChunk(chunkKey: number, payload: Uint8Array, cardinality: number): Promise<void>;
|
|
32
|
-
/**
|
|
33
|
-
|
|
34
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Write the extension block (when there is metadata), the index region and the fixed footer. After this the object
|
|
47
|
+
* is complete and immutable.
|
|
48
|
+
*/
|
|
35
49
|
finish(): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* The finished object's size and its footer's CRC: what names it, as a reader's fingerprint does. A writer that
|
|
52
|
+
* has to tell its own object from another stored later under the same key compares these with the object's
|
|
53
|
+
* footer. Throws before {@link finish}.
|
|
54
|
+
*/
|
|
55
|
+
get identity(): {
|
|
56
|
+
readonly size: number;
|
|
57
|
+
readonly footerCrc: number;
|
|
58
|
+
};
|
|
36
59
|
private ensurePreamble;
|
|
60
|
+
/**
|
|
61
|
+
* The extension block: one metadata section (`u8 type ‖ u32 length ‖ bytes`), then the trailer
|
|
62
|
+
* (`u32 sectionsLength ‖ u32 crc32c(sections ‖ sectionsLength) ‖ "CRBX"`). Encrypted, the section holds
|
|
63
|
+
* `nonce ‖ ciphertext ‖ tag`, sealed with the metadata scope of the associated data.
|
|
64
|
+
*/
|
|
65
|
+
private extensionBlock;
|
|
37
66
|
private encodeIndex;
|
|
38
67
|
private buildFooter;
|
|
39
68
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { type IAuditSink } from './audit.js';
|
|
2
2
|
import type { Yielder } from './cooperative.js';
|
|
3
|
-
import type { Clock } from './determinism.js';
|
|
3
|
+
import type { Clock, Rng } from './determinism.js';
|
|
4
4
|
import type { CrbmCrypto, IKeystore, WrappedDek } from './crypto.js';
|
|
5
|
-
import type { ChunkRef, StorageChunkSource, GenKey, IStorageDriver, IRegistryDriver, SegmentRef, SegmentSize, Token } from './ports.js';
|
|
5
|
+
import type { ChunkRef, StorageChunkSource, GenerationMetadata, GenerationSummary, GenKey, IStorageDriver, IRegistryDriver, RegistryRecord, RegistrySummary, SegmentRef, SegmentSize, Token } from './ports.js';
|
|
6
6
|
import { CrbmReader } from './crbm/reader.js';
|
|
7
7
|
import type { CrbmReaderOptions } from './crbm/reader.js';
|
|
8
8
|
import type { CodecBitmap, CodecInterface } from './codec.js';
|
|
9
|
+
import { DecodedLoadInput } from './load-input.js';
|
|
9
10
|
export interface CrbmStorageChunkSourceOptions extends CrbmReaderOptions {
|
|
10
11
|
/**
|
|
11
12
|
* Optional {@link IRegistryDriver}. When provided, the current generation is resolved via the registry's
|
|
@@ -57,8 +58,8 @@ export interface CrbmStorageChunkSourceOptions extends CrbmReaderOptions {
|
|
|
57
58
|
* Aggregate byte ceiling on the parsed `.crbm` indices resident in the reader cache (default 64 MiB) — the
|
|
58
59
|
* **second half of that memory bound**. `maxOpenSegments` alone bounds by *count*, but a wide/dense segment's
|
|
59
60
|
* parsed index can reach about 1.3 MB, so 1024 wide indices could pin over a GB and blow a small heap (e.g. a 128 MB
|
|
60
|
-
* Lambda) while the count is nominally "in bounds". This caps the summed {@link CrbmReader.
|
|
61
|
-
* across cached readers; the least-recently-used reader is evicted once the total would exceed it — whichever
|
|
61
|
+
* Lambda) while the count is nominally "in bounds". This caps the summed {@link CrbmReader.retainedBytes} (the
|
|
62
|
+
* parsed index, and any metadata a generation carries) across cached readers; the least-recently-used reader is evicted once the total would exceed it — whichever
|
|
62
63
|
* of the count/byte bounds binds first. Lower it for memory-tight deployments with wide segments; a single
|
|
63
64
|
* segment whose index alone exceeds the budget is still cached (it can't be shrunk) but nothing else alongside.
|
|
64
65
|
*/
|
|
@@ -110,6 +111,13 @@ export declare class CrbmStorageChunkSource implements StorageChunkSource {
|
|
|
110
111
|
* check again.
|
|
111
112
|
*/
|
|
112
113
|
private invalidations;
|
|
114
|
+
/**
|
|
115
|
+
* Generations whose row summary this process found to disagree with their object, by segment and generation. A
|
|
116
|
+
* summary here is not used again: a count of that generation reads the object. Bounded as the readers are, so a
|
|
117
|
+
* long-lived process cannot grow it without limit; one that falls out is trusted again until its next open finds
|
|
118
|
+
* the disagreement again.
|
|
119
|
+
*/
|
|
120
|
+
private readonly distrusted;
|
|
113
121
|
private readonly registry;
|
|
114
122
|
private readonly keystore;
|
|
115
123
|
private readonly requireEncryption;
|
|
@@ -117,7 +125,12 @@ export declare class CrbmStorageChunkSource implements StorageChunkSource {
|
|
|
117
125
|
private readonly clock;
|
|
118
126
|
private readonly currentGenTtlMs;
|
|
119
127
|
constructor(driver: IStorageDriver, options?: CrbmStorageChunkSourceOptions);
|
|
120
|
-
/**
|
|
128
|
+
/**
|
|
129
|
+
* The segment's live snapshot, refreshing on the TTL. Cheap within the TTL window. The snapshot holds the resolved
|
|
130
|
+
* target, and its reader once a read has asked for one.
|
|
131
|
+
*/
|
|
132
|
+
private liveSnapshot;
|
|
133
|
+
/** The current resolved reader for a segment, refreshing on the TTL. */
|
|
121
134
|
private resolvedReader;
|
|
122
135
|
/**
|
|
123
136
|
* A refresh failed transiently and the prior reader keeps serving: make this snapshot lapse after
|
|
@@ -125,12 +138,16 @@ export declare class CrbmStorageChunkSource implements StorageChunkSource {
|
|
|
125
138
|
* that asks the registry again. It costs no read of its own; it only changes when the next refresh happens.
|
|
126
139
|
*/
|
|
127
140
|
private retrySoon;
|
|
128
|
-
/** TTL elapsed: cheaply re-resolve `currentGen`; reopen only if it actually changed, else reuse the prior reader. */
|
|
129
|
-
private computeRefreshedReader;
|
|
130
141
|
/**
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
142
|
+
* TTL elapsed: cheaply re-resolve the pointer. The row read is always fresh, so what the new snapshot says of the
|
|
143
|
+
* generation (its summary included) is what the row says now, never the prior snapshot's. The reader is reopened
|
|
144
|
+
* only if the generation or the row changed, and only when a read asks for it.
|
|
145
|
+
*/
|
|
146
|
+
private refreshedTarget;
|
|
147
|
+
/**
|
|
148
|
+
* Memoize a snapshot as the segment's at `now()`, forgetting it later if it resolves to null / throws
|
|
149
|
+
* (identity-guarded, so a stale cleanup never clobbers a fresher snapshot). Installing the (pending) snapshot
|
|
150
|
+
* **synchronously** is what lets concurrent callers coalesce onto one in-flight resolve.
|
|
134
151
|
*/
|
|
135
152
|
private install;
|
|
136
153
|
private now;
|
|
@@ -273,26 +290,51 @@ export declare class CrbmStorageChunkSource implements StorageChunkSource {
|
|
|
273
290
|
*/
|
|
274
291
|
exists(ref: SegmentRef): Promise<boolean>;
|
|
275
292
|
/**
|
|
276
|
-
* Evict a snapshot we just failed to read from — matched by
|
|
293
|
+
* Evict a snapshot we just failed to read from — matched by the snapshot itself, so a concurrent call's
|
|
277
294
|
* fresher snapshot is never clobbered. The identity guard is the whole point: `install` replaces the entry
|
|
278
295
|
* wholesale, so comparing anything else would drop a good snapshot on the floor.
|
|
279
296
|
*/
|
|
280
297
|
private dropStale;
|
|
281
|
-
private resolveLatest;
|
|
282
298
|
/** Open a {@link CrbmReader} for an already-resolved generation target (decrypting if the segment is encrypted). */
|
|
283
299
|
private openForTarget;
|
|
300
|
+
private openGeneration;
|
|
301
|
+
/**
|
|
302
|
+
* Open the generation a resolution found, and hold the row's summary against the object while it is open anyway:
|
|
303
|
+
* the same count and metadata, or the summary is not believed again in this process ({@link distrusted}). This
|
|
304
|
+
* costs no request; a mismatch is not an error, since the object is the truth and this read has it.
|
|
305
|
+
*/
|
|
306
|
+
private openLive;
|
|
307
|
+
private distrustKey;
|
|
308
|
+
/** What the row says of the generation, unless it is a summary this process has found to disagree with its object. */
|
|
309
|
+
private trustedSummary;
|
|
284
310
|
/**
|
|
285
311
|
* The current generation + its DEK wrappings: the registry's authoritative record (one strong read), or a
|
|
286
312
|
* `list` scan for the max generation when there's no registry (a registry-less source can only read cleartext
|
|
287
|
-
* — there's nowhere a wrapped DEK could live).
|
|
313
|
+
* — there's nowhere a wrapped DEK could live). The row's summary rides along, to be used or not by whoever
|
|
314
|
+
* asks.
|
|
288
315
|
*/
|
|
289
|
-
private
|
|
316
|
+
private resolveLive;
|
|
290
317
|
/** Build the per-generation decryption context for an encrypted segment (undefined for cleartext). */
|
|
291
318
|
private cryptoForRead;
|
|
292
319
|
getChunk(ref: ChunkRef): Promise<Uint8Array | null>;
|
|
293
320
|
listChunkKeys(ref: SegmentRef): Promise<number[]>;
|
|
294
321
|
sizeOf(ref: SegmentRef): Promise<SegmentSize | null>;
|
|
295
322
|
cardinalities(ref: SegmentRef): Promise<ReadonlyMap<number, number> | null>;
|
|
323
|
+
/**
|
|
324
|
+
* The current generation's number, id count and metadata. The answer comes from the row the snapshot resolved when
|
|
325
|
+
* it can: the row's summary of the generation it names, on an active row, in the shape the row's keys call for
|
|
326
|
+
* (a sealed one is opened with the segment's key), and one this process has not found to disagree with its
|
|
327
|
+
* object. That is one registry read when the snapshot is cold, none when it is warm, and no read of the object.
|
|
328
|
+
* With no summary it can use (an older row, one for another generation, one that does not open), it opens the
|
|
329
|
+
* generation and answers from the object. A row that names an object that is gone still answers, while a read
|
|
330
|
+
* of the object throws: the number is true of the generation the row names.
|
|
331
|
+
*
|
|
332
|
+
* What the row says is not confirmed here: a summary edited to agree with itself by whoever can write the row is
|
|
333
|
+
* believed until the next time the object is opened, which holds it against the summary.
|
|
334
|
+
*/
|
|
335
|
+
summary(ref: SegmentRef): Promise<GenerationSummary | null>;
|
|
336
|
+
/** {@link summary} of a specific generation, from the object a pin holds. `held` as for {@link getChunkAt}. */
|
|
337
|
+
summaryAt(ref: SegmentRef, generation: number, held?: PinnedObject): Promise<GenerationSummary | null>;
|
|
296
338
|
/**
|
|
297
339
|
* Run `read` against the pinned snapshot, healing the torn-read window generation GC can open: if the
|
|
298
340
|
* generation we resolved was superseded *and* swept (the grace window elapsed), the Storage driver throws
|
|
@@ -320,6 +362,17 @@ export declare class CrbmStorageChunkSource implements StorageChunkSource {
|
|
|
320
362
|
* `intersect` would do it N times. Gated in `tests/core/storage-source-heal-open.test.ts` by counting calls.
|
|
321
363
|
*/
|
|
322
364
|
private withFreshSnapshot;
|
|
365
|
+
/**
|
|
366
|
+
* Whether `err`, raised by a read through the snapshot's reader, came from an object that is no longer the one the
|
|
367
|
+
* reader opened. A generation number can be taken again once its object is deleted (an erasure removes the
|
|
368
|
+
* generations above a rolled-back pointer that held the id, a collection pass can stop part-way, and a load then
|
|
369
|
+
* numbers `currentGen + 1` again), so a reader that still holds the old object's index reads the new object's
|
|
370
|
+
* bytes at the old offsets: a chunk checksum fails, or a range runs past the end. The object's footer says which it
|
|
371
|
+
* was, as it does for a pin ({@link CrbmReader.sameObject}): replaced, or gone, re-resolves; the same object means
|
|
372
|
+
* the error is real, and it stands. Only these two errors pay for the footer read, never a read that succeeded. A
|
|
373
|
+
* transient fault in the check is rethrown; a footer that cannot be read says nothing, and the error stands.
|
|
374
|
+
*/
|
|
375
|
+
private replacedUnder;
|
|
323
376
|
}
|
|
324
377
|
/**
|
|
325
378
|
* Write one immutable generation from in-memory bitmaps. Chunks are
|
|
@@ -333,9 +386,30 @@ export declare function writeCrbmGeneration(driver: IStorageDriver, key: GenKey,
|
|
|
333
386
|
}>, options?: {
|
|
334
387
|
crypto?: CrbmCrypto;
|
|
335
388
|
clock?: Yielder;
|
|
389
|
+
metadata?: GenerationMetadata;
|
|
336
390
|
}): Promise<{
|
|
337
391
|
size: number;
|
|
338
392
|
sha256: string;
|
|
393
|
+
fingerprint: string;
|
|
394
|
+
}>;
|
|
395
|
+
/**
|
|
396
|
+
* Whether the object under `key` is provably the one `fingerprint` names ({@link CrbmReader.fingerprint}), from one
|
|
397
|
+
* read of its footer. False when it is another object, when it is gone, and when the read fails in any way: a caller
|
|
398
|
+
* that deletes on `true` deletes nothing it cannot prove is its own.
|
|
399
|
+
*/
|
|
400
|
+
export declare function holdsObject(storage: IStorageDriver, key: GenKey, fingerprint: string): Promise<boolean>;
|
|
401
|
+
/** Whether the object under `key` says it is encrypted, from one read of its footer, with no key. */
|
|
402
|
+
export declare function objectIsEncrypted(storage: IStorageDriver, key: GenKey): Promise<boolean>;
|
|
403
|
+
/**
|
|
404
|
+
* What one tail read of the object under `key` tells a rollback about it: whether the footer says it is encrypted, and,
|
|
405
|
+
* when the object and the key agree (an encrypted object opened with `crypto`, a cleartext one with none), the reader
|
|
406
|
+
* the open gives, which holds its count and metadata. No reader when they disagree, which the caller refuses as a
|
|
407
|
+
* target the row cannot use, and none for an encrypted object when the caller has no key. A footer that fails its own
|
|
408
|
+
* checks, an object another generation's, and an index or metadata that do not open are an {@link IntegrityError}.
|
|
409
|
+
*/
|
|
410
|
+
export declare function openRollbackTarget(storage: IStorageDriver, key: GenKey, crypto: CrbmCrypto | undefined): Promise<{
|
|
411
|
+
encrypted: boolean;
|
|
412
|
+
reader: CrbmReader | undefined;
|
|
339
413
|
}>;
|
|
340
414
|
/** What {@link writeCrbmGenerationStream} wrote: the driver's `{ size, sha256 }` + a tally of the generation. */
|
|
341
415
|
export interface StreamWriteResult {
|
|
@@ -345,6 +419,8 @@ export interface StreamWriteResult {
|
|
|
345
419
|
readonly chunkKeys: number[];
|
|
346
420
|
/** Total ids written. */
|
|
347
421
|
readonly cardinality: number;
|
|
422
|
+
/** The fingerprint of what this call wrote ({@link CrbmReader.fingerprint}), which tells it from another object under its key. */
|
|
423
|
+
readonly fingerprint: string;
|
|
348
424
|
}
|
|
349
425
|
/**
|
|
350
426
|
* Streaming variant of {@link writeCrbmGeneration} for a **constant-memory** rewrite (the erasure rewrite, a
|
|
@@ -360,6 +436,7 @@ export declare function writeCrbmGenerationStream(driver: IStorageDriver, key: G
|
|
|
360
436
|
}>, options?: {
|
|
361
437
|
crypto?: CrbmCrypto;
|
|
362
438
|
clock?: Yielder;
|
|
439
|
+
metadata?: GenerationMetadata;
|
|
363
440
|
}): Promise<StreamWriteResult>;
|
|
364
441
|
/**
|
|
365
442
|
* Point a segment's registry `currentGen` at `key.generation` — the publish step that makes a freshly-written
|
|
@@ -385,17 +462,43 @@ export declare function writeCrbmGenerationStream(driver: IStorageDriver, key: G
|
|
|
385
462
|
* so the check and the write see the same row.
|
|
386
463
|
*
|
|
387
464
|
* **`expectToken` fences the LINEAGE, and a derived writer needs both.** A generation number identifies a
|
|
388
|
-
* generation only within one incarnation of a name:
|
|
465
|
+
* generation only within one incarnation of a name: the numbering restarts at `0` once the row is purged and
|
|
389
466
|
* the bucket empty, so a name that is retired and re-created has a *different* segment wearing the *same*
|
|
390
467
|
* `currentGen`. `expectFrom` alone matched it — and an erasure rewrite then published one incarnation's content
|
|
391
468
|
* over another's, deleted the live objects with its `keep: 0` collection, and returned `erased: true`. The row's
|
|
392
|
-
* OCC token is the identity that survives this:
|
|
393
|
-
*
|
|
469
|
+
* OCC token is the identity that survives this: a shipped registry's later `create` gets a token never issued before
|
|
470
|
+
* under that name, but for a collision of probability 2^-128 per pair of incarnations, so no token is reused across them.
|
|
394
471
|
*
|
|
395
472
|
* Pass the token read alongside `expectFrom` and the publish lands only on the same row it was derived from.
|
|
396
473
|
* The check is deliberately **conservative**: the token also changes on writes that are not supersessions at
|
|
397
474
|
* all (a `setRetention`, a due-index reindex), so such a write makes a derived publish report `superseded` and
|
|
398
475
|
* the caller re-derive. That costs a re-run on a rare, unrelated write; the alternative costs a segment.
|
|
476
|
+
*
|
|
477
|
+
* **The driver sends a registry write once, and an unanswered one is reconciled by its effect.** A `create` or
|
|
478
|
+
* `compareAndSwap` that fails with {@link TransientError} (a throttle, a lost response, a timeout) may or may not
|
|
479
|
+
* have landed, and may still be in flight. The publish reads the row once more and decides from what it holds:
|
|
480
|
+
*
|
|
481
|
+
* - the pointer names `key.generation`, on the incarnation the write was made against (the row's `createdAt`), and
|
|
482
|
+
* `holdsOwnObject` proves the object under it the caller's: the write landed, and the publish returns `true`. Only
|
|
483
|
+
* the writer of a write-once object publishes its number, and a number is never taken while an object holds it;
|
|
484
|
+
* - the row is still the one the write was made against (the same token, or still no row): the write may land yet. The
|
|
485
|
+
* publish then waits, on the injected clock, and sends a **new** write from the row it just read: a request of its
|
|
486
|
+
* own, not a replay of the driver's, carrying the same version, so under the registry's fence at most one of the two
|
|
487
|
+
* lands and the other meets a conflict, which the next read settles. At most {@link UNANSWERED_RESENDS} follow, after
|
|
488
|
+
* a wait under 500 ms, then 1 s, then 2 s (full jitter when an `rng` is given); each is settled the same way. Still
|
|
489
|
+
* unanswered, or with no clock to wait on, this throws the registry's own `TransientError` and writes nothing more.
|
|
490
|
+
* A caller must not delete its object on it;
|
|
491
|
+
* - anything else: the row has moved past the state the write was conditioned on, so that write can never land, and
|
|
492
|
+
* the publish goes on as after a lost race, from the row it just read. A pointer at `key.generation` that is not over
|
|
493
|
+
* the caller's own object, or is on another incarnation, is a lost race too and answers `false`, never an already
|
|
494
|
+
* current re-publish.
|
|
495
|
+
*
|
|
496
|
+
* A failed read throws `TransientError` too. The same check of the pointer runs after a {@link WriteConflictError},
|
|
497
|
+
* so a write that landed and then met itself (a registry whose client re-sent it) is not taken for a lost race.
|
|
498
|
+
*
|
|
499
|
+
* **A `summary` goes in the same write that moves the pointer**, so a reader that sees this generation as current
|
|
500
|
+
* sees the count and metadata that describe it, and every attempt sends the same one. It must be the shape the row's
|
|
501
|
+
* keys call for: sealed for an object written with a key, clear for one written without.
|
|
399
502
|
*/
|
|
400
503
|
export declare function publishGeneration(registry: IRegistryDriver, key: GenKey, options?: {
|
|
401
504
|
wrappedDeks?: readonly WrappedDek[];
|
|
@@ -417,7 +520,55 @@ export declare function publishGeneration(registry: IRegistryDriver, key: GenKey
|
|
|
417
520
|
* destination that does not exist yet is the ordinary first run of a pipeline.
|
|
418
521
|
*/
|
|
419
522
|
expectAbsent?: boolean;
|
|
523
|
+
/**
|
|
524
|
+
* The generation's summary (see {@link RegistrySummary}), written by the write that moves the pointer to it. It
|
|
525
|
+
* names `key.generation`. Absent, the row is left with none for this generation.
|
|
526
|
+
*/
|
|
527
|
+
summary?: RegistrySummary;
|
|
528
|
+
/**
|
|
529
|
+
* The segment's row as the caller already read it (`null`: it found none). The first attempt acts on it
|
|
530
|
+
* instead of reading the row again, which is sound because that attempt writes only under the row's own
|
|
531
|
+
* fence: its compare-and-swap carries this row's token, and with no row it creates create-only, so a row
|
|
532
|
+
* that changed or appeared since makes the write lose and the next attempt reads the row afresh. Only the
|
|
533
|
+
* idempotent "already current" answer writes nothing, so a reused row never gives it.
|
|
534
|
+
*/
|
|
535
|
+
row?: RegistryRecord | null;
|
|
536
|
+
/**
|
|
537
|
+
* The object was written without encryption. Its publish is refused, with {@link KeyUnavailableError}, onto a row
|
|
538
|
+
* that carries key material: a reader of that segment opens every generation with its key, and a later
|
|
539
|
+
* `destroySegment` would attest that shredding it made a readable object unreadable. A caller decides its key
|
|
540
|
+
* from the row it read before the write, so this closes the window in which another writer creates the
|
|
541
|
+
* segment encrypted meanwhile.
|
|
542
|
+
*/
|
|
543
|
+
cleartext?: boolean;
|
|
544
|
+
/**
|
|
545
|
+
* Whether the object under `key` is the one the caller wrote, and throws when it cannot tell. Asked only when a
|
|
546
|
+
* registry write failed and the row then names `key.generation`, to tell this publish's write from another
|
|
547
|
+
* writer's at the same number. Every caller that writes the object passes it. Without it, a write that ended
|
|
548
|
+
* without an answer over a row that names `key.generation` cannot be settled: the publish throws
|
|
549
|
+
* {@link TransientError} and the caller keeps its object.
|
|
550
|
+
*/
|
|
551
|
+
holdsOwnObject?: () => Promise<boolean>;
|
|
552
|
+
/**
|
|
553
|
+
* What a publish waits on before it sends a fresh compare-and-swap, after a write that ended without an answer and
|
|
554
|
+
* left the row as it was. Without a `sleep`, such a write throws at once instead of being sent again.
|
|
555
|
+
*/
|
|
556
|
+
clock?: Yielder;
|
|
557
|
+
/** Spreads that wait (full jitter) so concurrent publishers do not retry together. Without it the wait is the bound. */
|
|
558
|
+
rng?: Rng;
|
|
559
|
+
/**
|
|
560
|
+
* Called once for each registry write that ended without an answer, before the row is read back. A caller whose
|
|
561
|
+
* publish then answers `false` can say that it cannot tell whether its own write landed first: the row may have
|
|
562
|
+
* held its generation for a while before another writer moved on.
|
|
563
|
+
*/
|
|
564
|
+
onUnanswered?: () => void;
|
|
420
565
|
}): Promise<boolean>;
|
|
566
|
+
/**
|
|
567
|
+
* Whether the object under `key` is the one `fingerprint` names, from one read of its footer: false when it is another
|
|
568
|
+
* object or gone. Unlike {@link holdsObject}, a read that cannot tell throws, for a caller that must not take "could
|
|
569
|
+
* not look" for "not this object".
|
|
570
|
+
*/
|
|
571
|
+
export declare function provesOwnObject(storage: IStorageDriver, key: GenKey, fingerprint: string): Promise<boolean>;
|
|
421
572
|
/** What a bulk-load wrote — the driver's `{ size, sha256 }` plus a summary of the built generation. */
|
|
422
573
|
export interface BulkLoadResult {
|
|
423
574
|
/** Bytes written to the Storage object. */
|
|
@@ -436,8 +587,18 @@ export interface BulkLoadResult {
|
|
|
436
587
|
* unreadable: the bytes are encrypted with a key nothing recorded.
|
|
437
588
|
*/
|
|
438
589
|
readonly wrappedDeks?: readonly WrappedDek[];
|
|
590
|
+
/** Whether the object was written encrypted: under the segment's key, or under one this call minted. */
|
|
591
|
+
readonly encrypted: boolean;
|
|
592
|
+
/** The written object's fingerprint, which tells it from an object stored later under the same key. */
|
|
593
|
+
readonly fingerprint: string;
|
|
439
594
|
/** Total distinct ids in the generation (post-dedup). */
|
|
440
595
|
readonly cardinality: number;
|
|
596
|
+
/**
|
|
597
|
+
* The summary of what was written, for the write that makes it current: its id count and metadata, sealed under the
|
|
598
|
+
* segment's key when the object was written with one. A caller that defers the publish (`publish: false`) passes it
|
|
599
|
+
* to {@link publishGeneration}.
|
|
600
|
+
*/
|
|
601
|
+
readonly summary: RegistrySummary;
|
|
441
602
|
/**
|
|
442
603
|
* Whether this generation is now the segment's **current** one — `undefined` when no `registry` was wired, so
|
|
443
604
|
* there is no pointer and no publish step to report on.
|
|
@@ -467,11 +628,12 @@ export interface BulkLoadResult {
|
|
|
467
628
|
* commits only after the callback resolves). An empty source writes a valid empty generation.
|
|
468
629
|
*
|
|
469
630
|
* Writes a fresh full snapshot of a segment. The caller picks the generation number in `key` — `nextGeneration`
|
|
470
|
-
* computes
|
|
631
|
+
* computes one from the registry and the bucket, as `nextLoadGeneration` does for a load — and re-using an existing
|
|
632
|
+
* generation throws
|
|
471
633
|
* {@link WriteConflictError} (write-once). Without a registry a `StorageChunkSource` serves the **highest**
|
|
472
634
|
* generation present, so a too-high number silently shadows real data; with one, `publishGeneration` decides.
|
|
473
635
|
*/
|
|
474
|
-
export declare function bulkLoadCrbmGeneration(driver: IStorageDriver, key: GenKey, ids: Iterable<number> | AsyncIterable<number
|
|
636
|
+
export declare function bulkLoadCrbmGeneration(driver: IStorageDriver, key: GenKey, ids: Iterable<number> | AsyncIterable<number> | DecodedLoadInput, options?: {
|
|
475
637
|
registry?: IRegistryDriver;
|
|
476
638
|
keystore?: IKeystore;
|
|
477
639
|
requireEncryption?: boolean;
|
|
@@ -502,6 +664,22 @@ export declare function bulkLoadCrbmGeneration(driver: IStorageDriver, key: GenK
|
|
|
502
664
|
* current.
|
|
503
665
|
*/
|
|
504
666
|
publish?: boolean;
|
|
667
|
+
/**
|
|
668
|
+
* The generation's metadata (see {@link GenerationMetadata}), written into the object and carried in the summary.
|
|
669
|
+
* The caller has checked it against the metadata rules; the writer checks it again, and refuses a record that breaks
|
|
670
|
+
* one before a byte is written.
|
|
671
|
+
*/
|
|
672
|
+
metadata?: GenerationMetadata;
|
|
673
|
+
/**
|
|
674
|
+
* The segment's row as the caller already read it (`null`: it found none), for a caller that defers the
|
|
675
|
+
* publish (`publish: false`) and fences it on that same row. A present cleartext row is then used as read,
|
|
676
|
+
* rather than read again: a row that changes in between (a publish, a drop, a purge and re-create) makes the
|
|
677
|
+
* fenced publish lose, so the object is never published on the strength of the stale read. A row read as
|
|
678
|
+
* absent, or one carrying key material, is read again after the ids, as with no row passed: a first load must
|
|
679
|
+
* see a row another writer created meanwhile, and an encrypted segment's key is unwrapped only from a row read
|
|
680
|
+
* after the ids, so a segment shredded while they streamed is refused before its key is used.
|
|
681
|
+
*/
|
|
682
|
+
row?: RegistryRecord | null;
|
|
505
683
|
}): Promise<BulkLoadResult>;
|
|
506
684
|
/**
|
|
507
685
|
* Open a {@link CrbmReader} on one generation over the storage driver's range/tail reads (decrypting if `crypto`).
|
|
@@ -511,12 +689,15 @@ export declare function bulkLoadCrbmGeneration(driver: IStorageDriver, key: GenK
|
|
|
511
689
|
export declare function openGenerationReader(storage: IStorageDriver, key: GenKey, crypto: CrbmCrypto | undefined, options?: Omit<CrbmReaderOptions, 'crypto'>): Promise<CrbmReader>;
|
|
512
690
|
/**
|
|
513
691
|
* Re-open a freshly written generation and assert it round-trips exactly what was streamed into it: the same
|
|
514
|
-
* per-chunk key set *and* the same total cardinality (on top of the codec's own per-chunk CRC + footer checks)
|
|
515
|
-
* `expected` is the streaming writer's tally (the stream is consumed, so it can't be
|
|
516
|
-
* comparison catches a dropped/extra chunk that a cardinality-only check could miss when
|
|
692
|
+
* per-chunk key set *and* the same total cardinality (on top of the codec's own per-chunk CRC + footer checks), and the
|
|
693
|
+
* metadata it was given. `expected` is the streaming writer's tally (the stream is consumed, so it can't be
|
|
694
|
+
* re-iterated) — the key-set comparison catches a dropped/extra chunk that a cardinality-only check could miss when
|
|
695
|
+
* two errors cancel out. The count and the metadata are what the generation's row summary is about to claim, so the
|
|
696
|
+
* summary is held to the object it describes before it is published.
|
|
517
697
|
* Throws {@link IntegrityError}: the object is on disk but must not be published.
|
|
518
698
|
*/
|
|
519
699
|
export declare function verifyGeneration(storage: IStorageDriver, key: GenKey, expected: {
|
|
520
700
|
readonly chunkKeys: readonly number[];
|
|
521
701
|
readonly cardinality: number;
|
|
702
|
+
readonly metadata?: GenerationMetadata | undefined;
|
|
522
703
|
}, crypto: CrbmCrypto | undefined): Promise<void>;
|
package/dist/core/crypto.d.ts
CHANGED
|
@@ -62,19 +62,23 @@ export interface IKeystore {
|
|
|
62
62
|
}
|
|
63
63
|
/**
|
|
64
64
|
* What the encrypting codec needs: the {@link Aead} for this segment's DEK plus a way to build the AAD for a
|
|
65
|
-
* given scope (`'index'
|
|
66
|
-
* codec itself stays segment-agnostic.
|
|
65
|
+
* given scope (a chunkKey, `'index'`, or `'metadata'` for the generation's metadata section). Built per
|
|
66
|
+
* (segment, generation) by the storage-source bridge so the codec itself stays segment-agnostic.
|
|
67
67
|
*/
|
|
68
68
|
export interface CrbmCrypto {
|
|
69
69
|
readonly aead: Aead;
|
|
70
|
-
aadFor(scope: number | 'index'): Uint8Array;
|
|
70
|
+
aadFor(scope: number | 'index' | 'metadata'): Uint8Array;
|
|
71
71
|
}
|
|
72
72
|
/**
|
|
73
|
-
* Build the AEAD associated-data binding a chunk
|
|
74
|
-
*
|
|
75
|
-
*
|
|
73
|
+
* Build the AEAD associated-data binding a chunk, the index, the metadata section or a registry row's sealed summary
|
|
74
|
+
* to its exact location:
|
|
75
|
+
* `v1 ‖ len(namespace) ‖ namespace ‖ len(segment) ‖ segment ‖ generation ‖ scope ‖ chunkKey`, where scope is
|
|
76
|
+
* `0` for a chunk, `1` for the index, `2` for the metadata and `3` for the row's summary, and chunkKey is `0` for
|
|
77
|
+
* the last three.
|
|
78
|
+
* Length-prefixed so distinct `(namespace, segment)` pairs can never collide. Pure (no crypto) — just the
|
|
79
|
+
* authenticated label.
|
|
76
80
|
*/
|
|
77
81
|
export declare function aadFor(ref: {
|
|
78
82
|
readonly namespace?: string;
|
|
79
83
|
readonly segment: string;
|
|
80
|
-
}, generation: number, scope: number | 'index'): Uint8Array;
|
|
84
|
+
}, generation: number, scope: number | 'index' | 'metadata' | 'summary'): Uint8Array;
|
|
@@ -40,3 +40,10 @@ export interface Rng {
|
|
|
40
40
|
/** A float in the half-open interval [0, 1). */
|
|
41
41
|
next(): number;
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Injected entropy: `length` cryptographically strong random bytes, for an identity that must never repeat (a
|
|
45
|
+
* registry row's incarnation id, and the random part every registry write adds to its token). Deliberately not an
|
|
46
|
+
* {@link Rng}: an `Rng` is seedable for simulation, and a seeded source hands every process the same "random"
|
|
47
|
+
* identity. Production wiring uses the platform's Web Crypto; a test injects a deterministic source to replay a run.
|
|
48
|
+
*/
|
|
49
|
+
export type Entropy = (length: number) => Uint8Array;
|